Project Structure

What gets generated and where everything lives.

Directory layout

env.ts
errors.ts
logger.ts
error-handler.ts
validate.ts
rate-limit.ts
health.ts
example.ts
app.ts
server.ts
app.test.ts
example.test.ts
pre-commit
.env
.env.example
.gitignore
.prettierrc
.prettierignore
eslint.config.mjs
jest.config.js
package.json
tsconfig.json
vercel.json

When you select a database, additional files are generated (e.g. prisma/schema.prisma + prisma.config.ts for Prisma, or src/db/ for Drizzle/TypeORM). When Docker is enabled, you get a Dockerfile, .dockerignore, and docker-compose.yml. See the Database and Docker pages for details.

Application

src/app.ts

The Express application. Middleware is applied in this order:

  1. helmet for security headers
  2. cors for cross-origin requests
  3. express.json() for body parsing
  4. globalLimiter for rate limiting (100 requests per 15 minutes)
  5. pinoHttp for structured request logging

Routes are mounted after middleware: /health for the health check, /api/examples for the CRUD example. Unmatched routes throw a NotFoundError, and the error handler catches everything.

The app is exported separately from the server so tests can import it without starting a listener.

src/server.ts

Imports the app and starts listening on the configured port. Uses logger.info() for the startup message. For TypeORM projects, the database connection is initialized before the server starts.

Includes graceful shutdown handlers for SIGTERM and SIGINT. When the process receives a shutdown signal (e.g. from Docker, Kubernetes, or Ctrl+C), it stops accepting new connections, finishes in-flight requests, closes the database connection (if applicable), and exits cleanly.

This is what npm start and npm run dev run.

Config and utilities

src/config/env.ts

Loads environment variables with dotenv and exports a typed env object. Port is validated to be a number between 0 and 65535. Rate limit values are configurable. When a database is selected, DATABASE_URL is required. See Environment Variables for the full reference.

src/lib/logger.ts

Pino logger that uses pino-pretty formatting in development and raw JSON in production.

src/lib/errors.ts

Custom error classes that map to HTTP status codes:

Error classStatus code
AppErrorConfigurable (base class)
NotFoundError404
ValidationError400
UnauthorizedError401

Middleware

src/middleware/error-handler.ts

The global error handler. It checks the error type and responds accordingly: AppError subclasses return their own status code and message, ZodError returns 400 with validation details, and everything else returns 500. Error details are hidden in production. All errors are logged with Pino.

src/middleware/validate.ts

A factory that takes a Zod schema and returns middleware. It parses req.body, req.query, and req.params against the schema. If validation fails, the ZodError bubbles up to the error handler.

src/middleware/rate-limit.ts

Two exports: globalLimiter (applied to all routes in app.ts) and createLimiter() for per-route limits. Both are configurable through environment variables.

Routes

src/routes/health.ts

A simple GET /health endpoint that returns { status: "ok" }.

src/routes/example.ts

A full CRUD route at /api/examples with create, list, get by ID, and delete. The implementation depends on your ORM choice: Prisma uses the Prisma client, Drizzle uses drizzle-orm queries, TypeORM uses the repository pattern, and "none" uses an in-memory array.

Tests

The tests/ directory contains Jest + Supertest tests. app.test.ts covers the root endpoint, health check, and 404 handling. example.test.ts is generated when no database is selected and tests the in-memory CRUD route. See Testing for details on the config and writing your own tests.

Scripts

ScriptWhat it does
devStart with tsx watch (hot reload)
buildCompile TypeScript with tsc
startRun compiled output (node dist/server.js)
testRun Jest tests
lintRun ESLint

When a database ORM is selected, additional scripts are added for migrations, schema push, and database studio. See the Database page for the full list.