Mount Fastify applications within your MooseStack project using the WebApp class. Fastify is a fast and low overhead web framework with powerful schema-based validation.
WebApp for unified deployment and access to MooseStack utilities.import Fastify from "fastify";import { WebApp, getMooseUtils } from "@514labs/moose-lib";import { MyTable } from "../tables/MyTable"; const app = Fastify({ logger: true }); app.get("/health", async (request, reply) => { return { status: "ok" };}); app.get("/data", async (request, reply) => { const moose = getMooseUtils(request.raw); if (!moose) { reply.code(500); return { error: "Moose utilities not available" }; } const { client, sql } = moose; const limit = parseInt((request.query as any).limit || "10"); try { const query = sql` SELECT ${MyTable.columns.id}, ${MyTable.columns.name}, ${MyTable.columns.createdAt} FROM ${MyTable} ORDER BY ${MyTable.columns.createdAt} DESC LIMIT ${limit} `; const result = await client.query.execute(query); return await result.json(); } catch (error) { reply.code(500); return { error: String(error) }; }}); export const fastifyApp = new WebApp("fastifyApp", app, { mountPath: "/fastify", metadata: { description: "Fastify API" }});Access your API:
GET http://localhost:4000/fastify/healthGET http://localhost:4000/fastify/data?limit=20import Fastify, { FastifyRequest } from "fastify";import { WebApp, getMooseUtils } from "@514labs/moose-lib";import { UserEvents } from "../tables/UserEvents"; const app = Fastify({ logger: true, ajv: { customOptions: { removeAdditional: "all", coerceTypes: true } }}); // Schema definitionsconst getUserEventsSchema = { querystring: { type: "object", properties: { limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, eventType: { type: "string" } } }, params: { type: "object", required: ["userId"], properties: { userId: { type: "string", pattern: "^[a-zA-Z0-9-]+$" } } }, response: { 200: { type: "object", properties: { userId: { type: "string" }, count: { type: "integer" }, events: { type: "array" } } } }}; // GET with schema validationapp.get<{ Params: { userId: string }; Querystring: { limit?: number; eventType?: string };}>("/users/:userId/events", { schema: getUserEventsSchema}, async (request, reply) => { const moose = getMooseUtils(request.raw); if (!moose) { reply.code(500); return { error: "Moose utilities not available" }; } const { client, sql } = moose; const { userId } = request.params; const { limit = 10, eventType } = request.query; const cols = UserEvents.columns; const query = sql` SELECT ${cols.id}, ${cols.event_type}, ${cols.timestamp} FROM ${UserEvents} WHERE ${cols.user_id} = ${userId} ${eventType ? sql`AND ${cols.event_type} = ${eventType}` : sql``} ORDER BY ${cols.timestamp} DESC LIMIT ${limit} `; const result = await client.query.execute(query); const events = await result.json(); return { userId, count: events.length, events };}); // POST with schema validationconst createEventSchema = { body: { type: "object", required: ["eventType", "data"], properties: { eventType: { type: "string", minLength: 1 }, data: { type: "object" } } }}; app.post<{ Params: { userId: string }; Body: { eventType: string; data: object };}>("/users/:userId/events", { schema: createEventSchema}, async (request, reply) => { const { userId } = request.params; const { eventType, data } = request.body; // Handle POST logic return { success: true, userId, eventType, data };}); // Protected route with JWTapp.get("/protected", async (request, reply) => { const moose = getMooseUtils(request.raw); if (!moose?.jwt) { reply.code(401); return { error: "Unauthorized" }; } return { message: "Authenticated", userId: moose.jwt.sub };}); // Error handlerapp.setErrorHandler((error, request, reply) => { request.log.error(error); reply.code(500).send({ error: "Internal Server Error", message: error.message });}); await app.ready(); export const advancedFastifyApp = new WebApp("advancedFastify", app, { mountPath: "/api/v1", metadata: { description: "Advanced Fastify API with schema validation" }});Use request.raw to access the underlying Node.js request:
const moose = getMooseUtils(request.raw);if (!moose) { reply.code(500); return { error: "Utilities not available" };} const { client, sql, jwt } = moose;Fastify plugins work seamlessly:
import Fastify from "fastify";import cors from "@fastify/cors";import helmet from "@fastify/helmet";import rateLimit from "@fastify/rate-limit";import { getMooseUtils } from "@514labs/moose-lib";import { MyTable } from "../tables/MyTable"; const app = Fastify({ logger: true }); // CORSawait app.register(cors, { origin: true}); // Security headersawait app.register(helmet); // Rate limitingawait app.register(rateLimit, { max: 100, timeWindow: "15 minutes"}); // Custom decoratorapp.decorate("utility", { formatResponse: (data: any) => ({ success: true, timestamp: new Date().toISOString(), data })}); app.get("/data", async (request, reply) => { const moose = getMooseUtils(request.raw); if (!moose) { reply.code(500); return { error: "Utilities not available" }; } const { client, sql } = moose; const result = await client.query.execute(sql` SELECT ${MyTable.columns.id}, ${MyTable.columns.name}, ${MyTable.columns.status} FROM ${MyTable} WHERE ${MyTable.columns.status} = 'active' LIMIT 10 `); const data = await result.json(); return app.utility.formatResponse(data);}); await app.ready();Leverage TypeScript for type-safe routes:
interface UserQueryParams { limit?: number; offset?: number; status?: "active" | "inactive";} interface UserResponse { id: string; name: string; email: string;} app.get<{ Querystring: UserQueryParams; Reply: UserResponse[]}>("/users", async (request, reply) => { const { limit = 10, offset = 0, status } = request.query; // TypeScript knows the shape of query params const moose = getMooseUtils(request.raw); // ... query logic // Return type is checked return [ { id: "1", name: "John", email: "john@example.com" } ];});new WebApp(name, app, config)WebAppConfig:
interface WebAppConfig { mountPath: string; metadata?: { description?: string }; injectMooseUtils?: boolean; // default: true}app.ready() before creating WebAppgetMooseUtils(request.raw)setErrorHandler for global error handlingSolution: Use request.raw to access the underlying request:
const moose = getMooseUtils(request.raw); // Not request!Solution: Ensure you called .ready():
await app.ready(); // Must call before WebAppexport const fastifyApp = new WebApp("name", app, config);Solution: Match your TypeScript types with JSON schemas:
// JSON Schema{ querystring: { type: "object", properties: { limit: { type: "integer" } } } } // TypeScript typeinterface Query { limit?: number }