Defining Schema
Define schema for your API endpoints
Schemas are the foundation of type-safe API documentation. Define validation schemas using your preferred library—VineJS, TypeBox, or Zod—and Adonis Open Swagger automatically converts them to OpenAPI-compatible documentation.
What are Schemas?
Schemas define the structure, types, and validation rules for your API's data. They serve three critical purposes:
- Validation: Ensure incoming data meets your requirements
- Documentation: Automatically generate accurate API docs
- Type Safety: Power end-to-end TypeScript types for frontend clients
With Adonis Open Swagger, you write schemas once and get validation, documentation, and type safety for free.
Choosing a Schema Library
Adonis Open Swagger supports three popular schema validation libraries. Choose the one that fits your project best:
| Library | Best For | Strengths |
|---|---|---|
| VineJS | AdonisJS projects | Official AdonisJS validator, seamless integration, excellent error messages |
| TypeBox | Performance-critical apps | Fastest runtime validation, JSON Schema native, minimal overhead |
| Zod | TypeScript-first teams | Best developer experience, powerful type inference, rich ecosystem |
Configure your preferred validator in config/swagger.ts:
export default defineConfig({
validator: "vinejs", // 'vinejs' | 'typebox' | 'zod'
});Creating Schemas
Let's create a user schema using all three libraries. We'll define a schema for creating a new user with full_name, email, and password fields.
File Structure
Create a dedicated file for your schemas to keep them organized and reusable:
app/
schemas/
user.schema.ts ← Define your schemas here
controllers/
users_controller.tsVineJS Schema
VineJS is AdonisJS's official validation library, designed specifically for the framework.
import vine from "@vinejs/vine";
export const createUserSchema = vine.object({
full_name: vine.string(),
email: vine.string().email(),
password: vine.string(),
});Key Features:
- Native AdonisJS integration
- Excellent error messages out of the box
- Built-in validators for common patterns
- Async validation support
Learn More: VineJS Documentation
TypeBox Schema
TypeBox provides JSON Schema-based validation with excellent performance.
import { Type } from "@sinclair/typebox";
export const createUserSchema = Type.Object({
full_name: Type.String(),
email: Type.String({ format: "email" }),
password: Type.String(),
});Key Features:
- Fastest runtime validation
- Native JSON Schema output
- Minimal bundle size
- Perfect for high-performance APIs
Learn More: TypeBox Documentation
Zod Schema
Zod is a TypeScript-first schema validation library with excellent developer experience.
import { z } from "zod";
export const createUserSchema = z.object({
full_name: z.string(),
email: z.string().email(),
password: z.string(),
});Key Features:
- Best-in-class TypeScript inference
- Intuitive, chainable API
- Rich ecosystem of extensions
- Excellent error messages
Learn More: Zod Documentation
Using Schemas with Decorators
Once you've defined your schemas, use them with Adonis Open Swagger decorators to document your API endpoints.
Request Body Example
import { createUserSchema } from "#schemas/user.schema";
export default class UsersController {
@SwaggerInfo({
summary: "Create a new user",
tags: ["Users"],
})
@SwaggerRequestBody("User creation data", createUserSchema)
@SwaggerResponse(
201,
"User created successfully",
Type.Object({
id: Type.String(),
full_name: Type.String(),
email: Type.String(),
})
)
async create({ request, response }: HttpContext) {
// Your implementation
}
}Query Parameters Example
@SwaggerParam(
{
name: 'page',
location: 'query',
description: 'Page number for pagination',
},
Type.Number({ minimum: 1 }),
false // optional
)Path Parameters Example
@SwaggerParam(
{
name: 'id',
location: 'path',
description: 'User ID',
},
Type.String({ format: 'uuid' }),
true // required
)Advanced Schema Patterns
Nested Objects
Define complex, nested data structures:
const userWithAddressSchema = vine.object({
full_name: vine.string(),
email: vine.string().email(),
address: vine.object({
street: vine.string(),
city: vine.string(),
country: vine.string(),
postal_code: vine.string(),
}),
});Arrays
Define arrays of items with validation:
const userListSchema = vine.object({
users: vine.array(
vine.object({
id: vine.string(),
full_name: vine.string(),
email: vine.string().email(),
})
),
});Optional Fields
Make fields optional with proper typing:
const updateUserSchema = vine.object({
full_name: vine.string().optional(),
email: vine.string().email().optional(),
bio: vine.string().optional(),
});Enums and Unions
Restrict values to specific options:
const userStatusSchema = vine.object({
status: vine.enum(["active", "inactive", "suspended"]),
role: vine.enum(["user", "admin", "moderator"]),
});Schema Reusability
Composing Schemas
Build complex schemas from smaller, reusable pieces:
// Base schemas
const addressSchema = vine.object({
street: vine.string(),
city: vine.string(),
country: vine.string(),
});
const contactSchema = vine.object({
phone: vine.string(),
email: vine.string().email(),
});
// Composed schema
const userProfileSchema = vine.object({
full_name: vine.string(),
address: addressSchema,
contact: contactSchema,
});Extending Schemas
Extend existing schemas with additional fields:
const baseUserSchema = vine.object({
full_name: vine.string(),
email: vine.string().email(),
});
// VineJS doesn't have built-in extend, use composition
const adminUserSchema = vine.object({
...baseUserSchema.getProperties(),
role: vine.string(),
permissions: vine.array(vine.string()),
});Best Practices
1. Organize Schemas by Domain
Group related schemas together in domain-specific files:
app/schemas/
user.schema.ts ← User-related schemas
product.schema.ts ← Product-related schemas
order.schema.ts ← Order-related schemas2. Use Descriptive Names
Name your schemas clearly to indicate their purpose:
// ✅ Good
export const createUserSchema = ...
export const updateUserSchema = ...
export const userResponseSchema = ...
// ❌ Bad
export const schema1 = ...
export const userSchema = ... // Too generic3. Validate Everything
Always validate all inputs—request bodies, query parameters, path parameters, and headers:
@SwaggerParam({ name: 'id', location: 'path' }, Type.String())
@SwaggerRequestBody('User data', createUserSchema)
@SwaggerHeader({ name: 'X-API-Key' }, Type.String(), true)4. Document with Descriptions
Add descriptions to your schema fields for better documentation:
// VineJS uses meta for descriptions
const userSchema = vine.object({
email: vine.string().email(),
});5. Keep Schemas DRY
Reuse common patterns and avoid duplication:
// Define once
const emailField = Type.String({ format: "email" });
const uuidField = Type.String({ format: "uuid" });
// Reuse everywhere
const userSchema = Type.Object({
id: uuidField,
email: emailField,
});
const adminSchema = Type.Object({
id: uuidField,
email: emailField,
role: Type.String(),
});Common Validation Patterns
Email Validation
email: vine.string().email();Password Validation
password: vine.string().minLength(8).maxLength(100);UUID Validation
id: vine.string().uuid();Date Validation
created_at: vine.date();Pagination Schema
const paginationSchema = vine.object({
page: vine.number().min(1),
limit: vine.number().min(1).max(100),
});Last updated on