Search across all documentation pages
8 examples to get you started with Architecture & Design for Node.js backends - 6 basic and 2 intermediate.
These examples assume Node.js 24.18.0, TypeScript 5.6+, and a small Express 5 or Fastify 5 API.
mkdir orders-api && cd orders-api
npm init -y
npm install express@5 zod
npm install -D typescript@5.6 @types/node @types/express tsx
npx tsc --initOne deployable, clear module boundaries inside the repo.
src/
├── modules/
│ ├── orders/
│ │ ├── domain/
│ │ ├── application/
│ │ └── infrastructure/
│ └── billing/
├── shared/
└── main.tsRelated: Modular Monolith - package-by-feature inside one deployable
Routes translate HTTP; domain code stays framework-agnostic.
// src/modules/orders/application/create-order.ts
export type CreateOrderInput = { customerId: string; sku: string; qty: number };
export function createOrder(input: CreateOrderInput) {
if (input.qty <= 0
// src/modules/orders/infrastructure/http/routes.ts
import express from "express";
import { createOrder } from "../../application/create-order";
export const ordersRouter = express.Router();
ordersRouter.post("/", (req, res) => {
createOrder has no Request or Response imports - unit tests run without HTTPRelated: Hexagonal Architecture in Node - ports and adapters layout
A port is an interface the domain depends on; adapters implement it.
// src/modules/orders/domain/ports/order-repository.ts
export type Order = { id: string; customerId: string; sku: string; qty: number };
export interface OrderRepository {
save(order:
// src/modules/orders/infrastructure/postgres-order-repository.ts
import type { Order, OrderRepository } from "../domain/ports/order-repository";
export class PostgresOrderRepository implements OrderRepository {
constructor(private readonly pool: { query: (sql: string, params:
pg, or Redis clientsGroup everything for "orders" together instead of a global controllers/ tree.
src/modules/orders/
├── domain/
│ ├── order.ts
│ └── ports/
├── application/
│ ├── create-order.ts
│ └── list-orders.ts
└── infrastructure/
├── http/routes.ts
└── postgres-order-repository.tsimport/no-restricted-paths can enforce module boundariesRelated: Modular Monolith - enforcing boundaries in one repo
Distributed systems buy independence at the cost of network, ops, and consistency.
| Signal | Stay monolith | Consider services |
|---|---|---|
| Team size | 1-3 squads on one product | 4+ squads stepping on same deploy |
| Deploy cadence | Weekly or daily shared release | Need hourly deploys per domain |
| Data coupling | Shared transactions across features | Clear bounded contexts with rare cross-joins |
| Ops maturity | Single on-call rotation | Platform team for mesh, tracing, SLOs |
Related: Microservices When Worth It - team and cost trade-offs
Architecture Decision Records survive reorgs and memory loss.
# ADR-003: Stay Modular Monolith for 2026
## Status
Accepted
## Context
Three squads, 40k RPS peak, shared Postgres. No dedicated platform team.
## Decision
Remain a modular monolith. Revisit when billing needs independent deploy cadence.
## Consequences
+ Faster feature delivery, simpler debugging
- Billing deploys wait on orders releasesRelated: ADR: Monolith vs Services - Node-specific template
The composition root is the only place that knows concrete implementations.
// src/main.ts
import express from "express";
import { PostgresOrderRepository } from "./modules/orders/infrastructure/postgres-order-repository";
import { ordersRouter } from "./modules/orders/infrastructure/http/routes";
const pool = { query: async () => [] }; // replace with real pg.Pool
const
main.ts or a dedicated composition-root.tsRun a structural checklist before extracting a service.
// scripts/architecture-audit.ts - grep for cross-module imports
import fs from "node:fs";
import path from "node:path";
const modulesDir = "src/modules";
for (const mod of fs.readdirSync(modulesDir)) {
const files = fs.readdirSync
index.ts re-exports everything, fix modularity inside the monolith firstRelated: Refactoring Checklist - god
index.tsand route sprawl audits
Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.