Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Node Express Boilerplate

CI License: MIT Node TypeScript PRs Welcome

A production-grade Node.js + Express REST API boilerplate built on TypeScript 7, PostgreSQL, and Prisma — a complete starter template with JWT authentication and refresh-token rotation, Zod-driven OpenAPI documentation, and Docker-based dev/stage/prod environments. It's built around a small reference domain — auth, inventory, orders, and reports — to demonstrate real patterns (layered architecture, transactional writes, environment-parity containers) rather than a bare "hello world" starter.

It runs on TypeScript 7, the native Go-ported TypeScript compiler — see Why TypeScript 7? for what that buys the project.

Table of Contents

Features

  • Layered, feature-based architecture — routes → controller → service → repository, enforced consistently across every module (see Project Architecture).
  • JWT auth with refresh-token rotation — short-lived signed access tokens (jose), opaque DB-tracked refresh tokens with reuse detection.
  • Schema-first validation and docs — Zod schemas validate every request and double as the source for an auto-generated OpenAPI spec, served live at /docs.
  • Transactional writes where it matters — e.g. placing an order atomically decrements stock and creates the order in one database transaction.
  • Environment-parity Docker setup — one Dockerfile, one base docker-compose.yml, and a thin overlay per environment (dev/stage/prod), all driven through a single Makefile.
  • Layered test strategy — fast fake-repository unit tests with no database, plus real-Postgres integration tests against a fully isolated, disposable test stack.

Why TypeScript 7?

This project intentionally runs on TypeScript 7 (typescript@^7.0.2, released 2026-07-08) instead of TypeScript 6 — a from-scratch, Go-ported reimplementation of the compiler and language service, not a new language version. The type system and tsconfig.json options you already know are unchanged; what changes is the engine running them.

The practical payoff is speed: native compiled code lets tsc type-check and build a codebase like this one substantially faster than the JavaScript-based compiler did, which is felt directly in this project's npm run typecheck step, npm run build, and every CI run that shells out to either.

The trade-off is ecosystem maturity, not capability:

  • typescript-eslint doesn't support TS7 yet — it hard-throws at import time (tracked upstream at typescript-eslint#10940). This project uses Biome for linting and formatting instead of ESLint as a result. npm run typecheck (native tsc --noEmit) remains the actual source of truth for type errors either way — Biome only lints style and common mistakes, it does not type-check.
  • tsx (the dev-time runner) is esbuild-based and never touches the typescript package's compiler API, so it's unaffected and behaves exactly as it would under TS6.

If typescript-eslint ships TS7 support later, swapping back to ESLint is a contained, optional follow-up — nothing here depends on Biome specifically.

Tech Stack

Layer Choice
Runtime Node 22, Express 5
Language TypeScript 7 (tsc for build/typecheck, tsx for dev)
Database PostgreSQL via Prisma 7 (driver-adapter based, @prisma/adapter-pg)
Auth JWT access tokens (jose) + rotated, DB-tracked opaque refresh tokens
Validation & docs Zod, doubling as the OpenAPI schema source (@asteasolutions/zod-to-openapi)
Testing Vitest + Supertest
Lint / format Biome
Containers Multi-stage Dockerfile + Compose overlays for dev/stage/prod, via Makefile

Prerequisites

  • Node.js ≥ 22.19 and npm
  • Docker + Docker Compose (recommended path), or a local PostgreSQL instance if running bare-metal

Getting Started

Docker (recommended)

cp .env.example .env.development   # fill in real secrets
make dev-up                        # builds + starts app (hot-reload) and Postgres together
make migrate                       # applies migrations

Then:

To seed demo data (an admin user, 20 demo users, 40 inventory items, and 150 orders):

make seed

Stop the stack with make dev-down (keeps data) or make clean (also drops the Postgres volume).

Bare-metal (no Docker)

npm install
cp .env.example .env.development   # point DATABASE_URL at your own Postgres
npx prisma generate
npx prisma migrate dev
npm run dev

Environment Configuration

Three environment files, one per deployment target — only .env.example is committed:

File Used by
.env.development npm run dev and make dev-up
.env.staging make stage-up
.env.production make prod-up

src/config/env.ts validates every variable at boot with Zod and fails fast, with a clear error, if anything required is missing or malformed.

Available npm Scripts

