Adonis Open Swagger

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:

  1. Validation: Ensure incoming data meets your requirements
  2. Documentation: Automatically generate accurate API docs
  3. 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:

LibraryBest ForStrengths
VineJSAdonisJS projectsOfficial AdonisJS validator, seamless integration, excellent error messages
TypeBoxPerformance-critical appsFastest runtime validation, JSON Schema native, minimal overhead
ZodTypeScript-first teamsBest developer experience, powerful type inference, rich ecosystem

Configure your preferred validator in config/swagger.ts:

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.ts

VineJS Schema

VineJS is AdonisJS's official validation library, designed specifically for the framework.

app/schemas/user.schema.ts
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.

app/schemas/user.schema.ts
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.

app/schemas/user.schema.ts
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

app/controllers/users_controller.ts
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 schemas

2. 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 generic

3. 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