Fastify
Fastify Recipe
Section titled “Fastify Recipe”Fastify is not fetch-native, so it binds the HTTP transport core through createRequestHandler, which takes a normalized request object and returns { status, body }. The bridge is one catch-all route.
import Fastify from 'fastify';import { createRequestHandler, toErrorResponse } from 'uql-orm/http';import './uql.config.js'; // setQuerierPool + entity importsimport { Post, User } from './shared/models/index.js';
const fastify = Fastify();const handle = createRequestHandler({ include: [User, Post] });
fastify.all<{ Params: { entityPath: string; subPath?: string }; Querystring: Record<string, unknown>;}>('/api/:entityPath/:subPath?', async (req, reply) => { const { entityPath, subPath } = req.params; const pending = handle({ method: req.method, entityPath, subPath, query: req.query, body: req.body, context: req, // passed through to hooks }); // unknown entity/route: hand it to Fastify's not-found handler if (!pending) return reply.callNotFound(); try { const { status, body } = await pending; return reply.status(status).send(body); } catch (err) { const { status, body } = toErrorResponse(err); return reply.status(status).send(body); }});
await fastify.listen({ port: 3000 });That serves the full wire protocol per entity. Thrown hook errors map to the canonical envelope via toErrorResponse, with a numeric status on the error becoming the HTTP status. Fastify already parses application/json, so the write routes need nothing added.
fastify.all registers the standard verbs only, so this bridge serves the GET read transport but not the QUERY method.
createRequestHandler accepts the core’s hooks; the hook context is whatever you passed above, here the Fastify request:
const handle = createRequestHandler({ include: [User, Post], async pre({ context }) { if (!context.user) { throw Object.assign(new Error('unauthorized'), { status: 401 }); } },});For tenant isolation pass getContext plus a security filter rather than folding $where by hand: it scopes every query in the request, cannot be bypassed from the wire, and fails closed. See Multi-tenancy.
Register your own routes on their own paths and Fastify matches them before this catch-all, so both styles coexist.