askill
fastify-typebox-apis

fastify-typebox-apisSafety 60Repository

Building REST APIs with Fastify and TypeBox in TypeScript. Use when creating routes, defining request/response schemas, implementing validation, structuring API modules, or working with type-safe HTTP handlers.

0 stars
1.2k downloads
Updated 1/11/2026

Package Files

Loading files...
SKILL.md

Fastify + TypeBox APIs

Type-safe REST APIs with automatic validation and serialization.

Setup

npm i fastify @fastify/type-provider-typebox @sinclair/typebox
import Fastify from 'fastify'
import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'

const app = Fastify({ logger: true }).withTypeProvider<TypeBoxTypeProvider>()

Schema Definition

import { Type, Static } from '@sinclair/typebox'

// Request/response schemas
export const UserSchema = Type.Object({
  id: Type.String({ format: 'uuid' }),
  name: Type.String({ minLength: 1, maxLength: 100 }),
  email: Type.String({ format: 'email' }),
  createdAt: Type.String({ format: 'date-time' }),
})

export type User = Static<typeof UserSchema>

// Input schemas (omit generated fields)
export const CreateUserSchema = Type.Object({
  name: Type.String({ minLength: 1, maxLength: 100 }),
  email: Type.String({ format: 'email' }),
})

export type CreateUserInput = Static<typeof CreateUserSchema>

Route with Full Schema

app.post('/users', {
  schema: {
    body: CreateUserSchema,
    response: {
      201: UserSchema,
      400: Type.Object({
        statusCode: Type.Number(),
        error: Type.String(),
        message: Type.String(),
      }),
    },
  },
}, async (request, reply) => {
  const { name, email } = request.body // fully typed
  
  const user = await createUser({ name, email })
  return reply.status(201).send(user)
})

Common Schema Patterns

// Path parameters
const ParamsSchema = Type.Object({
  id: Type.String({ format: 'uuid' }),
})

// Query string with pagination
const QuerySchema = Type.Object({
  limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 100, default: 20 })),
  cursor: Type.Optional(Type.String()),
  sort: Type.Optional(Type.Union([Type.Literal('asc'), Type.Literal('desc')])),
})

// Paginated response wrapper
const PaginatedResponse = <T extends TSchema>(itemSchema: T) =>
  Type.Object({
    items: Type.Array(itemSchema),
    nextCursor: Type.Optional(Type.String()),
    hasMore: Type.Boolean(),
  })

app.get('/users/:id', {
  schema: {
    params: ParamsSchema,
    querystring: QuerySchema,
    response: {
      200: UserSchema,
      404: Type.Object({ message: Type.String() }),
    },
  },
}, async (request, reply) => {
  const { id } = request.params
  const { limit, cursor } = request.query
  // ...
})

Modular Route Registration

// types.ts - Export typed Fastify instance
import {
  FastifyInstance,
  FastifyBaseLogger,
  RawReplyDefaultExpression,
  RawRequestDefaultExpression,
  RawServerDefault,
} from 'fastify'
import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'

export type FastifyTypebox = FastifyInstance<
  RawServerDefault,
  RawRequestDefaultExpression<RawServerDefault>,
  RawReplyDefaultExpression<RawServerDefault>,
  FastifyBaseLogger,
  TypeBoxTypeProvider
>
// routes/users.ts
import { Type } from '@sinclair/typebox'
import { FastifyTypebox } from '../types'

export async function userRoutes(app: FastifyTypebox) {
  app.get('/users', {
    schema: {
      response: {
        200: Type.Array(UserSchema),
      },
    },
  }, async () => {
    return await listUsers()
  })
}
// index.ts
import { userRoutes } from './routes/users'

app.register(userRoutes, { prefix: '/api/v1' })

Error Handling

import { Type } from '@sinclair/typebox'

// Standard error schema
export const ErrorSchema = Type.Object({
  statusCode: Type.Number(),
  error: Type.String(),
  message: Type.String(),
})

// Custom error handler
app.setErrorHandler((error, request, reply) => {
  if (error.validation) {
    return reply.status(400).send({
      statusCode: 400,
      error: 'Validation Error',
      message: error.message,
    })
  }
  
  request.log.error(error)
  return reply.status(500).send({
    statusCode: 500,
    error: 'Internal Server Error',
    message: 'Something went wrong',
  })
})

Reusable Schemas (Shared References)

// Add schema to instance for $ref usage
app.addSchema({
  $id: 'User',
  ...UserSchema,
})

app.addSchema({
  $id: 'Error',
  ...ErrorSchema,
})

// Reference in routes
app.get('/me', {
  schema: {
    response: {
      200: Type.Ref('User'),
      401: Type.Ref('Error'),
    },
  },
}, handler)

Headers and Auth

const AuthHeadersSchema = Type.Object({
  authorization: Type.String({ pattern: '^Bearer .+$' }),
})

app.get('/protected', {
  schema: {
    headers: AuthHeadersSchema,
    response: { 200: UserSchema },
  },
  preValidation: async (request, reply) => {
    const token = request.headers.authorization?.replace('Bearer ', '')
    if (!token || !verifyToken(token)) {
      return reply.status(401).send({ message: 'Unauthorized' })
    }
  },
}, handler)

Guidelines

  1. Always define schemas with Type.Object({ ... }) - full JSON Schema required in Fastify v5
  2. Use Static<typeof Schema> to derive TypeScript types from schemas
  3. Split input schemas (CreateX) from output schemas (X) - omit generated fields
  4. Define response schemas for all status codes you return
  5. Use Type.Optional() for optional fields, not ? in the type
  6. Export FastifyTypebox type for modular route files
  7. Add format validators: uuid, email, date-time, uri
  8. Use Type.Union([Type.Literal(...)]) for string enums

Install

Download ZIP
Requires askill CLI v1.0+

AI Quality Score

81/100Analyzed 4/14/2026

High-quality technical skill covering Fastify+TypeBox REST API development. Excellent actionability with copy-paste ready code examples for setup, schema patterns, modular routes, error handling, and authentication. Well-structured with clear sections and good metadata (tags, description with 'use when' trigger). Minor gaps in safety coverage (no rate limiting/sanitization mention) and completeness (no testing/hooks), but these are acceptable for the schema validation focus. Generic enough for broad reuse across projects.

60
90
85
70
90

Metadata

Licenseunknown
Version-
Updated1/11/2026
Publishermartinffx

Tags

apisecurity