A production-ready Fastify monorepo template β scaffold, build, and ship high-performance Node.js APIs in seconds.
Starting a new Node.js API project means wiring up the same boilerplate every time β auth, validation, logging, CORS, rate-limiting, OpenAPI docs, database migrationsβ¦ Fastify Forge ships all of it, pre-configured and production-grade, in a single npx command.
Built on Fastify 5 β one of the fastest HTTP frameworks for Node.js β and organised as an Nx monorepo, the template gives you a clean, scalable foundation you can grow into without ever fighting the scaffolding again.
| Category | What's included |
|---|---|
| π Performance | Fastify 5 + Pino structured logging + @fastify/under-pressure overload protection |
| π Authentication | Better Auth with email/password, admin plugin, session caching & cookie management |
| ποΈ Database | Drizzle ORM + PostgreSQL 17 with pre-built users, sessions & accounts schema |
| β Validation | End-to-end type-safe schemas via TypeBox + @fastify/type-provider-typebox |
| π OpenAPI | Swagger UI at /api/docs + Better Auth's OpenAPI integration, auto-generated from route schemas |
| π‘οΈ Security | @fastify/helmet, @fastify/cors, @fastify/rate-limit (with 404-route scan protection) |
| π Auto-loading | Plugin & route discovery via @fastify/autoload β drop a file, it's registered |
| πͺ Route Hooks | Cascading auth.hook.ts pattern keeps authentication logic out of route handlers |
| βοΈ Config | Type-safe environment variables via @fastify/env + TypeBox schema |
| π³ Docker | PostgreSQL 17 via docker-compose.yaml with health checks and persistent volumes |
| π Graceful shutdown | close-with-grace ensures in-flight requests complete before the server exits |
| ποΈ Monorepo | Nx workspace with affected-only CI, build caching, and Docker targets |
| π¨ DX | Husky, commitlint (Conventional Commits), Prettier, ESLint 9 flat config, Changesets, syncpack |
| π οΈ CLI | Interactive scaffolder β npx fastify-forge to clone, configure, and initialise a new project |
| Layer | Technology |
|---|---|
| Runtime | Node.js β₯ 24 |
| Framework | Fastify 5 |
| Language | TypeScript 5 |
| ORM | Drizzle ORM |
| Database | PostgreSQL 17 |
| Auth | Better Auth |
| Logging | Pino + pino-pretty |
| Validation | TypeBox |
| Monorepo | Nx |
| Package manager | pnpm 10 |
npx fastify-forge --name my-apiThe interactive CLI will:
- Clone the template into
./my-api - Strip git history and set up a fresh repo
- Copy
.env.exampleβ.env - Optionally initialise git and install dependencies
git clone https://github.com/flaviodelgrosso/fastify-forge.git my-api
cd my-api
pnpm installCopy the example file and fill in your values:
cp apps/api/.env.example apps/api/.env| Variable | Description | Default |
|---|---|---|
HOST |
Server bind address | localhost |
PORT |
Server port | 8080 |
LOG_LEVEL |
Pino log level (trace | debug | info | warn | error) |
info |
POSTGRES_HOST |
PostgreSQL host | β |
POSTGRES_PORT |
PostgreSQL port | 5432 |
POSTGRES_USER |
PostgreSQL user | β |
POSTGRES_PASSWORD |
PostgreSQL password | β |
POSTGRES_DB |
PostgreSQL database name | β |
BETTER_AUTH_SECRET |
Secret key for Better Auth (min 32 chars) | β |
docker compose up -dpnpm --filter @fastify-forge/db db:pushpnpm startThe API is now running at http://localhost:8080 π
fastify-forge/
βββ apps/
β βββ api/ # Main Fastify application
β βββ src/
β βββ main.ts # Server entry point & graceful shutdown
β βββ app.ts # Plugin & route registration, error handlers
β βββ auth.ts # Better Auth configuration
β βββ plugins/
β β βββ external/ # Third-party Fastify plugins
β β β βββ cors.ts
β β β βββ env.ts # Type-safe env schema
β β β βββ helmet.ts
β β β βββ multipart.ts
β β β βββ rate-limit.ts
β β β βββ sensible.ts
β β β βββ swagger.ts # OpenAPI + Swagger UI
β β β βββ under-pressure.ts
β β βββ internal/ # App-specific plugins
β β βββ authentication.ts # Better Auth plugin
β β βββ db.ts # Drizzle connection
β βββ routes/
β βββ health.ts # GET /health
β βββ root.route.ts # Root route
β βββ api/
β βββ auth.hook.ts # Session guard (cascades to child routes)
β βββ v1/
β βββ protected.ts # Example protected endpoint
β
βββ packages/
β βββ db/ # @fastify-forge/db β Drizzle client & schema
β β βββ src/
β β βββ index.ts # Drizzle client export
β β βββ schema.ts # users, sessions, accounts tables
β βββ logger/ # @fastify-forge/logger β Pino logger instance
β βββ src/
β βββ index.ts
β
βββ cli/ # npx fastify-forge scaffolder
βββ docker-compose.yaml # PostgreSQL 17 service
βββ nx.json # Nx workspace configuration
βββ pnpm-workspace.yaml # pnpm catalogs & workspace config
βββ eslint.config.js # ESLint 9 flat config
Plugins are auto-discovered from the plugins/ directory via @fastify/autoload. Adding a new plugin is as simple as dropping a file:
// apps/api/src/plugins/external/my-plugin.ts
import fp from 'fastify-plugin'
import myPlugin from 'fastify-my-plugin'
export default fp(async (fastify) => {
await fastify.register(myPlugin, { /* options */ })
})The auth.hook.ts file lives next to the api/ route folder and cascades down to every child route automatically. Any route placed inside api/ is protected by session validation β no extra wiring needed.
routes/
βββ api/
βββ auth.hook.ts β runs for all routes below this directory
βββ v1/
βββ protected.ts β session already validated β
The @fastify-forge/db package exports a ready-to-use Drizzle ORM client and a starter schema:
usersβ id, email, name, role (admin|user), image, ban management, timestampssessionsβ token, expiry, user agent, IP address, impersonation supportaccountsβ OAuth provider accounts with access/refresh token storage
# Generate a new migration
pnpm --filter @fastify-forge/db db:generate
# Push schema to database (dev)
pnpm --filter @fastify-forge/db db:push
# Open Drizzle Studio
pnpm --filter @fastify-forge/db db:studioAuthentication is powered by Better Auth with the following setup out of the box:
- Email & password sign-up / sign-in
- Session caching (5-minute cookie cache) to reduce database hits
- Admin plugin for user management endpoints
- OpenAPI integration β auth routes appear in the Swagger UI at
/api/docs - 1-week session expiry with daily refresh
// Sign in β POST /api/auth/sign-in/email
{
"email": "user@example.com",
"password": "supersecret"
}Swagger UI is served at /api/docs and is automatically populated from TypeBox schemas on your route definitions. Add a schema to any route and it appears instantly:
app.route({
url: '/users/:id',
method: 'GET',
schema: {
tags: ['Users'],
params: Type.Object({ id: Type.String({ format: 'uuid' }) }),
response: { 200: UserSchema },
},
handler: async (req) => getUserById(req.params.id),
})Run these from the workspace root:
| Command | Description |
|---|---|
pnpm start |
Start all apps in parallel |
pnpm build |
Build all packages and apps |
pnpm lint |
Lint all projects |
pnpm typecheck |
Type-check all projects |
pnpm test |
Run all tests |
pnpm format |
Format all files with Prettier |
pnpm clean |
Remove all build artifacts and node_modules |
Nx only re-runs tasks for projects affected by your changes, keeping feedback loops fast.
npx fastify-forge [options]| Flag | Description |
|---|---|
--name <name> |
Project name / directory (prompted interactively if omitted) |
--no-git |
Skip git repository initialisation |
Contributions are welcome! Please follow Conventional Commits for commit messages β the commit-lint hook will remind you.
# Fork & clone your fork
git clone https://github.com/<your-handle>/fastify-forge.git
cd fastify-forge
pnpm install
# Create a feature branch
git checkout -b feat/my-awesome-feature
# Make your changes, then open a PR πReleases are managed via Changesets. If your PR includes a user-facing change, add a changeset:
pnpm changesetMIT Β© Flavio Del Grosso