| name | typebox-schemas |
| description | Guide for TypeBox schema conventions in Fastify routes and environment validation. Use when defining request/response schemas or validating configuration. |
TypeBox Schema Conventions
This skill provides patterns for using TypeBox schemas in this repository.
Route Schemas (Fastify Context)
Use FastifyPluginAsyncTypebox for route plugins to get automatic TypeBox type inference:
import { type FastifyPluginAsyncTypebox, Type } from "@fastify/type-provider-typebox";
const routes: FastifyPluginAsyncTypebox = async (fastify) => {
fastify.get(
"/endpoint",
{
schema: {
description: "Endpoint description",
tags: ["tag-name"],
summary: "Short summary",
querystring: Type.Object({
page: Type.Number({ minimum: 1, default: 1 }),
limit: Type.Number({ minimum: 1, maximum: 100, default: 10 }),
}),
params: Type.Object({
id: Type.String({ format: "uuid" }),
}),
body: Type.Object({
name: Type.String({ minLength: 1, maxLength: 255 }),
email: Type.String({ format: "email" }),
}),
response: {
200: Type.Object({
status: Type.Literal("ok"),
data: Type.Array(Type.Object({
id: Type.String(),
name: Type.String(),
})),
}),
400: Type.Object({
message: Type.String(),
}),
404: Type.Object({
message: Type.String(),
}),
},
},
},
async (request, reply) => {
return { status: "ok", data: [] };
},
);
};
export default routes;
Environment Validation (Pre-Fastify)
Use TypeBox standalone for environment validation before Fastify instance creation:
import Type from "typebox";
import Value from "typebox/value";
const EnvSchema = Type.Object({
NODE_ENV: Type.Union(
[
Type.Literal("development"),
Type.Literal("production"),
Type.Literal("test"),
],
{ default: "development" },
),
PORT: Type.Number({ default: 3000 }),
HOST: Type.String({ default: "0.0.0.0" }),
LOG_LEVEL: Type.Union(
[
Type.Literal("trace"),
Type.Literal("debug"),
Type.Literal("info"),
Type.Literal("warn"),
Type.Literal("error"),
Type.Literal("fatal"),
],
{ default: "info" },
),
});
export const env = Value.Parse(EnvSchema, {
NODE_ENV: process.env.NODE_ENV ?? "development",
PORT: process.env.PORT ? Number(process.env.PORT) : undefined,
HOST: process.env.HOST,
LOG_LEVEL: process.env.LOG_LEVEL,
});
Import Conventions
// Route plugins - use FastifyPluginAsyncTypebox for automatic type inference
import { type FastifyPluginAsyncTypebox, Type } from "@fastify/type-provider-typebox";
// Environment validation - use standalone typebox package
import Type from "typebox";
import Value from "typebox/value";
// Do not use the direct @sinclair/typebox import
// import { Type } from "@sinclair/typebox"; // Do not use
Common Type Patterns
String Types
Type.String() // Any string
Type.String({ minLength: 1 }) // Non-empty string
Type.String({ format: "email" }) // Email format
Type.String({ format: "uuid" }) // UUID format
Type.String({ pattern: "^[a-z]+$" }) // Regex pattern
Type.Literal("value") // Exact string value
Number Types
Type.Number() // Any number
Type.Number({ minimum: 0 }) // Non-negative
Type.Number({ minimum: 1, maximum: 100 }) // Range
Type.Integer() // Integer only
Object Types
Type.Object({
required: Type.String(),
optional: Type.Optional(Type.String()),
})
Array Types
Type.Array(Type.String()) // Array of strings
Type.Array(Type.Object({ id: Type.String() })) // Array of objects
Type.Array(Type.String(), { minItems: 1 }) // Non-empty array
Union Types
Type.Union([
Type.Literal("option1"),
Type.Literal("option2"),
Type.Literal("option3"),
])
With Defaults
Type.String({ default: "default-value" })
Type.Number({ default: 10 })
Type.Boolean({ default: false })
OpenAPI Integration
TypeBox schemas automatically generate OpenAPI documentation. Include:
descriptionon schema properties for documentation- Appropriate format hints (email, uuid, date-time)
- Valid default values
- Proper constraints (minLength, maximum, etc.)
Type.Object({
email: Type.String({
format: "email",
description: "User email address",
}),
createdAt: Type.String({
format: "date-time",
description: "ISO 8601 timestamp",
}),
})
Testing TypeBox Schemas
Verify response shapes match schemas in tests:
it("should return response matching schema", async () => {
const response = await fastify.inject({
method: "GET",
url: "/endpoint",
});
expect(response.statusCode).toBe(200);
const body = response.json();
// Verify structure matches schema
expect(body).toHaveProperty("status", "ok");
expect(body).toHaveProperty("data");
expect(Array.isArray(body.data)).toBe(true);
});
Commands
cd app
npm run build # TypeScript will validate schema types
npm run check # Run Biome linter
npm run test # Run tests
Boundaries
- Always use TypeBox for route schemas (not plain JSON Schema)
- Use the correct import for context (Fastify vs standalone)
- Do not use
@sinclair/typeboxdirectly