A production-ready Express starter that wires together Inversify for dependency injection, Prisma as the ORM, and TypeScript-first tooling. It comes with sane defaults for configuration, logging, testing, and documentation so you can start shipping features immediately.
- Type-safe stack: Express, TypeScript, Inversify, Prisma, Zod validation, and JWT auth helpers.
- Pragmatic DX: Nodemon-like hot reload via
ts-node-dev, Jest unit tests, path aliases, and typed configuration parsing. - Production touches: Graceful shutdown, structured logging with optional remote shipping, health checks, Docker multi-stage build, and Postgres docker-compose file.
- API documentation: Swagger (OpenAPI 3) docs exposed at
/api-docsin non-production environments.
- Node.js 18+
- npm (comes with Node)
- PostgreSQL database (local Docker Compose recipe provided)
- Install dependencies
npm install
- Provision a database (optional but recommended)
docker compose up -d
- Create your environment file
cp .env.example .env # update DATABASE_URL and JWT_SECRET at minimum - Run Prisma migrations
npx prisma migrate dev
- Start the app in watch mode
The server listens on
npm run dev
http://localhost:3000by default. AdjustPORTin.envif needed.
| Script | Description |
|---|---|
npm run dev |
Start Express with hot-reload (ts-node-dev). |
npm run build |
Compile TypeScript to dist/ and rewrite path aliases (tsc + tsc-alias). |
npm run start:prod |
Run the compiled server from dist/server.js. |
npm run test |
Execute Jest unit tests with coverage. |
npm run docker:build |
Build the production Docker image defined in Dockerfile. |
- Generate client after schema changes:
npx prisma generate - Create & apply new migration:
npx prisma migrate dev --name <migration-name> - Reset local database (destructive):
npx prisma migrate reset
The Prisma client is wrapped in src/prisma/prisma.service.ts and injected via Inversify so repositories stay testable.
- Health probe:
GET /health - User module (
src/modules/user)POST /api/users/registerPOST /api/users/login
Swagger UI (/api-docs) and the raw spec (/swagger.json) are registered automatically when NODE_ENV !== "production".
Configuration comes from environment variables validated by Zod (see src/config/index.ts). Required variables:
JWT_SECRET– secret used to sign authentication tokens.
Optional variables:
PORT(3000default)NODE_ENV(development,test,production)DATABASE_URL– PostgreSQL connection string used by Prisma.LOG_LEVEL– Pino log level (info,debug, etc.).LOG_ENDPOINT– HTTPS endpoint to forward JSON logs (non-blocking).
- Access logs via
src/middleware/request.logger.ts. - Application logging centralised in
src/utils/logger.ts(Pino-based). - Graceful shutdown in
src/server.tshandles SIGINT/SIGTERM, HTTP server close, and Prisma disconnect.
Run all tests with coverage:
npm run testJest is configured in jest.config.js with TypeScript support and module path aliases.
src/
app.ts # Express bootstrap when not using inversify-express-utils
server.ts # Primary entry point with DI container
inversify.config.ts # Inversify bindings
modules/user/ # Example feature module (controller, service, repository)
middleware/ # Request logger, Auth, error handling
prisma/ # Prisma service wrapper
services/ # Shared services (crypto utilities)
docs/ # Swagger wiring and components
public/ # Static assets (served from Express)
prisma/
schema.prisma # Prisma data model
migrations/ # Auto-generated SQL migrations
compose.yaml # Local Postgres container
Dockerfile # Multi-stage production image
- Express is bootstrapped through
InversifyExpressServer, allowing controllers to declare dependencies via decorators. - Services and repositories are composed through the DI container defined in
src/inversify.config.ts. - Prisma acts as the data access layer, exposed through
PrismaServiceso the raw client stays isolated from feature modules. - Zod schemas enforce request payload contracts before they reach business logic, keeping controllers thin.
- Cross-cutting concerns (logging, auth, error handling) live in
src/middlewareand are registered centrally.
- Incoming HTTP request hits Express.
- Global middlewares apply (JSON parsing,
requestLogger, CORS, static assets). - Inversify routes the request to the controller matching the path.
- Validation middleware (
validate) checks DTOs; failures short-circuit with400responses. - Controller resolves the required service from the container and invokes domain logic.
- Service interacts with repositories which call Prisma.
- Responses propagate back through the middleware stack; errors flow to
errorMiddleware.
Build the production image locally:
npm run docker:buildThe Dockerfile uses Node 18 Alpine images and copies only the compiled dist/ bundle plus production dependencies.
- Add a module: create a folder in
src/modules/<feature>containing controller, DTO/schema, service, repository. Register bindings insrc/inversify.config.tsand export@controller-decorated classes. - Add middleware: drop a new file under
src/middlewareand register it inserver.tsbefore controllers are mounted. - Add database tables: update
prisma/schema.prisma, runnpx prisma migrate dev, and expose the model through repositories. - Document a route: add OpenAPI annotations (JSDoc style) on controller methods so
swagger-jsdocpicks them up automatically.
A ready-to-use GitHub Actions workflow (.github/workflows/ci.yml) runs linting, build, and tests. Extend it with deploy steps as needed.
- Prisma cannot reach the database: confirm
DATABASE_URLpoints to an accessible Postgres instance and that migrations ran successfully. Cannot find module '@/...': ensurenpm run buildexecuted after adding new aliased paths and that Jest usests-jestwith the configured mapper.- Swagger missing in production: by design, Swagger mounts only when
NODE_ENV !== 'production'. Override the condition inserver.tsif you need docs in production. - Log forwarding failures: check network access to
LOG_ENDPOINTand inspect application logs for warn/error entries fromlogger.
- Why both
app.tsandserver.ts?app.tsdemonstrates a plain Express setup, whileserver.tsis the DI-enabled entry point used in production builds. - Can I swap Postgres for another database? Prisma supports multiple providers. Update
prisma/schema.prisma, adjustDATABASE_URL, and regenerate the client. - How do I seed data? Create
prisma/seed.tsand configurepackage.jsonscripts (prisma db seed) following Prisma docs. - Where do auth guards go? Implement middleware (e.g.,
AuthMiddleware) and apply it via@httpGetdecorator options or globally inserver.ts.
- Add more modules by following the
modules/userpattern (controller + service + repository + schema). - Wire additional middlewares (rate limiting, tracing, etc.) in
server.tsorapp.ts. - Plug the logger into a central platform by pointing
LOG_ENDPOINTat your collector.