@routegraph/fastify
v1.0.0
Published
Fastify v4/v5 plugin for RouteGraph — Zod owns validation, Fastify's built-in schema validation stays off.
Readme
@routegraph/fastify
Fastify adapter for RouteGraph — a Fastify plugin, not a standalone router object.
Installation
pnpm add @routegraph/fastify @routegraph/core fastify zodfastify is a peer dependency (>=4.0.0).
createFastifyPlugin(graph)
function createFastifyPlugin(graph: RouteGraph): FastifyPluginAsyncFastify's own architecture is plugin-based, so this adapter returns a FastifyPluginAsync meant to be registered with app.register(...), rather than an externally-built router mounted with app.use(...) the way the Express/Koa adapters work — see DECISIONS.md for why.
Usage
import Fastify from 'fastify'
import { RouteGraph } from '@routegraph/core'
import { createFastifyPlugin } from '@routegraph/fastify'
const graph = new RouteGraph({ routesDir: './routes' })
await graph.load()
const app = Fastify()
await app.register(createFastifyPlugin(graph), { prefix: '/api' })
app.setErrorHandler((err, _request, reply) => {
reply.code(500).send({ error: 'Internal server error' })
})
await app.listen({ port: 3000 })Why Fastify's built-in validation is disabled
Every route is registered with { schema: {} }, which prevents Fastify's own AJV-based schema compiler from running. RouteGraph owns validation end-to-end via validateRequest() (Zod safeParse) — leaving Fastify's schema validation active alongside it would be redundant at best and could reject requests differently than RouteGraph's own 400 response would.
Using reply.raw for Fastify-specific features
NormalizedRequest.raw is the Fastify FastifyRequest object; there's no equivalent .raw on NormalizedResponse (every NormalizedResponse method already maps 1:1 onto reply inside the adapter — .json() → reply.send(), .status() → reply.code(), .setHeader() → reply.header()). To reach reply itself (e.g. reply.raw for the underlying Node ServerResponse, as the docs-UI bridging in examples/with-express/index.fastify.ts does), you need to wire that outside a route handler — e.g. via a Fastify hook registered alongside the plugin.
const handler: RouteHandler<typeof config> = async (req, res) => {
const request = req.raw as import('fastify').FastifyRequest
console.log(request.id)
}Validation error format
{ "error": "Validation failed", "issues": [{ "field": "query.role", "message": "Invalid enum value", "code": "invalid_enum_value" }] }Hot reload
Like every other adapter, routes are looked up fresh per request (graph.getRoute(method, urlPath)), so @routegraph/watcher's graph.reload() takes effect on the next request without re-registering anything with Fastify.
