Open Source CLI
West JS Case Study
An open-source CLI that scaffolds fully-configured Express 5 + TypeScript backends in seconds — Prisma, Drizzle, Docker, Zod, Pino, and Git hooks all wired from day one, so developers can skip the setup and start shipping.
Product
west.js
Role
Creator & Maintainer
Timeline
2024 – Present
Completion
100%
By the numbers
v3.0.0
Current stable version
4
ORM integrations
1
Command to scaffold
100%
Open source
The Problem
Every new Node.js project starts the same painful way
Starting a Node.js backend from scratch means repeating the same scaffolding ritual every single time — setting up TypeScript configs, picking an ORM, wiring Docker, configuring ESLint and Prettier, adding pre-commit hooks, and writing boilerplate middleware before writing a single line of real logic. It's slow, error-prone, and inconsistent across projects.
Boilerplate that takes hours, not minutes
A production-grade Express setup involves a dozen config files, path aliases, strict tsconfig, ESLint flat config, Prettier, and Husky hooks — none of which are unique to any one project, yet every project starts from scratch.
ORM wiring is always different
Prisma 7's new driver adapter pattern, Drizzle's per-dialect connection setup, TypeORM's decorator metadata — each ORM has its own footguns. Getting them right takes documentation-diving every time.
Docker config gets copy-pasted and grows stale
Multi-stage Dockerfiles and database-service docker-compose configs get copy-pasted between projects and drift over time. There's no canonical, up-to-date baseline.
Security defaults are an afterthought
Helmet, CORS, rate limiting, and Zod-validated environment variables rarely make it into an initial scaffold — they get added reactively, if at all.
The Solution
One command. A fully production-wired backend.
west-js-app is a CLI that runs an interactive wizard (or accepts flags for CI) and writes every file, config, and middleware you'd otherwise spend hours assembling — typed, linted, tested, and containerised before you've typed your first route.
Interactive Wizard or Headless CI Mode
Run npx west-js-app for a guided prompt flow — choose ORM, database provider, Docker support, and package manager. Or pass --yes with flags for a fully non-interactive, scriptable generation suitable for CI pipelines.
Four ORM Paths, All Fully Scaffolded
Prisma 7 (with driver adapters), Drizzle ORM, TypeORM (with decorator metadata wired), or no database — each path generates correct config files, connection modules, example schemas, and db:* scripts. Not just dependencies: actual working code.
Docker Out of the Box
Multi-stage Dockerfile with a lean production image, plus docker-compose.yml with the correct database service (postgres:16-alpine, mysql:8-debian) auto-selected for the chosen provider. Run docker compose up --build and it works.
Security-first Middleware Stack
Helmet, CORS, express-rate-limit, and Zod-parsed environment variable validation are pre-wired at startup. If a required env variable is missing, the process exits immediately with a readable error — not silently in production.
Structured Logging with Pino
pino + pino-http produce JSON request logs in production and pretty-printed coloured output in development. Plugs directly into Datadog, Loki, or CloudWatch without any additional configuration.
Testing from the First File
Jest + ts-jest + Supertest are scaffolded with integration tests for the health route and example CRUD route. Run npm test and tests pass before you've written anything.
Technology Stack
Built with the right tools
Architecture
Monorepo (pnpm workspaces)
west.js/ ├── packages/ │ └── cli/ │ ├── src/ │ │ ├── index.ts # CLI entrypoint & flag parsing │ │ ├── prompts/ # Interactive wizard steps │ │ ├── generators/ # ORM-specific file generators │ │ └── templates/ # File templates per combination │ └── tests/ # CLI test suite └── web/ # Documentation site (westjs.vishalvoid.com)
Development Journey
How it was built
Phase 01
The itch worth scratching
The motivation was personal. Setting up a new Express backend for a client project meant spending the first hour on TypeScript config, ESLint, Prettier, Husky, and Zod middleware before writing a single endpoint. I'd done it too many times. The idea was simple: build the tool once, so the setup is never the bottleneck again.
Phase 02
Designing the generator architecture
The CLI needed to support a matrix of ORM × database × tooling combinations — each producing working code, not just updated package.json files. The approach was template-based generation with conditional logic: every combination is explicitly handled, meaning Prisma 7's driver adapter pattern is correctly scaffolded regardless of whether the user picks PostgreSQL, MySQL, or SQLite.
Phase 03
Tackling Prisma 7's breaking changes
Prisma 7 introduced a required driver adapter pattern that broke the classic environment-variable-only approach. The generator had to produce the correct PrismaClient constructor with the right adapter package (PrismaPg, PrismaMysql, PrismaBetterSqlite3) per provider, plus the new prisma.config.ts with defineConfig(). Getting this right required deep reading of upstream docs and iteration.
Phase 04
The --yes flag and CI workflows
After the interactive mode worked reliably, I added a fully non-interactive --yes flag with per-option flags (--name, --database, --db-provider, --docker, --package-manager). This made the CLI useful in CI/CD pipelines, project templates, and scripted environments — not just interactive developer terminals. It also became the primary way to run the generator's own test suite.
Phase 05
Versioning and the documentation site
As the tool evolved through v1 → v2 → v3.0.0, a proper documentation site became necessary. The westjs.vishalvoid.com site was built to provide a searchable reference for all CLI flags, generated project structure, ORM-specific setup flows, and Docker config — making the tool self-contained without relying on a README.
Engineering Challenges
Hard problems, real solutions
Supporting every ORM × database combination correctly
Problem
Prisma, Drizzle, TypeORM, and Mongoose each have fundamentally different initialization patterns, config file formats, and required dependencies. A naive template approach would produce broken projects for less-common combinations.
Solution
Built a conditional generation engine where each ORM path is explicitly handled. Every combination is tested by running the CLI with the --yes flag and verifying the generated project compiles, lints, and passes tests.
Prisma 7's driver adapter requirement
Problem
Prisma 7 deprecated the classic PrismaClient without a driver adapter. The generator had to produce correct, version-specific scaffolding — including the new prisma.config.ts and the right adapter package per database provider — without falling back to outdated patterns.
Solution
Implemented provider-specific adapter mappings (pg → PrismaPg, mysql2 → PrismaMysql, better-sqlite3 → PrismaBetterSqlite3) and generated the complete Prisma 7 config surface in every relevant file.
TypeORM's reflect-metadata footgun
Problem
TypeORM requires import 'reflect-metadata' to appear before any TypeORM imports — a constraint that is easy to miss and produces cryptic runtime errors. The documentation warns about it, but developers still hit it.
Solution
The generator injects the import at the top of src/app.ts automatically and enables experimentalDecorators and emitDecoratorMetadata in tsconfig.json, eliminating the most common TypeORM setup failure entirely.
Quality & Testing
Tests included. Zero config required.
Every generated project ships with a working test suite. Developers don't set up Jest — they inherit a configured environment with integration tests that already pass. The test infrastructure itself is verified by running the CLI and executing npm test on the output.
| Method | Endpoint | Tests | Passing |
|---|---|---|---|
| GET | /health | 2 | 2 |
| GET | /api/examples | 2 | 2 |
| POST | /api/examples | 2 | 2 |
| GET | /api/examples/:id | 2 | 2 |
| DELETE | /api/examples/:id | 2 | 2 |
Project Roadmap
What's done, what's next
v3.0.0 · Stable
Upcoming · In Progress
Future · Planned