mgd34msu/goodvibes-gemini

fastify

Builds high-performance Node.js APIs with Fastify, TypeScript, schema validation, and plugins. Use when building fast REST APIs, microservices, or needing schema-based validation.

First seen Mar 17, 2026

Installation

$ npx skills add mgd34msu/goodvibes-gemini --skill fastify

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from mgd34msu/goodvibes-gemini · top by installs.

npx skills add mgd34msu/goodvibes-gemini

Browse all from mgd34msu/goodvibes-gemini

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Stars 4
License MIT
Default branch main
Open issues 0
Status Active

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 12,434 B
  • docs SUMMARY.md 194 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 2 installs

SKILL.md

Fastify

Fastify is a high-performance Node.js web framework. It's TypeScript-first, schema-driven, and can handle 76k+ requests/second.

Quick Start

npm install fastify
npm install -D typescript @types/node

Basic Server

import Fastify from 'fastify'

const fastify = Fastify({
  logger: true
})

fastify.get('/', async (request, reply) => {
  return { hello: 'world' }
})

const start = async () => {
  try {
    await fastify.listen({ port: 3000 })
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}

start()

TypeScript Setup

Configuration

// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "esModuleInterop": true,
    "outDir": "./dist",
    "declaration": true
  }
}

Typed Routes

import Fastify, { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'

const fastify: FastifyInstance = Fastify()

// Route with typed params
interface UserParams {
  id: string
}

fastify.get<{ Params: UserParams }>('/users/:id', async (request, reply) => {
  const { id } = request.params // typed as string
  return { userId: id }
})

// Route with typed body
interface CreateUserBody {
  email: string
  name?: string
}

fastify.post<{ Body: CreateUserBody }>('/users', async (request, reply) => {
  const { email, name } = request.body
  return { email, name }
})

// Route with typed query
interface ListUsersQuery {
  page?: number
  limit?: number
}

fastify.get<{ Querystring: ListUsersQuery }>('/users', async (request, reply) => {
  const { page = 1, limit = 10 } = request.query
  return { page, limit }
})

Schema Validation

JSON Schema

const getUserSchema = {
  params: {
    type: 'object',
    properties: {
      id: { type: 'string' }
    },
    required: ['id']
  },
  response: {
    200: {
      type: 'object',
      properties: {
        id: { type: 'string' },
        email: { type: 'string' },
        name: { type: 'string' }
      }
    }
  }
}

fastify.get('/users/:id', { schema: getUserSchema }, async (request, reply) => {
  const { id } = request.params as { id: string }
  return db.users.findById(id)
})

TypeBox (Recommended)

npm install @sinclair/typebox @fastify/type-provider-typebox
import Fastify from 'fastify'
import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
import { Type, Static } from '@sinclair/typebox'

const fastify = Fastify().withTypeProvider<TypeBoxTypeProvider>()

// Define schemas
const UserSchema = Type.Object({
  id: Type.String(),
  email: Type.String({ format: 'email' }),
  name: Type.Optional(Type.String())
})

const CreateUserSchema = Type.Object({
  email: Type.String({ format: 'email' }),
  name: Type.Optional(Type.String())
})

type User = Static<typeof UserSchema>
type CreateUser = Static<typeof CreateUserSchema>

// Route with TypeBox
fastify.post('/users', {
  schema: {
    body: CreateUserSchema,
    response: {
      201: UserSchema
    }
  }
}, async (request, reply) => {
  // request.body is typed as CreateUser
  const user = await db.users.create(request.body)
  reply.status(201)
  return user
})

Zod

npm install zod fastify-type-provider-zod
import Fastify from 'fastify'
import { serializerCompiler, validatorCompiler, ZodTypeProvider } from 'fastify-type-provider-zod'
import { z } from 'zod'

const fastify = Fastify().withTypeProvider<ZodTypeProvider>()
fastify.setValidatorCompiler(validatorCompiler)
fastify.setSerializerCompiler(serializerCompiler)

const UserSchema = z.object({
  id: z.string(),
  email: z.string().email(),
  name: z.string().optional()
})

fastify.post('/users', {
  schema: {
    body: z.object({
      email: z.string().email(),
      name: z.string().optional()
    }),
    response: {
      201: UserSchema
    }
  }
}, async (request, reply) => {
  const user = await db.users.create(request.body)
  reply.status(201)
  return user
})

Plugins

Creating Plugins

import { FastifyPluginAsync } from 'fastify'
import fp from 'fastify-plugin'

interface PluginOptions {
  prefix?: string
}

const myPlugin: FastifyPluginAsync<PluginOptions> = async (fastify, options) => {
  fastify.decorate('utility', () => 'hello')

  fastify.addHook('onRequest', async (request, reply) => {
    request.startTime = Date.now()
  })
}

export default fp(myPlugin, {
  name: 'my-plugin',
  fastify: '5.x'
})

Using Plugins

import myPlugin from './plugins/my-plugin'
import dbPlugin from './plugins/db'

await fastify.register(myPlugin, { prefix: '/api' })
await fastify.register(dbPlugin)

// Scoped plugins
await fastify.register(async (instance) => {
  // Plugins registered here only affect this scope
  await instance.register(authPlugin)

  instance.get('/protected', async (request) => {
    return { user: request.user }
  })
}, { prefix: '/api' })

Common Plugins

// CORS
import cors from '@fastify/cors'
await fastify.register(cors, {
  origin: ['https://app.example.com'],
  credentials: true
})

// Helmet (security headers)
import helmet from '@fastify/helmet'
await fastify.register(helmet)

// Rate limiting
import rateLimit from '@fastify/rate-limit'
await fastify.register(rateLimit, {
  max: 100,
  timeWindow: '1 minute'
})

// JWT
import jwt from '@fastify/jwt'
await fastify.register(jwt, {
  secret: process.env.JWT_SECRET
})

// Cookie
import cookie from '@fastify/cookie'
await fastify.register(cookie, {
  secret: process.env.COOKIE_SECRET
})

// Multipart
import multipart from '@fastify/multipart'
await fastify.register(multipart)

Hooks

Request Lifecycle

// Before routing
fastify.addHook('onRequest', async (request, reply) => {
  // Parse token, log request, etc.
})

// Before validation
fastify.addHook('preValidation', async (request, reply) => {
  // Modify request before validation
})

// Before handler
fastify.addHook('preHandler', async (request, reply) => {
  // Auth check, load data, etc.
})

// Before serialization
fastify.addHook('preSerialization', async (request, reply, payload) => {
  // Modify payload before JSON serialization
  return payload
})

// Before sending response
fastify.addHook('onSend', async (request, reply, payload) => {
  // Modify final response
  return payload
})

// After response sent
fastify.addHook('onResponse', async (request, reply) => {
  // Log response time, metrics, etc.
  const responseTime = Date.now() - request.startTime
  console.log(`${request.method} ${request.url} - ${responseTime}ms`)
})

// On error
fastify.addHook('onError', async (request, reply, error) => {
  // Log errors
  console.error(error)
})

Route-Level Hooks

fastify.route({
  method: 'GET',
  url: '/protected',
  preHandler: async (request, reply) => {
    const user = await verifyToken(request.headers.authorization)
    if (!user) {
      reply.code(401).send({ error: 'Unauthorized' })
      return
    }
    request.user = user
  },
  handler: async (request, reply) => {
    return { user: request.user }
  }
})

Error Handling

Error Handler

fastify.setErrorHandler((error, request, reply) => {
  // Log error
  request.log.error(error)

  // Validation error
  if (error.validation) {
    return reply.status(400).send({
      error: 'VALIDATION_ERROR',
      message: 'Request validation failed',
      details: error.validation
    })
  }

  // Custom errors
  if (error.statusCode) {
    return reply.status(error.statusCode).send({
      error: error.code || 'ERROR',
      message: error.message
    })
  }

  // Internal error
  reply.status(500).send({
    error: 'INTERNAL_ERROR',
    message: 'Internal server error'
  })
})

Custom Errors

import createError from '@fastify/error'

const NotFoundError = createError('NOT_FOUND', 'Resource not found', 404)
const UnauthorizedError = createError('UNAUTHORIZED', 'Authentication required', 401)

fastify.get('/users/:id', async (request, reply) => {
  const user = await db.users.findById(request.params.id)
  if (!user) {
    throw new NotFoundError()
  }
  return user
})

Authentication

JWT Authentication

import jwt from '@fastify/jwt'

await fastify.register(jwt, {
  secret: process.env.JWT_SECRET!
})

// Decorate with authenticate method
fastify.decorate('authenticate', async (request: FastifyRequest, reply: FastifyReply) => {
  try {
    await request.jwtVerify()
  } catch (err) {
    reply.send(err)
  }
})

// Protected route
fastify.get('/me', {
  onRequest: [fastify.authenticate]
}, async (request, reply) => {
  return request.user
})

// Login
fastify.post('/login', async (request, reply) => {
  const { email, password } = request.body as { email: string; password: string }

  const user = await db.users.findByEmail(email)
  if (!user || !await verifyPassword(password, user.passwordHash)) {
    reply.code(401).send({ error: 'Invalid credentials' })
    return
  }

  const token = fastify.jwt.sign({ id: user.id, email: user.email })
  return { token }
})

Decorators

Request Decorators

// Extend FastifyRequest type
declare module 'fastify' {
  interface FastifyRequest {
    user?: { id: string; email: string }
    startTime?: number
  }
}

// Add to request
fastify.decorateRequest('user', null)
fastify.decorateRequest('startTime', 0)

// Use in hooks
fastify.addHook('onRequest', async (request) => {
  request.startTime = Date.now()
})

Instance Decorators

// Extend Fastify type
declare module 'fastify' {
  interface FastifyInstance {
    db: Database
    authenticate: (request: FastifyRequest, reply: FastifyReply) => Promise<void>
  }
}

fastify.decorate('db', database)
fastify.decorate('authenticate', authMiddleware)

File Organization

Autoload Plugins

npm install @fastify/autoload
import autoLoad from '@fastify/autoload'
import { fileURLToPath } from 'url'
import { dirname, join } from 'path'

const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)

