nodehexagen
v0.2.0
Published
Enterprise-Grade Hexagonal Architecture Scaffolding for Node.js
Maintainers
Readme
NodeHexaGen 🛡️
Enterprise-grade Hexagonal (Ports & Adapters) Architecture scaffolding for Node.js + TypeScript.
Why NodeHexaGen?
Node.js projects tend to turn into a "Big Ball of Mud" as they grow, with framework code bleeding into business logic. NodeHexaGen scaffolds a strict Ports & Adapters layout — domain, application, and infrastructure kept apart — with the production-grade concerns (auth, transactions, observability, testing) already wired in, not left as an exercise for later.
- Framework agnostic: Fastify, Express, or Koa.
- ORM agnostic: Prisma, Drizzle, Mongoose, or none (bring your own adapter).
- Validation agnostic: Zod, class-validator, or none.
- Real transactions: repositories accept a unit-of-work transaction handle, not just a promise that atomicity happens.
- Real auth: JWT login/register out of the box, with role-based route protection.
- Real tests: generated integration tests run against actual databases in Docker via testcontainers, not mocks.
Quick Start
npx nodehexagen init MyApi
# interactive prompts for framework / ORM / validation / auth,
# or skip them with flags:
npx nodehexagen init MyApi --framework Fastify --orm Prisma --validation Zod --auth JWT
cd MyApi
npm run devGenerate a full vertical slice (entity, port, repository, DTO, use cases, controller, routes, unit test, and — if an ORM is configured — an integration test):
npx nodehexagen gen:resource Product name:string price:numberProtect a resource's routes behind JWT + role checks:
npx nodehexagen gen:resource Product name:string price:number --protect --roles admin,editorMake a resource's create endpoint safe to retry (client sends an Idempotency-Key header):
npx nodehexagen gen:resource Order total:number --idempotent
--protectrequires the project was initialized with--auth JWT. Sinceinitalready scaffolds aUserentity/repository for auth, avoid naming a resourceUserin the same project.
What gets generated
Every project, regardless of choices
- Fail-fast environment validation (
src/infrastructure/config/env.ts, Zod-backed) — the process refuses to start with missing/invalid config instead of silently falling back to unsafe defaults. - Structured logging (pino) shared across all three frameworks, with redaction of common secret-shaped fields (
authorization,cookie,password,token) and a correlation ID (x-request-id) echoed on every response. - Security middleware: helmet, rate limiting, and CORS, all env-configurable.
- Pagination: every generated resource gets a
GET /xs?page=&pageSize=list endpoint. - Idempotency infrastructure: a pluggable
IIdempotencyStore(in-memory by default), opt-in per resource. - Health & readiness:
GET /health(liveness) andGET /ready(checks the real DB connection when an ORM is configured). - Graceful shutdown:
SIGTERM/SIGINTstop accepting new connections, disconnect the database, and exit cleanly (with a timeout fallback). - Docker:
Dockerfile+docker-compose.yml, matching the chosen ORM's database (or none). - CI: a GitHub Actions workflow (lint, build, unit tests,
npm audit, and integration tests when an ORM is present). - DI container (awilix), auto-wired by
gen:resourcefor every new resource.
When an ORM is chosen (Prisma / Drizzle / Mongoose)
- Repositories accept an optional transaction handle on every method, so a hand-written multi-repository use case can run atomically:
await unitOfWork.execute(async (tx) => { await accountRepository.save(debited, tx); await accountRepository.save(credited, tx); }); - Real integration tests:
gen:resourcealso writes a testcontainers-backed test that exercises the actual repository against a real, disposable database — including a rollback test that proves the unit of work is genuinely atomic. Run them with:
(Requires Docker.npm run test:integration # spins up Postgres/MongoDB in Docker, runs, tears downnpm test/npm run test:unitstays fast and Docker-free.)
When JWT auth is chosen
- A full login/register vertical slice:
Userentity + repository, password hashing (bcrypt),POST /auth/register,POST /auth/loginissuing a signed JWT with the user's roles. authenticate(validates the bearer token) andauthorize(...roles)(checks role membership) middleware, ready to attach to any route viagen:resource --protect --roles <a,b>.
Fastify only
- OpenAPI docs auto-registered at
GET /docs.
CLI Reference
nodehexagen init <name>
| Flag | Values |
|---|---|
| --framework | Fastify | Express | Koa |
| --orm | Prisma | Drizzle | Mongoose | None |
| --validation | Zod | ClassValidator | None |
| --auth | JWT | None |
Any flag left unset falls back to an interactive prompt.
nodehexagen gen:resource <Name> [field:type ...]
| Flag | Description |
|---|---|
| --protect | Require a valid JWT to hit this resource's routes (needs --auth JWT at init time). |
| --roles <a,b> | Comma-separated roles required when --protect is set. |
| --idempotent | Cache the create (POST) route's response by Idempotency-Key header. |
Project Layout
src/
domain/ # Entities, repository ports, domain exceptions - zero framework/ORM imports
application/ # Use cases, DTOs
infrastructure/
http/ # Controllers, routes, middlewares
database/ # Repository adapters (Prisma/Drizzle/Mongoose/InMemory)
di/ # awilix container
config/ # env.ts, idempotency config
logging/ # shared pino instance
health/ # readiness check
tests/
unit/ # fast, no external dependencies
integration/ # testcontainers-backed, requires DockerLicense
MIT. Developed by rbaezc, licensed to Vortex Solutions.
