fastify-boilerplate: A TypeScript-first Fastify 5 scaffold with DDD, CQRS, and zero build step
Fastify 5 application boilerplate based on clean architecture, domain-driven design, CQRS, functional programming, vertical slice architecture for building production-grade applications.
At a glance
- What is it?
- marcoturi/fastify-boilerplate is a production-oriented Fastify 5 starter that combines clean architecture, CQRS, and vertical slices with native TypeScript execution on Node 24. The trade-off is a steep learning curve and a rigid structure that suits teams, not quick prototypes.
- Who is it for?
- Adopt this boilerplate if you are building a long-lived REST or GraphQL service on Fastify and your team already works with DDD, CQRS, or functional programming. Skip it if you need a minimal Express-style start or if your team is not comfortable with layered boundaries and dependency-cruiser rules.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 14, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
What this boilerplate actually enforces
This is not a bare Fastify starter. The repository enforces a specific way to build applications: clean architecture, domain-driven design, CQRS, functional programming, and vertical slices. The README states the architecture is framework-agnostic at its core, meaning the patterns transfer to other languages. For a team that already practices these patterns, the scaffold removes the usual setup friction. For a team that does not, the boilerplate imposes a learning curve before the first feature ships. The dependency-cruiser validation at CI time is the key mechanism: it fails builds when a layer imports from the wrong layer. That is a concrete guardrail, not a suggestion.
How the pieces fit together
The stack is explicit: Fastify 5 for HTTP, Awilix for dependency injection, Pino for logging, TypeBox for REST schemas, Mercurius for GraphQL, Postgres.js for the database, and DBMate for migrations. The runtime is Node.js 24 with native TypeScript type stripping, so there is no build step and no transpiler. That is a notable design choice. It removes a whole class of build configuration, but it also ties you to Node 24 or newer. The API surface is split: REST routes live under /api, GraphQL is a separate endpoint, and both share the same domain layer. The README shows a /health endpoint backed by @fastify/under-pressure, which gives you load-based health checks out of the box.
Getting it running: commands and config
The quickstart uses degit to scaffold a new project: npx degit marcoturi/fastify-boilerplate my-app. Then you run pnpm install, pnpm create:env to copy .env.example to .env, start PostgreSQL via docker compose up postgres -d, apply migrations with pnpm db:migrate, and launch the dev server with pnpm start. The server listens on port 3000. Production startup is pnpm start:prod. The Docker path is equally direct: docker compose up builds the app image and starts all services. The Dockerfile is multi-stage, runs as a non-root user, includes dumb-init for signal handling, and has a HEALTHCHECK against /health every 30 seconds. For local development, the .nvmrc and corepack instructions are there to pin Node and pnpm versions.
The testing and quality pipeline
Testing is split into three tiers. Unit and integration tests use node:test, which is the built-in Node test runner, and coverage comes from c8. End-to-end tests use Cucumber with Gherkin, and load tests use k6. That is a broader testing matrix than most boilerplates ship. The scripts are pnpm test:unit, pnpm test:coverage, and pnpm test:e2e. The E2E suite requires a running Postgres, which is a real constraint for CI. The code quality side uses Biome for linting and formatting, plus dependency-cruiser for architecture validation. The README explicitly argues why Biome replaces ESLint and Prettier, which is a single-tool stance that reduces configuration but may clash with teams that already have an ESLint-based setup.
Telemetry and client types: two features with caveats
OpenTelemetry is included with auto-instrumentation, but the README states it is disabled by default. That is an important detail: you get the scaffolding, but you must turn it on and configure an exporter. The vendor-agnostic claim is accurate, but it means you need to bring your own collector or backend. The client types package is more unusual: REST and GraphQL types are auto-generated and published to npm on every release. The script pnpm generate:types requires a running server and database. That is a powerful feature for consumers of your API, but it also couples your release process to a live environment. If your CI cannot run Postgres, this feature will not work.
Where this boilerplate is the wrong tool
The obvious mismatch is a small service or a prototype where the overhead of DDD and CQRS is not justified. The boilerplate expects you to structure code into modules with clear boundaries, and dependency-cruiser will block imports that violate those boundaries. That is a feature, but it becomes friction when you just want to expose a couple of endpoints. The Node 24 requirement is another constraint: if your production environment is still on Node 20 or 22, this boilerplate will not run as intended. The README does not mention a fallback build step, so native type stripping is the only path. Also, the release cadence is aggressive: the repository shows three releases in three consecutive days (v2.9.21, v2.9.22, v2.9.23). That suggests active maintenance, but it also means you should pin a specific version rather than track the latest tag blindly.
Alternatives and the real difference
A direct alternative is the official Fastify plugin template or a plain Fastify + TypeScript setup with a traditional tsc build. The difference is architectural enforcement: this boilerplate brings DDD and CQRS as a mandatory structure, while a minimal Fastify starter leaves layering to the developer. Another alternative is NestJS, which also provides a modular architecture with DI and decorators, but it is a full framework with its own HTTP layer, not a Fastify boilerplate. The README does not mention NestJS, but the comparison is useful: NestJS gives you structure through its own module system, while this boilerplate gives you structure through folder conventions and dependency-cruiser rules. The trade-off is that NestJS is opinionated about the framework itself, whereas this project keeps Fastify as the transport and pushes the architecture into your code organization.
Maintenance and license considerations
The project is MIT licensed, which means you can use it commercially without a copyleft obligation. The maintenance activity is visible: recent releases are dated within days of each other, and the default branch is not archived. That is a positive signal for a boilerplate, because stale templates quickly rot against Fastify updates. However, the README mentions Semantic Release and Commitlint, which means the project follows conventional commits and automated versioning. If you fork or vendor this boilerplate, you inherit those tooling requirements. The dependency-cruiser config and the AGENTS.md file for AI assistants are part of the repository, so they become part of your project. Upgrading later means tracking Fastify 5 releases and any breaking changes in the boilerplate's own abstractions, which the rapid release cadence suggests could happen often.
Editorial conclusion
Adopt this boilerplate if you are building a long-lived REST or GraphQL service on Fastify and your team already works with DDD, CQRS, or functional programming. Skip it if you need a minimal Express-style start or if your team is not comfortable with layered boundaries and dependency-cruiser rules. Before committing, verify that Node 24 type stripping works in your deployment images and that the OpenTelemetry auto-instrumentation, which is disabled by default, can be enabled without conflicting with your existing tracing setup. Also check that the generated client types package fits your npm publishing workflow, because that feature only triggers on release.
Community notes