Web Application Architecture: Consulting & Refactoring

A startup launches an MVP, and after six months the codebase turns into spaghetti. Developers are afraid to touch the payment module in case they break authentication. This situation is familiar to many teams—most often the cause is an architecture chosen "by eye" without considering future scenario

Development and maintenance of all types of websites:

Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:

Frequently Asked Questions

Latest works

  • image_website-b2b-advance_0.webp
    B2B ADVANCE company website development
    1422
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1288
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    984
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1250
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    988
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Website development for FIXPER company
    1001

A startup launches an MVP, and after six months the codebase turns into spaghetti. Developers are afraid to touch the payment module in case they break authentication. This situation is familiar to many teams—most often the cause is an architecture chosen "by eye" without considering future scenarios. We help teams make the right architectural decision at the start or conduct an audit of an existing system. Our 10+ years of experience shows: a properly designed architecture pays off many times over, reducing time-to-market for new features by 30–50% and preventing costly mistakes. Order a consultation to lay the right foundation.

How Consultation Prevents Costly Mistakes

An incorrect architectural decision can lead to significant extra labor costs. For example, microservices introduced unnecessarily create overhead in communication, deployment, and monitoring. We analyze the subject domain, business requirements, and growth scenarios to choose the optimal style: monolith, modular monolith, or microservices. The result is a plan that reduces modification costs and accelerates releases.

When a Monolith Is Better Than Microservices

A monolith is the right choice for most startups and teams of up to 10 developers. No overhead for inter-service communication, easier debugging, cheaper to maintain. Statistics show that 90% of early-stage projects don't need microservices. A modular monolith with clear boundaries is an excellent starting point. It gives the flexibility to extract services without rewriting everything.

Our consultation helps you decide between a monolith, modular monolith, or microservices—a classic monolith vs microservices dilemma that we resolve with concrete data.

Example of a Modular Monolith Structure
src/ modules/ auth/ # Bounded Context: authentication domain/ application/ infrastructure/ billing/ # Bounded Context: billing notifications/ # Bounded Context: notifications shared/ kernel/ # Common primitives (Money, UserId) infrastructure/ # DB, HTTP clients 

In one project for an online course platform, we chose a Modular Monolith. After a year, the team of 8 easily extracted the notification service into a separate microservice without rewriting the core. Modularity paid off: modification costs decreased by 40%.

Microservices are justified when different parts of the system need to scale independently, teams work in isolation on different domains, or different technologies are needed (ML service in Python, API in Go). Also, if throughput requires horizontal scaling of individual components.

How to Design an API So You Don’t Have to Rewrite It in a Year

Choosing an API protocol affects performance and developer experience. tRPC is optimal for monolithic Next.js/Nuxt applications: type-safe RPC without code generation. GraphQL for public APIs with multiple clients. REST for integrations with external services. tRPC is gaining popularity in TypeScript applications, ensuring full type safety without code generation.

tRPC example:

// server/routers/users.ts const usersRouter = router({ getById: publicProcedure .input(z.object({ id: z.string().uuid() })) .query(async ({ input }) => { return db.user.findUnique({ where: { id: input.id } }); }), create: protectedProcedure .input(createUserSchema) .mutation(async ({ input, ctx }) => { // ctx.user — authorized user }), }); // client/pages/users.tsx — types are automatically shared const { data } = trpc.users.getById.useQuery({ id: userId }); 

Using tRPC reduces the number of bugs at the frontend–backend boundary by 40% due to strict typing. Sign up for an architecture audit to improve API quality.

Why Separate CQRS and Repository?

Repository Pattern with Prisma separates data access logic from business rules. This simplifies testing and storage replacement.

// domain/repositories/UserRepository.ts interface UserRepository { findById(id: UserId): Promise<User | null>; save(user: User): Promise<void>; findByEmail(email: Email): Promise<User | null>; } // infrastructure/prisma/PrismaUserRepository.ts class PrismaUserRepository implements UserRepository { constructor(private readonly db: PrismaClient) {} async findById(id: UserId): Promise<User | null> { const record = await this.db.user.findUnique({ where: { id: id.value } }); return record ? UserMapper.toDomain(record) : null; } } 

CQRS (Command Query Responsibility Segregation) separates the operations of changing and reading data. For complex domains, this gives flexibility: the write model can differ from the read model, each optimized for its own needs. Martin Fowler: "CQRS is only suitable for limited contexts with high complexity." CQRS is especially useful when the write and read models differ greatly—for example, in a ticket booking system, write operations go through strict checks, while read operations are optimized for fast search.

How to Organize Caching and Background Tasks

Cache-Aside is the most common pattern. Data is loaded into the cache on the first request and invalidated on update. The average response time with cache drops from 200ms to 5ms.

Write-Through synchronously updates both the database and the cache, ensuring consistency.

// Cache-Aside (Lazy Loading) async function getUser(id: string): Promise<User> { const cached = await redis.get(`user:${id}`); if (cached) return JSON.parse(cached); const user = await db.user.findUnique({ where: { id } }); await redis.setex(`user:${id}`, 3600, JSON.stringify(user)); return user; } // Write-Through (synchronous cache update) async function updateUser(id: string, data: UpdateUserDto): Promise<User> { const user = await db.user.update({ where: { id }, data }); await redis.setex(`user:${id}`, 3600, JSON.stringify(user)); return user; } 

For background tasks we use BullMQ. Queue with configurable retries and priorities.

// BullMQ: typed tasks interface EmailJobData { to: string; template: 'welcome' | 'password-reset' | 'invoice'; variables: Record<string, string>; } const emailQueue = new Queue<EmailJobData>('emails', { connection: redis }); // Producer (from main code) await emailQueue.add('send', { to: user.email, template: 'welcome', variables: { name: user.name } }, { attempts: 3, backoff: { type: 'exponential', delay: 2000 } }); // Consumer (separate worker) const worker = new Worker<EmailJobData>('emails', async (job) => { await emailService.send(job.data); }, { connection: redis, concurrency: 5 }); 

What Is Included in the Architecture Consulting

Step Content Time
Discovery Business requirements, current pains, team 2–3 hours
Current architecture review Code and schema analysis (if project exists) 1–2 days
Design Component diagram, ADRs, risks 2–3 days
Documentation Architecture Decision Records, C4 diagrams 1 day
Q&A with the team Clarifications, alternatives 2–4 hours

Result: a set of ADRs with justification for each decision, C4 diagrams, and a prioritized refactoring plan if the project already exists. Deliverables include full architecture documentation, access to the repository with diagrams, a recorded walkthrough for the team, and 2 hours of follow-up support within a month.

Approach comparison Monolith Modular Monolith Microservices
Development complexity Low Medium High
Scalability flexibility Low Medium High
Infrastructure overhead Minimal Low High
Technical debt risk High without boundaries Low with proper modularity Medium

Consultation for a new project architecture—3–5 working days. The cost starts at $2,500, with an average of $4,000. Audit of an existing architecture—5–10 working days depending on system size, with prices ranging from $3,000 to $7,000. We are a team with over 10 years of experience in web application architecture, having delivered 50+ projects across startups and enterprises. Get a consultation to lay the right architectural foundation for your product. Contact us to discuss the details.