// Load all plugins from directory
await fastify.register(autoLoad, {
  dir: join(__dirname, 'plugins')
})

// Load all routes from directory
await fastify.register(autoLoad, {
  dir: join(__dirname, 'routes'),
  options: { prefix: '/api' }
})

Route File Structure

// routes/users/index.ts
import { FastifyPluginAsync } from 'fastify'

const users: FastifyPluginAsync = async (fastify) => {
  fastify.get('/', async (request, reply) => {
    return fastify.db.users.findMany()
  })

  fastify.get('/:id', async (request, reply) => {
    const { id } = request.params as { id: string }
    return fastify.db.users.findById(id)
  })

  fastify.post('/', async (request, reply) => {
    const user = await fastify.db.users.create(request.body)
    reply.code(201)
    return user
  })
}

export default users

Testing

npm install -D tap
import { test } from 'tap'
import build from './app'

test('GET /users returns users', async (t) => {
  const app = build()

  const response = await app.inject({
    method: 'GET',
    url: '/users'
  })

  t.equal(response.statusCode, 200)
  t.ok(Array.isArray(response.json()))
})

test('POST /users creates user', async (t) => {
  const app = build()

  const response = await app.inject({
    method: 'POST',
    url: '/users',
    payload: {
      email: '[email protected]',
      name: 'Test User'
    }
  })

  t.equal(response.statusCode, 201)
  t.equal(response.json().email, '[email protected]')
})

Best Practices

  1. Use schema validation - TypeBox or Zod for type safety
  2. Organize with plugins - Encapsulate related functionality
  3. Use autoload - Automatic plugin/route loading
  4. Handle errors properly - Custom error handler
  5. Add request logging - Built-in Pino logger
  6. Decorate for DI - Add db, services to instance

References

  • [Plugins](references/plugins.md)
  • [Performance](references/performance.md)