Fastify
Fastify Recipe
Section titled “Fastify Recipe”Fastify is not fetch-native, so instead of createFetchHandler it binds the HTTP transport core through createRequestHandler, which takes a plain normalized request object and returns a { status, body } result. The bridge is a single catch-all route.
import Fastify from 'fastify';import { createRequestHandler, toErrorResponse } from 'uql-orm/http';import './uql.config.js'; // setQuerierPool + entity importsimport { User, Post } 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: fall through to your own routes 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 });This serves the full wire protocol for each entity (list, get, count, create, upsert, bulk operations, delete). createRequestHandler returns undefined for an unknown entity or route, so reply.callNotFound() hands those requests to Fastify’s not-found handler (a 404) instead of the CRUD path. Thrown hook errors map to the canonical { error: { message, code } } envelope via toErrorResponse (a numeric status on the error becomes the HTTP status).
createRequestHandler accepts the core’s pre, preSave, preFilter, and post hooks. The hook context is whatever you pass as context above, here the Fastify request, so session and tenant state are directly at hand:
const handle = createRequestHandler({ include: [User, Post], async preFilter({ query, context }) { // context is the Fastify request; abort by throwing with a numeric status if (!context.user) { throw Object.assign(new Error('unauthorized'), { status: 401 }); } query.$where ??= {}; Object.assign(query.$where, { creatorId: context.user.id }); },});