Command What it does
npm run dev Start the dev server (tsx, hot-reload, no Docker)
npm run build Compile to dist/ (native tsc)
npm start Run the compiled build
npm run typecheck tsc --noEmit — the real source of truth for type errors
npm run lint / lint:fix Biome lint
npm run format Biome format (writes changes)
npm run format:check Biome format check (no changes, CI-friendly)
npm test Full test suite (needs a reachable Postgres)
npm run test:unit Fake-repository unit tests only — no database needed
npm run test:integration Real-Postgres integration tests
npm run test:watch Vitest in watch mode
npm run test:coverage Full suite with coverage report
npm run prisma:generate Regenerate the Prisma client from schema.prisma
npm run prisma:migrate Create + apply a dev migration
npm run prisma:migrate:deploy Apply already-committed migrations (no new ones generated)
npm run prisma:seed Seed an admin user + sample inventory/orders

Docker & Make Targets

Target What it does
make dev-up / make dev-down Build + start app (hot-reload) + Postgres together / stop the dev stack (keeps data)
make stage-up / make stage-down Build + start the immutable runtime image against .env.staging / stop it
make prod-up / make prod-down Build + start the immutable runtime image against .env.production / stop it
make migrate Apply already-committed migrations (prisma migrate deploy); ENV=staging make migrate to target another env
make makemigrations Dev-only: create + apply a new migration from schema changes (NAME=add_foo make makemigrations to skip the naming prompt)
make seed Seed demo data (ENV=staging make seed to target another env)
make test Bring up an isolated test Postgres (own project/port/volume — safe to run even while make dev-up is up), migrate, run the suite on the host, tear back down
make clean Stop the dev stack and drop the Postgres volume
make down-test Recovery: tear down the test stack if a make test run got interrupted
make logs Tail the app container's logs

Adding another backing service (e.g. Redis) later only means adding one block to docker-compose.yml — every environment overlay and make dev-up/stage-up/prod-up picks it up automatically.

Project Architecture

Feature-based modules under src/modules/<feature>/ (auth, health, inventory, orders, reports), each owning its own routes → controller → service(s) → validation → repository. A few rules hold across every module:

  • Controllers call only their module's service(s) — never a repository, never Prisma directly.
  • Services call only their module's repository (or another module's service, for cross-module composition) — never Prisma directly. This is what makes services unit-testable with fake repositories and no database (see tests/unit/).
  • Repositories are the only files that import the Prisma client. Every write method takes an optional trailing tx parameter so it can run standalone or inside someone else's transaction.
  • Transactions are owned by the service that knows an operation must be atomic — see orders.service.createOrder(), which wraps inventoryService.adjustStock() (the module owning the "stock can't go negative" rule) and ordersRepository.create() in one withTransaction() call.
  • Cross-module reads that don't belong to any single module (the sales-summary report, joining orders + inventory + users) live in src/shared/repositories/, not inside one module's repository.
  • Pagination (src/shared/utils/pagination.ts) is shared across every list endpoint instead of hand-rolled per module.
src/
├── modules/{health,auth,inventory,orders,reports}/
├── shared/{utils,repositories}/     # cross-module concerns
├── middlewares/                     # security, validation, error handling
├── lib/                             # Prisma client + transaction helper
├── docs/                            # OpenAPI registry + generation
└── config/                          # env validation, logger

Testing Strategy

Two distinct suites, kept intentionally separate:

  • Unit tests (tests/unit/) exercise services against fake, in-memory repository implementations (tests/helpers/fakeRepositories/) — no database, no Docker, fast enough to run on every save.
  • Integration tests (tests/integration/) exercise the real HTTP layer against a real Postgres database. make test brings up a fully isolated Postgres (its own Compose project, port, and volume — safe to run even while make dev-up is already up), applies migrations, runs the full suite, and tears the stack back down afterward. Each test file resets every table before running (tests/helpers/db.ts), and integration files run sequentially rather than in parallel to avoid one file's reset racing another's in-flight requests.

Always run make test for a full, correct run — invoking npm test directly requires that isolated Postgres to already be up and migrated (see the Makefile's test target for the exact sequence).

API Documentation

Interactive Swagger UI is served at /docs once the app is running, generated directly from the Zod validation schemas via @asteasolutions/zod-to-openapi — request/response shapes in the docs are guaranteed to match what the API actually validates, because they come from the same source.

About

Production-grade Node.js + Express API boilerplate on TypeScript 7 — JWT auth with refresh rotation, PostgreSQL/Prisma, Zod-driven OpenAPI docs, and Docker Compose for dev/stage/prod.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages