AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Nodejs Fastify

skill-j4flmao-agent-skills-fastify · by j4flmao

>

No reviews yet
0 installs
34 views
0.0% view→install

Install

$ agentstack add skill-j4flmao-agent-skills-fastify

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-j4flmao-agent-skills-fastify)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
2mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Nodejs Fastify? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Node.js Fastify

Purpose

Build Fastify applications with schema-first validation, plugin encapsulation, request/response serialization, and lifecycle hooks.

Agent Protocol

Trigger

User request includes: fastify, fastify server, fastify plugin, fastify schema, fastify hooks, fastify validation, fastify serializer, TypeBox, @fastify/swagger.

Input Context

  • Fastify version (4.x, 5.x)
  • Validation approach (JSON Schema, TypeBox, Zod)
  • Plugin set (swagger, cors, jwt, rate-limit)
  • Database (TypeORM, Prisma, Knex)

Output Artifact

Server setup, plugin registration, schema definitions, hook pipeline, route patterns.

Response Format

Produce artifact directly. No preamble, no postamble, no explanations.

Completion Criteria

  • Server created with logger and schema validator
  • Plugins registered with encapsulation
  • Routes defined with JSON Schema validation
  • Hooks attached at correct lifecycle points
  • Swagger/OpenAPI documentation configured

Max Response Length

4096 tokens

Architecture Decision Trees

Validation: JSON Schema vs TypeBox vs Zod

| Criterion | JSON Schema | TypeBox | Zod | |-----------|-------------|---------|-----| | Type inference | Manual | Static | z.infer | | Code-first | No | Yes | Yes | | Composition | $ref | Type.Union, Type.Intersect | z.union, z.intersection | | Performance | Fast native compilation | Fastest (TypeCode compiler) | Good | | Ecosystem | Standard, swagger compatible | Fastify-native | Widely used |

Decision: Need OpenAPI/Swagger docs → JSON Schema or TypeBox. Want maximum type safety → TypeBox. Already using Zod in codebase → Zod.

Plugin vs Register Scope

| Scope | Encapsulation | Use Case | |-------|--------------|----------| | Root | Global | cors, rate-limiter, jwt, formbody | | Plugin-level | Encapsulated within plugin | Feature modules, route groups | | Child | Nested encapsulation | Sub-plugins within feature |

Decision: Infrastructure cross-cutting → Root. Feature-specific → Plugin. Shared helpers → fastify-plugin (bypass encapsulation).

Workflow

Step 1: Server Bootstrap

// src/server.ts
import Fastify from 'fastify';
import { registerPlugins } from './plugins';
import { config } from './config';

export async function buildServer() {
  const server = Fastify({
    logger: {
      level: config.LOG_LEVEL,
      transport: config.NODE_ENV === 'development'
        ? { target: 'pino-pretty' }
        : undefined,
    },
    ajv: {
      customOptions: { allErrors: true, coerceTypes: 'array' },
    },
  });

  await registerPlugins(server);

  // Health check
  server.get('/health', async () => ({ status: 'ok', uptime: process.uptime() }));

  return server;
}

// src/index.ts
import { buildServer } from './server';

async function main() {
  const server = await buildServer();
  const port = parseInt(process.env.PORT || '3000');

  await server.listen({ port, host: '0.0.0.0' });

  const shutdown = async (signal: string) => {
    server.log.info(`${signal} received. Shutting down...`);
    await server.close();
    process.exit(0);
  };
  process.on('SIGTERM', () => shutdown('SIGTERM'));
  process.on('SIGINT', () => shutdown('SIGINT'));
}

main().catch(err => {
  console.error('Fatal:', err);
  process.exit(1);
});

Step 2: Plugin Registration

