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.
Builds high-performance Node.js APIs with Fastify, TypeScript, and schema validation. Use when creating REST APIs or microservices that need type-safe routes, request validation, and plugin architecture.
/plugin marketplace add mgd34msu/goodvibes-plugin/plugin install goodvibes@goodvibes-marketThis skill inherits all available tools. When active, it can use any tool Claude has access to.
references/performance.mdreferences/plugins.mdFastify is a high-performance Node.js web framework. It's TypeScript-first, schema-driven, and can handle 76k+ requests/second.
npm install fastify
npm install -D typescript @types/node
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()
// tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"esModuleInterop": true,
"outDir": "./dist",
"declaration": true
}
}
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 }
})
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)
})
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
})
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
})
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'
})
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' })
// 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)
// 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)
})
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 }
}
})
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'
})
})
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
})
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 }
})
// 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()
})
// 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)
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' }
})
// 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
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')
})
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.