Skip to content

Node.js and TypeScript monorepos

Default for new products

New DoWEB software products use a Node.js and TypeScript monorepo unless there is a documented reason not to. The default toolchain is:

  • Node.js 24 LTS, pinned in .nvmrc or .tool-versions and in the Dockerfile;
  • pnpm workspaces, with the pnpm version pinned in the root package.json packageManager field;
  • Turborepo for running cached workspace tasks; and
  • TypeScript in strict mode.

Node versions are reviewed every six months. Production applications use only an actively supported Node.js LTS release.

Repository layout

cus-acme-portal/
├── apps/
│   ├── web/                 # customer-facing web application
│   ├── api/                 # HTTP API
│   └── worker/              # optional background jobs
├── packages/
│   ├── api-client/          # reusable API client
│   ├── ui/                  # reusable UI components
│   ├── types/               # shared domain types
│   ├── eslint-config/       # shared lint configuration
│   └── tsconfig/            # shared TypeScript configuration
├── infra/                   # deployment-only configuration when needed
├── package.json
├── pnpm-workspace.yaml
├── pnpm-lock.yaml
├── turbo.json
└── README.md

apps/ contains independently buildable and deployable products. packages/ contains deliberately reusable code and configuration. Do not use packages/ as a dumping ground: a package must have a named owner, a clear API, and at least two consumers or a stated future reuse case.

Deploy applications from apps/; do not deploy a shared package directly.

Product architecture

Start with the smallest architecture that meets the customer need: normally one web application, one API, and one database. Add a worker only for genuinely asynchronous work. Do not introduce microservices, event infrastructure, or extra databases merely for future flexibility.

Each deployable application must:

  • own its route handlers, domain logic, and tests;
  • expose configuration through a single validated configuration module;
  • read secrets only from environment variables or injected secret files;
  • emit structured logs to standard output; and
  • expose a health endpoint when deployed as a service.

Shared packages may not import from an application. Applications may depend on shared packages through their public package exports, never through another workspace's src/ directory.

Root scripts and local workflow

Every new monorepo provides these root scripts:

pnpm format:check
pnpm lint
pnpm typecheck
pnpm test
pnpm build

Use corepack enable and pnpm install --frozen-lockfile in CI. A contributor should be able to clone the repository, copy .env.example to a local .env, install dependencies, and run the same checks locally.

Quality baseline

  • TypeScript strict mode is enabled for application and shared-package code.
  • Use one root ESLint and Prettier configuration; applications may extend it but may not replace it without an exception.
  • Add automated tests for business rules, integration boundaries, and regressions. A UI-only snapshot is not sufficient coverage for customer-critical logic.
  • Keep API contracts and database migrations in the application that owns them.

Exceptions

Use a separate repository, a different language, or a different architecture only when it materially reduces risk or cost. Document the decision, its owner, and the review date in the repository README. Examples include a customer-required platform, a vendor integration, or a small isolated script that does not benefit from a workspace.