// src/plugins/index.ts
import { FastifyInstance } from 'fastify';
import fp from 'fastify-plugin';
import cors from '@fastify/cors';
import helmet from '@fastify/helmet';
import rateLimit from '@fastify/rate-limit';
import swagger from '@fastify/swagger';
import swaggerUi from '@fastify/swagger-ui';
import jwt from '@fastify/jwt';
import { userModule } from '../modules/users';
import { orderModule } from '../modules/orders';
import { errorHandlerPlugin } from './error-handler';
import { config } from '../config';

export async function registerPlugins(app: FastifyInstance) {
  // Core plugins (global)
  await app.register(cors, { origin: config.CORS_ORIGIN });
  await app.register(helmet);
  await app.register(rateLimit, { max: 100, timeWindow: '1 minute' });
  await app.register(jwt, { secret: config.JWT_SECRET });

  // Documentation
  await app.register(swagger, {
    openapi: { info: { title: 'API', version: '1.0.0' } },
  });
  await app.register(swaggerUi, { routePrefix: '/docs' });

  // Error handler
  await app.register(errorHandlerPlugin);

  // Feature modules (encapsulated)
  await app.register(userModule, { prefix: '/api/v1/users' });
  await app.register(orderModule, { prefix: '/api/v1/orders' });
}

Step 3: Schema-First Routes with TypeBox

// src/modules/users/schema.ts
import { Type, Static } from '@sinclair/typebox';

export const CreateUserSchema = Type.Object({
  name: Type.String({ minLength: 2, maxLength: 100 }),
  email: Type.String({ format: 'email' }),
  role: Type.Optional(Type.Union([Type.Literal('admin'), Type.Literal('user')])),
});

export const UserResponseSchema = Type.Object({
  id: Type.String({ format: 'uuid' }),
  name: Type.String(),
  email: Type.String({ format: 'email' }),
  role: Type.String(),
  createdAt: Type.String({ format: 'date-time' }),
});

export const UserParamsSchema = Type.Object({
  id: Type.String({ format: 'uuid' }),
});

export type CreateUserDto = Static;

// src/modules/users/routes.ts
import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
import { userService } from './service';
import { CreateUserSchema, UserResponseSchema, UserParamsSchema } from './schema';

export async function userRoutes(app: FastifyInstance) {
  app.get('/', {
    schema: {
      querystring: Type.Object({
        page: Type.Optional(Type.Number({ default: 1 })),
        limit: Type.Optional(Type.Number({ default: 20 })),
      }),
      response: {
        200: Type.Object({
          data: Type.Array(UserResponseSchema),
          total: Type.Number(),
          page: Type.Number(),
        }),
      },
    },
    handler: async (request, reply) => {
      const { page, limit } = request.query;
      const result = await userService.findAll(page, limit);
      return result;
    },
  });

  app.get }>('/:id', {
    schema: {
      params: UserParamsSchema,
      response: { 200: UserResponseSchema, 404: Type.Object({ error: Type.String() }) },
    },
    handler: async (request, reply) => {
      const user = await userService.findById(request.params.id);
      if (!user) {
        reply.code(404);
        return { error: 'User not found' };
      }
      return user;
    },
  });

  app.post('/', {
    schema: {
      body: CreateUserSchema,
      response: { 201: UserResponseSchema },
    },
    handler: async (request, reply) => {
      const user = await userService.create(request.body);
      reply.code(201);
      return user;
    },
  });
}

Step 4: Hooks Lifecycle

// src/plugins/hooks.ts
import { FastifyInstance } from 'fastify';

export async function registerHooks(app: FastifyInstance) {
  // Pre-validation — transform request
  app.addHook('preValidation', async (request, reply) => {
    // e.g., add request ID
    request.id = request.id || crypto.randomUUID();
  });

  // Pre-handler — auth check
  app.addHook('preHandler', async (request, reply) => {
    if (request.url.startsWith('/api/v1/auth')) return;
    try {
      await request.jwtVerify();
    } catch (err) {
      reply.code(401).send({ error: 'Unauthorized' });
    }
  });

  // On-send — response logging
  app.addHook('onSend', async (request, reply, payload) => {
    reply.header('X-Request-Id', request.id as string);
    return payload;
  });

  // On-response — metrics
  app.addHook('onResponse', async (request, reply) => {
    const elapsed = reply.elapsedTime;
    app.log.info({ method: request.method, url: request.url, status: reply.statusCode, elapsed }, 'response');
  });
}

