Claude-skill-registry 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.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/fastify" ~/.claude/skills/majiayu000-claude-skill-registry-fastify && rm -rf "$T"
manifest:
skills/data/fastify/SKILL.mdsource content
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: 'test@example.com', name: 'Test User' } }) t.equal(response.statusCode, 201) t.equal(response.json().email, 'test@example.com') })
Best Practices
- Use schema validation - TypeBox or Zod for type safety
- Organize with plugins - Encapsulate related functionality
- Use autoload - Automatic plugin/route loading
- Handle errors properly - Custom error handler
- Add request logging - Built-in Pino logger
- Decorate for DI - Add db, services to instance