1. MooseStack
  2. App / API frameworks
  3. Fastify with MooseStack

On this page

Basic ExampleComplete Example with Schema ValidationAccessing Moose UtilitiesPlugins and DecoratorsType-Safe RoutesWebApp ConfigurationBest PracticesTroubleshooting"Moose utilities not available"App not responding after mountingSchema validation errors

Fastify with MooseStack

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.

Choose your integration path
  • Already running Fastify elsewhere? Keep it outside your MooseStack project and query data with the MooseStack client. The Querying Data guide walks through the SDK.
  • Want to mount Fastify in your MooseStack project? Follow the setup below with WebApp for unified deployment and access to MooseStack utilities.

Basic Example

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/health
  • GET http://localhost:4000/fastify/data?limit=20
  • Complete Example with Schema Validation

    import 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"  }});

    Accessing Moose Utilities

    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;

    Plugins and Decorators

    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();

    Type-Safe Routes

    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" }  ];});

    WebApp Configuration

    new WebApp(name, app, config)

    WebAppConfig:

    interface WebAppConfig {  mountPath: string;  metadata?: { description?: string };  injectMooseUtils?: boolean; // default: true}

    Best Practices

    1. Call .ready() before WebApp: Always await app.ready() before creating WebApp
    2. Use request.raw for utilities: Access Moose utilities via getMooseUtils(request.raw)
    3. Define schemas: Use Fastify's JSON Schema validation for request/response
    4. Type your routes: Use TypeScript generics for type-safe route handlers
    5. Leverage plugins: Use Fastify's rich plugin ecosystem
    6. Handle errors: Use setErrorHandler for global error handling
    7. Enable logging: Use Fastify's built-in logger for debugging

    Troubleshooting

    "Moose utilities not available"

    Solution: Use request.raw to access the underlying request:

    const moose = getMooseUtils(request.raw); // Not request!

    App not responding after mounting

    Solution: Ensure you called .ready():

    await app.ready(); // Must call before WebAppexport const fastifyApp = new WebApp("name", app, config);

    Schema validation errors

    Solution: Match your TypeScript types with JSON schemas:

    // JSON Schema{ querystring: { type: "object", properties: { limit: { type: "integer" } } } } // TypeScript typeinterface Query { limit?: number }
    • Overview
    • Quick Start
    • Templates / Examples
    Fundamentals
    • Moose Runtime
    • MooseDev MCP
    • Data Modeling
    MooseStack in your App
    • App / API frameworks
      • Next.js
      • Express
      • Fastify
      • Koa
      • Raw Node.js
    Modules
    • Moose OLAP
    • Moose Streaming
    • Moose Workflows
    • Moose APIs
    Deployment & Lifecycle
    • Moose Migrate
    • Moose Deploy
    Reference
    • API Reference
    • Data Types
    • Table Engines
    • CLI
    • Configuration
    • Observability Metrics
    • Help
    • Changelog
    Contribution
    • Documentation
    • Framework
    FiveonefourFiveonefour
    Fiveonefour Docs
    MooseStackTemplates
    Changelog
    Source506