Step 5: Error Handler Plugin

// src/plugins/error-handler.ts
import { FastifyInstance, FastifyError, FastifyReply, FastifyRequest } from 'fastify';

export async function errorHandlerPlugin(app: FastifyInstance) {
  app.setErrorHandler((error: FastifyError, request: FastifyRequest, reply: FastifyReply) => {
    const statusCode = error.statusCode || 500;

    app.log.error({ err: error, requestId: request.id }, error.message);

    if (statusCode === 429) {
      return reply.code(429).send({ error: 'Too many requests', retryAfter: error.message });
    }

    if (error.validation) {
      return reply.code(400).send({
        error: 'Validation Error',
        details: error.validation.map(v => ({
          field: v.instancePath,
          message: v.message,
        })),
      });
    }

    const response = statusCode >= 500
      ? { error: 'Internal Server Error' }
      : { error: error.message };

    return reply.code(statusCode).send(response);
  });
}

Step 6: Graceful Shutdown with Health Check

// Graceful shutdown wrapper
async function main() {
  const app = await buildServer();
  await app.listen({ port: 3000 });

  const signals = ['SIGTERM', 'SIGINT'];
  for (const signal of signals) {
    process.on(signal, async () => {
      app.log.info(`${signal} received, shutting down`);
      await app.close();
      process.exit(0);
    });
  }
}

Implementation Patterns

Pattern: Encapsulated Plugin Module

// src/modules/users/index.ts
import { FastifyInstance } from 'fastify';
import { userRoutes } from './routes';

export async function userModule(app: FastifyInstance) {
  // Auth decorator (encapsulated — only within this plugin)
  app.decorate('userModule', true);

  // Pre-handler specific to this module
  app.addHook('preHandler', async (request, reply) => {
    // Module-level auth
  });

  await app.register(userRoutes);
}

Pattern: Custom Decorator

// src/plugins/auth-decorators.ts
import { FastifyInstance, FastifyRequest } from 'fastify';
import fp from 'fastify-plugin';

declare module 'fastify' {
  interface FastifyInstance {
    authenticate: (request: FastifyRequest, reply: FastifyReply) => Promise;
  }
}

export default fp(async function (app: FastifyInstance) {
  app.decorate('authenticate', async (request: FastifyRequest, reply: FastifyReply) => {
    try {
      await request.jwtVerify();
    } catch (err) {
      reply.code(401).send({ error: 'Unauthorized' });
    }
  });
});

Production Considerations

Serialization

Fastify serializes responses using compiled schemas. Define response schemas for maximum performance. Use @fastify/response-validation for development response validation.

Compression

await app.register(import('@fastify/compress'), { global: true, threshold: 1024 });

Trust Proxy

const server = Fastify({ trustProxy: true });

Anti-Patterns

| Anti-Pattern | Why | Fix | |-------------|-----|-----| | No schema validation | Manual validation, no serialization benefits | Always define schema on routes | | Global plugins modifying request | Encapsulation violation | Use fastify-plugin for shared decorators | | Reply payload manual JSON.stringify | Breaks serialization optimization | Return object, let Fastify serialize | | Heavy imports in hot handler | Slows non-related requests | Use await import() or separate plugins | | Async route without error handling | Unhandled promise rejection | Fastify wraps async handlers automatically |

Security Considerations

  • @fastify/helmet for security headers — register early
  • @fastify/cors with explicit origins — never * with credentials
  • @fastify/rate-limit per IP — configure per route for auth endpoints
  • @fastify/csrf protection for cookie-based auth
  • Schema validation strips unknown properties by default (additionalProperties: false)
  • JWT via @fastify/jwt with cookie option for cookie-based auth

