WestJS Documentation
west-js-app is a CLI that scaffolds production-ready Express 5 + TypeScript APIs in seconds. Choose your ORM, database provider, package manager, and Docker support — the generator writes every config file, middleware, route, test, and Git hook for you.
Overview
#west-js-app removes the tedium of bootstrapping a Node.js backend. A single command launches an interactive wizard (or accepts flags for CI/scripts) and produces a fully-wired Express 5 project — typed, linted, tested, and optionally containerised.
- Express 5 with production middleware (helmet, cors, rate-limit, pino logging)
- TypeScript strict mode — ES2023 target, bundler module resolution
- Four database paths: Prisma 7, Drizzle ORM, TypeORM, or no database
- PostgreSQL, MySQL, and SQLite support for all three ORMs
- Zod-powered environment variable validation at startup
- Jest + ts-jest test suite with health and route integration tests
- ESLint (flat config) + Prettier + Husky pre-commit hooks wired up
- Docker Compose with database service auto-configured per provider
- Vercel deployment config generated out of the box
- Interactive prompt mode or fully non-interactive --yes flag for scripting
Requirements
#west-js-app has minimal prerequisites. Make sure the following are available before running the CLI.
| Prerequisite | Version | Notes |
|---|---|---|
| Node.js | >= 18.0.0 | Required — CLI uses native fs, child_process |
| npm / pnpm / yarn | any current | Only the chosen PM needs to be installed |
| Git | any | Optional — needed only if you choose to initialise a repo |
Installation & Quick Start
#Run west-js-app directly with npx — no global install required. The interactive wizard guides you through every option.
Press Ctrl+C at any prompt to cancel cleanly. The CLI removes the partially-created directory on failure.
CLI Flags Reference
#Every interactive prompt has a corresponding flag. Pass --yes (-y) to skip all prompts and accept defaults — useful for CI pipelines, scripts, and reproducible scaffolding.
| Flag | Type | Default | Description |
|---|---|---|---|
| --name <name> | string | my-app | Project directory name — must be valid kebab-case |
| --database <orm> | enum | none | ORM to scaffold: prisma | drizzle | typeorm | none |
| --db-provider <db> | enum | postgresql | Database provider: postgresql | mysql | sqlite |
| --docker | boolean | false (true when DB set) | Generate Dockerfile + docker-compose.yml |
| --no-docker | boolean | — | Explicitly disable Docker even when a database is selected |
| --package-manager <pm> | enum | npm | Package manager: npm | pnpm | yarn |
| -y, --yes | boolean | false | Accept all defaults — enables fully non-interactive mode |
| -v, --version | boolean | — | Print the CLI version and exit |
| -h, --help | boolean | — | Print usage information and exit |
Non-interactive examples
# Minimal — all defaults, name only
npx west-js-app --yes --name my-api# Prisma + PostgreSQL + Docker using pnpm
npx west-js-app --yes --name my-api --database prisma --db-provider postgresql --docker --package-manager pnpm# Drizzle + SQLite, no Docker
npx west-js-app --yes --name my-api --database drizzle --db-provider sqlite --no-docker# TypeORM + MySQL
npx west-js-app --yes --name my-api --database typeorm --db-provider mysql --dockerGenerated Project Structure
#Every project gets the same core layout. Extra directories (prisma/, drizzle/, src/db/, src/generated/) are added only when a database is chosen. Docker files appear only when --docker is enabled.
my-api/
├── src/
│ ├── app.ts # Express app factory — middleware stack assembled here
│ ├── server.ts # HTTP server entrypoint, graceful shutdown
│ ├── routes/
│ │ ├── health.ts # GET /health — liveness probe
│ │ └── example.ts # CRUD example route (in-memory or DB-backed)
│ ├── middleware/
│ │ ├── error-handler.ts # Centralised error → JSON response mapper
│ │ ├── validate.ts # Zod schema validation middleware factory
│ │ └── rate-limit.ts # express-rate-limit configured from env
│ ├── config/
│ │ └── env.ts # Zod-parsed, type-safe env variables
│ ├── lib/
│ │ ├── errors.ts # AppError + HttpError base classes
│ │ └── logger.ts # pino logger instance (pretty in dev)
│ └── db/ # [DB only] ORM connection & schema
│ ├── index.ts
│ └── schema.ts # [Drizzle / TypeORM]
├── tests/
│ ├── app.test.ts # Supertest health route integration test
│ └── example.test.ts # [no-DB only] Example route integration test
├── prisma/ # [Prisma only]
│ └── schema.prisma
├── prisma.config.ts # [Prisma 7 only]
├── drizzle.config.ts # [Drizzle only]
├── .husky/
│ └── pre-commit # Runs lint-staged on every commit
├── Dockerfile # [Docker only] Multi-stage Node build
├── docker-compose.yml # [Docker only] App + DB services
├── .dockerignore # [Docker only]
├── vercel.json # Vercel serverless deployment config
├── jest.config.js # ts-jest preset
├── eslint.config.mjs # ESLint flat config (typescript-eslint)
├── .prettierrc # Prettier rules
├── tsconfig.json # TypeScript — strict, ES2023, bundler resolution
├── package.json # Scripts, dependencies, lint-staged config
├── .env # Local environment variables (git-ignored)
└── .env.example # Committed env templateThe .env file is always git-ignored. The .env.example is committed so team members know which variables to set.
Core Generated Files
#Each file has a specific responsibility. Understanding them helps you extend the project confidently.
src/app.ts — Express Application Factory
Creates and exports the Express app. Applies JSON body parsing, URL-encoded parsing, CORS (via cors), security headers (via helmet), HTTP request logging (via pino-http), rate limiting, and mounts all routers. The app is exported separately from the server so it can be imported in tests without binding a port.
- helmet() sets secure HTTP headers (XSS, clickjacking, MIME sniffing protection)
- cors() is configured with permissive defaults — tighten in production
- pino-http logs every request/response with duration and status code
- express.json() + express.urlencoded() parse incoming request bodies
- rateLimiter middleware is applied globally before routes
- Routes are mounted under their respective prefixes (/health, /api/examples)
src/server.ts — HTTP Server Entrypoint
Imports the app, binds it to the port from env, and registers SIGTERM/SIGINT handlers for graceful shutdown. When TypeORM is selected, the server waits for initializeDatabase() to resolve before binding the port — preventing requests before the DB connection is ready.
- Reads PORT from the validated env config (defaults to 3000)
- Logs the running port and NODE_ENV with pino
- SIGTERM / SIGINT close the HTTP server then destroy the DB connection (TypeORM)
- TypeORM variant calls initializeDatabase() before app.listen()
src/config/env.ts — Environment Variable Validation
Uses Zod to parse process.env at startup. If a required variable is missing or the wrong type, the process exits immediately with a readable error — preventing silent misconfiguration in production.
// Shape of the parsed env object
{
NODE_ENV: 'development' | 'production' | 'test',
PORT: number, // coerced from string
RATE_LIMIT_WINDOW_MS: number,
RATE_LIMIT_MAX: number,
DATABASE_URL: string // only present when a DB is selected
}src/lib/errors.ts — Error Classes
Exports AppError (base class with statusCode and isOperational flag) and HTTP-specific subclasses like NotFoundError (404) and ValidationError (422). Throwing these anywhere in the app causes the error handler middleware to produce a consistent JSON response.
- AppError extends Error — carry statusCode and message
- Operational errors (AppError subclasses) return JSON responses
- Non-operational errors (programming bugs) log full stack and return 500
- isOperational flag lets you distinguish expected vs unexpected failures
src/lib/logger.ts — Pino Logger
Creates a named pino logger instance. In development (NODE_ENV !== production) pino-pretty is used for human-readable, coloured log output. In production, plain JSON logs are emitted for log aggregation tools.
- Log level auto-set to 'debug' in development, 'info' in production
- pino-pretty provides coloured, formatted output during local development
- Structured JSON logs in production work with Datadog, Loki, CloudWatch etc.
src/middleware/error-handler.ts — Error Handler
Express error-handling middleware (4-argument signature). Catches all errors thrown or passed to next(). Operational AppError instances are returned as JSON with their statusCode. Everything else returns 500 and logs the full error details server-side.
// Response shape for all errors
{ "error": { "message": "Not found", "status": 404 } }src/middleware/validate.ts — Zod Validation Middleware
A middleware factory: validate(schema) returns an Express RequestHandler that parses req.body against the given Zod schema. On failure it calls next() with a ValidationError (422). On success it replaces req.body with the parsed (type-safe) value.
// Usage in a route
router.post('/', validate(CreateItemSchema), createItem);src/middleware/rate-limit.ts — Rate Limiter
Configures express-rate-limit using RATE_LIMIT_WINDOW_MS and RATE_LIMIT_MAX from the validated env. Exceeding the limit returns a 429 Too Many Requests response in standard JSON format.
- Window: 900000 ms (15 minutes) by default — change in .env
- Max requests per window: 100 by default
- Response after limit: { error: 'Too many requests' }
Generated npm Scripts
#All scripts are written into the generated project's package.json. Database scripts (db:*) are only added when an ORM is selected.
| Script | Command | Purpose |
|---|---|---|
| dev | tsx watch src/server.ts | Hot-reload dev server — no build step needed |
| build | tsc | Compile TypeScript to dist/ using tsconfig.json |
| start | node dist/server.js | Run the compiled production build |
| test | NODE_ENV=test jest | Run the test suite with Jest |
| lint | eslint src/ | Lint all files in src/ using the flat ESLint config |
| lint:fix | eslint src/ --fix | Auto-fix all fixable lint errors |
| format | prettier --write "src/**/*.ts" | Format all TypeScript source files |
| format:check | prettier --check "src/**/*.ts" | Check formatting without writing |
| prepare | husky || true | Install Husky hooks after npm install (silently skipped if Husky unavailable) |
| db:generate | prisma generate / drizzle-kit generate | Generate the ORM client / migration files |
| db:migrate | prisma migrate dev / drizzle-kit migrate | Run pending database migrations |
| db:push | prisma db push / drizzle-kit push | Push schema changes without a migration file |
| db:studio | prisma studio / drizzle-kit studio | Open the visual database browser |
Default API Endpoints
#The scaffold ships with a health route and a fully-wired CRUD example route. Remove or rename the example route before shipping to production.
| Method | Path | Handler | Description |
|---|---|---|---|
| GET | / | inline | Welcome message with project name and current version |
| GET | /health | healthRouteHandler | Returns { status: 'ok', timestamp } — suitable for load balancer probes |
| GET | /api/examples | getExamples | Returns array of all example items |
| GET | /api/examples/:id | getExampleById | Returns a single item by ID or 404 |
| POST | /api/examples | createExample | Validates body with Zod schema, creates item, returns 201 |
| DELETE | /api/examples/:id | deleteExample | Deletes item by ID or returns 404 |
When a database ORM is selected, the example route uses the real DB. Without a database it uses an in-memory array — useful for getting started without any infrastructure.
Database Support
#west-js-app supports three ORMs across three database providers. When you pick an ORM, the CLI writes the config file, connection module, an example schema or entity, and wires up the db:* scripts.
| ORM | PostgreSQL | MySQL | SQLite | Config file | Schema/Entity file |
|---|---|---|---|---|---|
| Prisma 7 | ✓ | ✓ | ✓ | prisma/schema.prisma + prisma.config.ts | prisma/schema.prisma |
| Drizzle ORM | ✓ | ✓ | ✓ | drizzle.config.ts | src/db/schema.ts |
| TypeORM | ✓ | ✓ | ✓ | src/db/data-source.ts | src/db/entities/Example.ts |
| None | — | — | — | — | — |
Prisma 7 Setup
#west-js-app targets Prisma 7, which introduced a required driver adapter pattern. Unlike Prisma 4/5, you must explicitly pass a driver adapter to the PrismaClient constructor — the classic environment-variable-only approach no longer works.
The generated client lives in src/generated/prisma — do not edit those files. Regenerate them whenever you change schema.prisma.
What the CLI generates
- prisma/schema.prisma — generator block with output pointing to src/generated/prisma
- prisma.config.ts — Prisma 7 defineConfig() with dotenv + datasource URL
- src/lib/prisma.ts — PrismaClient instantiated with the correct driver adapter
- src/db/index.ts — re-exports db for use across the project
- src/db/schema.ts — example User model to get started
Driver adapters per provider
| Provider | Adapter package | Driver package | Adapter class |
|---|---|---|---|
| PostgreSQL | @prisma/adapter-pg | pg + @types/pg | PrismaPg |
| MySQL | @prisma/adapter-mysql | mysql2 | PrismaMysql |
| SQLite | @prisma/adapter-better-sqlite3 | better-sqlite3 + @types/better-sqlite3 | PrismaBetterSqlite3 |
First-run workflow
Drizzle ORM Setup
#Drizzle is a lightweight, SQL-first TypeScript ORM. The CLI generates a drizzle.config.ts, per-provider connection setup in src/db/index.ts, and an example schema in src/db/schema.ts.
What the CLI generates
- drizzle.config.ts — dialect, schema path, output path, and DB credentials
- src/db/index.ts — provider-specific driver initialisation (postgres, mysql2, better-sqlite3)
- src/db/schema.ts — example 'examples' table definition using Drizzle column helpers
Drivers used per provider
| Provider | Driver package | Drizzle import |
|---|---|---|
| PostgreSQL | postgres (postgres.js) | drizzle-orm/postgres-js |
| MySQL | mysql2 | drizzle-orm/mysql2 |
| SQLite | better-sqlite3 + @types/better-sqlite3 | drizzle-orm/better-sqlite3 |
First-run workflow
TypeORM Setup
#TypeORM uses class decorators to define entities. The CLI enables experimentalDecorators and emitDecoratorMetadata in tsconfig.json and generates a DataSource configuration, an Example entity, and database initialisation logic.
What the CLI generates
- src/db/data-source.ts — TypeORM DataSource with provider-appropriate driver and entity path
- src/db/entities/Example.ts — @Entity decorated class with @PrimaryGeneratedColumn, @Column
- src/db/index.ts — initializeDatabase() function that initialises the DataSource
- tsconfig.json — experimentalDecorators: true, emitDecoratorMetadata: true added automatically
reflect-metadata
TypeORM requires reflect-metadata. The CLI adds it to dependencies and imports it at the top of src/app.ts — import 'reflect-metadata' must appear before any TypeORM imports.
Forgetting to import reflect-metadata is the most common TypeORM error. The generator handles this for you.
First-run workflow
Docker Support
#When Docker is enabled, the CLI generates three files: a multi-stage Dockerfile, a docker-compose.yml with both the app service and a database service, and a .dockerignore. The Docker database service is pre-configured for the chosen provider.
Run docker compose up --build to start both services. The app service waits for the database to be healthy before starting.
Dockerfile
Multi-stage build: a 'builder' stage installs dependencies and compiles TypeScript, then a lean 'production' stage copies only the compiled output and production node_modules. This keeps the final image small.
# Stage 1 — build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2 — production image
FROM node:20-alpine AS production
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
EXPOSE 3000
CMD ["node", "dist/server.js"]docker-compose.yml
Defines two services: your Express app (built from the Dockerfile) and the database. The DB service image and port are auto-selected based on your provider choice.
| Provider | Docker image | Port |
|---|---|---|
| PostgreSQL | postgres:16-alpine | 5432 |
| MySQL | mysql:8-debian | 3306 |
| SQLite | — | file-based, no DB service needed |
Environment Variables
#The CLI writes .env (git-ignored) and .env.example (committed) with sensible defaults. All variables are validated by Zod at startup in src/config/env.ts.
# ── Runtime ──────────────────────────────────────────
NODE_ENV=development
PORT=3000
# ── Rate limiting ────────────────────────────────────
RATE_LIMIT_WINDOW_MS=900000 # 15 minutes
RATE_LIMIT_MAX=100 # max requests per window per IP
# ── Database (only when an ORM is selected) ──────────
# PostgreSQL (Prisma)
DATABASE_URL="postgresql://user:password@localhost:5432/mydb?schema=public"
# PostgreSQL (Drizzle / TypeORM)
DATABASE_URL="postgresql://user:password@localhost:5432/mydb"
# MySQL
DATABASE_URL="mysql://user:password@localhost:3306/mydb"
# SQLite
DATABASE_URL="file:./dev.db" # Prisma
DATABASE_URL="./dev.db" # Drizzle / TypeORMNever commit .env. The .env.example file is checked in so team members know which variables to configure.
TypeScript Configuration
#The generated tsconfig.json is optimised for modern Node.js development. It uses strict mode to catch type errors early and bundler moduleResolution for compatibility with ESM packages.
| Option | Value | Why |
|---|---|---|
| target | ES2023 | Use modern JS features (structuredClone, Array.at, etc.) |
| module | ESNext | Emit ESM output |
| moduleResolution | bundler | Resolves package.json exports — compatible with tsx and bundlers |
| strict | true | Enables all strict type checks (null checks, implicit any, etc.) |
| outDir | ./dist | Compiled output goes to dist/ |
| rootDir | ./src | Source root — only src/ is compiled |
| esModuleInterop | true | Allows default imports from CommonJS modules |
| skipLibCheck | true | Skip type-checking of .d.ts files in node_modules |
| sourceMap | true | Source maps for debugging compiled JS |
| resolveJsonModule | true | Allow import of .json files |
| experimentalDecorators | true (TypeORM only) | Required for TypeORM @Entity / @Column decorators |
| emitDecoratorMetadata | true (TypeORM only) | Required for TypeORM reflection metadata |
ESLint + Prettier
#The generated project ships with ESLint flat config (eslint.config.mjs) and a .prettierrc. Husky + lint-staged run both tools on every git commit so your codebase stays clean automatically.
eslint.config.mjs
Uses typescript-eslint's tseslint.config() helper to compose the eslint recommended + typescript-eslint recommended rule sets. Unused variables prefixed with _ are ignored to allow intentional placeholders.
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommended,
{
rules: {
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
},
},
{ ignores: ['dist/', 'node_modules/', 'coverage/', 'jest.config.js'] }
);Husky pre-commit hook
lint-staged is configured in package.json and runs on every commit. It lints and formats only the staged .ts files — keeping commits fast by avoiding a full project scan.
// package.json — lint-staged config
"lint-staged": {
"*.ts": ["eslint --fix", "prettier --write"]
}Testing
#Projects are scaffolded with Jest + ts-jest + Supertest. Two integration tests are generated: an app health-check test that works universally, and an example route test that is only generated when no database is selected (the in-memory variant makes it predictable).
Run npm test — Jest sets NODE_ENV=test automatically via the script. The test server binds to a random port to avoid port conflicts when running in parallel.
jest.config.js
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/tests/**/*.test.ts'],
moduleNameMapper: {
// map .js extensions back to .ts source files for ts-jest
'^(\\.{1,2}/.+)\\.js$': '$1',
},
};Generated test files
| File | Tests | Always generated |
|---|---|---|
| tests/app.test.ts | GET /health → 200, body.status === 'ok' | Yes |
| tests/example.test.ts | GET /api/examples, POST, GET /:id, DELETE /:id | Only when database = none |
Vercel Deployment
#A vercel.json is always generated. It configures a serverless Node build that routes every request to dist/server.js, sets a 60-second function timeout, 1024 MB memory, and permissive CORS headers.
{
"version": 2,
"builds": [
{
"src": "dist/server.js",
"use": "@vercel/node",
"config": { "maxDuration": 60, "memory": 1024 }
}
],
"routes": [
{
"src": "/(.*)",
"dest": "dist/server.js",
"methods": ["GET","POST","PUT","DELETE","PATCH","OPTIONS"],
"headers": {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "*"
}
}
]
}Remember to add your environment variables in the Vercel project settings. DATABASE_URL in particular must be set for any DB-backed project.
Contributing
#west-js-app is an open-source monorepo. The CLI lives in packages/cli, the documentation website in web/. All contributions are welcome.
Useful development commands
# Clone
git clone https://github.com/vishalvoid/west.js.git && cd west.js# Install all workspace dependencies
pnpm install# Run CLI tests
pnpm -F west-js-app test# Build CLI
pnpm -F west-js-app build# Test the built output locally
node packages/cli/dist/index.js --helpnode packages/cli/dist/index.js --yes --name my-test-app --database drizzle --db-provider sqlite# Run web dev server
pnpm dev