Testing Strategies

import { buildServer } from '../src/server';
import { test, expect } from 'vitest';

test('POST /api/v1/users creates user', async () => {
  const app = await buildServer();
  const response = await app.inject({
    method: 'POST',
    url: '/api/v1/users',
    payload: { name: 'Test', email: 'test@test.com' },
  });
  expect(response.statusCode).toBe(201);
  expect(response.json().data).toHaveProperty('id');
});

test('GET /api/v1/users/:id returns 404', async () => {
  const app = await buildServer();
  const response = await app.inject({
    method: 'GET',
    url: '/api/v1/users/nonexistent',
  });
  expect(response.statusCode).toBe(404);
});

Use app.inject() for HTTP testing without a server. Use @fastify/test-helper for lifecycle tests.

Rules

  • Every route has a schema with body/querystring/params/response definitions.
  • Schema-first approach with TypeBox for type inference.
  • Plugin encapsulation: feature modules register their own routes, hooks, decorators.
  • Never use require — always import. Fastify supports native ESM.
  • Lifecycle hooks: onRequestpreParsingpreValidationpreHandler → handler → preSerializationonSendonResponse.
  • @fastify/swagger generates OpenAPI docs from route schemas automatically.
  • Graceful shutdown via app.close() in signal handlers.

References

  • references/fastify-advanced-plugins.md — Advanced Plugin Patterns
  • references/fastify-hooks.md — Lifecycle Hooks
  • references/fastify-plugins.md — Plugin System
  • references/fastify-setup.md — Setup Guide
  • references/fastify-testing.md — Testing
  • references/fastify-validation.md — Validation and Serialization

Handoff

Hand off to backend/universal/api-response/SKILL.md for API response formatting or backend/nodejs/patterns/SKILL.md for Node.js patterns.

Implementation Patterns

Factory Pattern for Module Creation

function createModule(config: ModuleConfig): T { const dependencies = initializeDependencies(config); const module = new Module(dependencies); module.hooks.onInit(); return module as T; }

Builder Pattern for Complex Configuration

class ConfigBuilder { private config: AppConfig = new AppConfig(); withDatabase(url: string): ConfigBuilder { ... } withCache(ttl: number): ConfigBuilder { ... } withLogging(level: string): ConfigBuilder { ... } build(): AppConfig { return this.config; } }

Production Considerations

Deployment Checklist

  • [ ] Production build with optimizations enabled
  • [ ] Environment variables configured per environment
  • [ ] Health check endpoint responds correctly
  • [ ] Error tracking and monitoring integrated
  • [ ] Logging level configured (not debug in production)
  • [ ] Resource limits configured
  • [ ] Database migrations applied
  • [ ] Static assets built and served from CDN or cache
  • [ ] Feature flags toggled appropriately
  • [ ] Rollback plan documented and tested

Monitoring and Alerting

| Metric | Threshold | Severity | Action | |--------|-----------|----------|--------| | Error rate | > 1% | Critical | Rollback or fix | | p95 latency | > 500ms | Warning | Profile and optimize | | Uptime | 80% | Warning | Check for leaks | | CPU usage | > 80% | Warning | Scale up or optimize |

Rules

  • Prefer composition over inheritance
  • Favor immutable data structures
  • Use dependency injection for testability
  • Keep functions pure when possible — no side effects
  • Fail fast with clear error messages
  • Don't repeat yourself (DRY) — extract shared logic
  • Keep it simple (KISS) — avoid unnecessary complexity
  • You aren't gonna need it (YAGNI) — build what's required
  • Separate concerns — single responsibility per module
  • Code to interfaces, not implementations
  • Write self-documenting code — clear names over comments
  • Prefer standard library over third-party dependencies
  • Handle errors explicitly — no silent failures
  • Validate inputs at boundaries
  • Log at appropriate levels (debug, info, warn, error)

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.