npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@kontract/adonis

v0.4.0

Published

AdonisJS adapter for kontract

Readme

@kontract/adonis

AdonisJS adapter for kontract. Provides route registration, AJV validation, Lucid ORM serializers, and OpenAPI spec generation for AdonisJS v6.

Features

  • Automatic route registration - Register decorated endpoints with AdonisJS router
  • AJV validation - TypeBox schema validation with type coercion and format support
  • Lucid serializers - Automatic serialization of Lucid models and paginators
  • OpenAPI generation - Build OpenAPI 3.0/3.1 specs from decorators
  • Full TypeBox support - Validate requests against TypeBox schemas

Installation

npm install @kontract/adonis @sinclair/typebox ajv ajv-formats

Note: This package depends on kontract (the core library), which will be installed automatically. You can import from either package - the adapter re-exports all commonly used items from the core.

Quick Start

1. Define Your Controller

// app/controllers/books_controller.ts
import { Api, Endpoint, ok, apiError } from '@kontract/adonis'
import { Type, Static } from '@sinclair/typebox'
import Book from '#models/book'

const BookSchema = Type.Object({
  id: Type.String({ format: 'uuid' }),
  title: Type.String(),
  author: Type.String(),
}, { $id: 'Book' })

const CreateBookRequest = Type.Object({
  title: Type.String({ minLength: 1 }),
  author: Type.String({ minLength: 1 }),
}, { $id: 'CreateBookRequest' })

@Api({ tag: 'Books', description: 'Book management' })
export default class BooksController {
  @Endpoint('GET /api/v1/books', {
    summary: 'List all books',
    responses: {
      200: { schema: Type.Array(BookSchema) },
    },
  })
  async index() {
    const books = await Book.all()
    return ok(Type.Array(BookSchema), books.map(b => b.toResponse()))
  }

  @Endpoint('POST /api/v1/books', {
    summary: 'Create a book',
    auth: 'required',
    body: CreateBookRequest,
    responses: {
      201: { schema: BookSchema },
      422: null,
    },
  })
  async store(
    ctx: HttpContext,
    body: Static<typeof CreateBookRequest>
  ) {
    const book = await Book.create(body)
    return ok(BookSchema, book.toResponse())
  }
}

2. Register Routes

// start/routes.ts
import router from '@adonisjs/core/services/router'
import { registerDecoratorRoutes, validate } from '@kontract/adonis'

// Import controllers to trigger decorator registration
import '#controllers/books_controller'
import '#controllers/users_controller'

// Register all decorated routes
registerDecoratorRoutes(router, { validate })

3. Generate OpenAPI Spec

// commands/generate_openapi.ts
import { OpenApiBuilder } from '@kontract/adonis'

// Import controllers
import '#controllers/books_controller'
import '#controllers/users_controller'

const builder = new OpenApiBuilder({
  title: 'My API',
  description: 'API documentation',
  version: '1.0.0',
  servers: [
    { url: 'http://localhost:3333', description: 'Development' },
  ],
})

const spec = builder.build()
console.log(JSON.stringify(spec, null, 2))

Runtime Validation

The packages support runtime validation for both requests and responses.

Request Validation (Automatic)

Request validation happens automatically when you use registerDecoratorRoutes(). The route registrar validates body, query, and params against the TypeBox schemas defined in your @Endpoint decorators.

@Endpoint('POST /api/v1/books', {
  body: CreateBookRequest,    // Validated at runtime
  query: PaginationQuery,     // Validated at runtime
  params: BookIdParams,       // Validated at runtime
  responses: { 201: BookSchema },
})
async store(ctx, body, query, params) {
  // body, query, params are already validated and typed
}

If validation fails, an AjvValidationError is thrown with status 422.

Response Validation (Optional)

Response validation catches contract violations during development. Enable it by calling defineConfig() at application startup:

// start/kernel.ts or providers/app_provider.ts
import { defineConfig } from 'kontract'
import { createAjvValidator } from '@kontract/adonis'

const validator = createAjvValidator()

defineConfig({
  openapi: {
    info: { title: 'My API', version: '1.0.0' },
  },
  runtime: {
    validateResponses: process.env.NODE_ENV !== 'production',
  },
  validator: (schema, data) => validator.validate(schema, data),
})

When enabled, response helpers like ok(), created(), etc. will validate the response data against the schema and throw ResponseValidationError if it doesn't match.

// This will throw in development if user doesn't match UserSchema
return ok(UserSchema, user)

Route Registration

registerDecoratorRoutes(router, options)

Registers all routes defined via @Endpoint decorators with the AdonisJS router.

import router from '@adonisjs/core/services/router'
import { registerDecoratorRoutes, validate } from '@kontract/adonis'

registerDecoratorRoutes(router, {
  validate,  // AJV validation function
})

The registrar:

  • Creates routes for each @Endpoint decorator
  • Validates request body, query, and params against TypeBox schemas
  • Handles authentication based on auth option
  • Calls the controller method with validated data
  • Processes API responses (status codes, JSON, binary)

Controller Method Signature

Controller methods receive validated data as separate parameters:

async store(
  ctx: HttpContext,        // AdonisJS context
  body: BodyType,          // Validated request body
  query: QueryType,        // Validated query parameters
  params: ParamsType       // Validated path parameters
) {
  // body, query, params are already validated
}

Validation

validate(schema, data)

Validates data against a TypeBox schema. Throws AjvValidationError on failure.

import { validate } from '@kontract/adonis'
import { Type } from '@sinclair/typebox'

const schema = Type.Object({
  email: Type.String({ format: 'email' }),
  age: Type.Integer({ minimum: 0 }),
})

try {
  const data = validate(schema, { email: '[email protected]', age: 25 })
  // data is typed and validated
} catch (error) {
  if (error instanceof AjvValidationError) {
    console.log(error.errors)
    // [{ field: 'email', message: 'must match format "email"' }]
  }
}

createAjvValidator(options?)

Create a customized AJV validator instance.

import { createAjvValidator } from '@kontract/adonis'

const validator = createAjvValidator({
  coerceTypes: true,       // Convert strings to numbers, etc.
  removeAdditional: true,  // Strip unknown properties
  useDefaults: true,       // Apply default values
  formats: {
    'custom-format': (value) => /^[A-Z]+$/.test(value),
  },
  ajvOptions: {
    // Additional AJV options
  },
})

// Validate (returns errors array)
const errors = validator.validate(schema, data)

// Validate or throw
validator.validateOrThrow(schema, data)

// Pre-compile for performance
const compiled = validator.compile(schema)
compiled.validate(data)
compiled.validateOrThrow(data)

AjvValidationError

Error thrown when validation fails.

import { AjvValidationError } from '@kontract/adonis'

try {
  validate(schema, data)
} catch (error) {
  if (error instanceof AjvValidationError) {
    error.status   // 422
    error.code     // 'E_VALIDATION_ERROR'
    error.errors   // [{ field: 'email', message: '...' }]
  }
}

Serializers

Built-in serializers for Lucid models and paginators.

Type Guards

import {
  isLucidModel,    // Has serialize() method
  isTypedModel,    // Has toResponse() method
  isPaginator,     // Lucid paginator object
  hasSerialize,    // Generic serialize check
} from '@kontract/adonis'

if (isTypedModel(data)) {
  return data.toResponse()
}

if (isLucidModel(data)) {
  return data.serialize()
}

if (isPaginator(data)) {
  // { data: [...], meta: { total, perPage, currentPage, ... } }
}

Serializer Registry

Serializers are ordered by priority (higher = checked first):

| Serializer | Priority | Checks | |------------|----------|--------| | paginatorSerializer | 150 | isPaginator() | | typedModelSerializer | 100 | isTypedModel() | | lucidModelSerializer | 50 | isLucidModel() | | serializableSerializer | 25 | hasSerialize() |

import { lucidSerializers } from '@kontract/adonis'

// All serializers in priority order
lucidSerializers

OpenAPI Builder

OpenApiBuilder

Generates OpenAPI specifications from decorated controllers.

import { OpenApiBuilder } from '@kontract/adonis'

const builder = new OpenApiBuilder({
  title: 'My API',
  description: 'API documentation',
  version: '1.0.0',
  servers: [
    { url: 'https://api.example.com', description: 'Production' },
    { url: 'http://localhost:3333', description: 'Development' },
  ],
  openapiVersion: '3.1.0',  // or '3.0.3'
  securityScheme: {
    name: 'BearerAuth',
    type: 'http',
    scheme: 'bearer',
    bearerFormat: 'JWT',
    description: 'JWT access token',
  },
})

const spec = builder.build()

OpenApiBuilderOptions

interface OpenApiBuilderOptions {
  title: string
  description: string
  version: string
  servers: Array<{ url: string; description: string }>
  openapiVersion?: '3.0.3' | '3.1.0'  // default: '3.1.0'
  securityScheme?: {
    name: string
    type: 'http' | 'apiKey' | 'oauth2'
    scheme?: string
    bearerFormat?: string
    description?: string
  }
}

Generated Features

The builder automatically:

  • Collects tags from @Api decorators
  • Converts paths (:id to {id})
  • Generates operationIds from method names
  • Adds security for auth: 'required' endpoints
  • Adds 401 response for authenticated endpoints
  • Registers schemas in components
  • Handles file uploads as multipart/form-data

OperationId Generation

For standard CRUD method names, operationIds are auto-generated:

| Method | Path | Generated operationId | |--------|------|----------------------| | index | GET /users | listUsers | | show | GET /users/:id | getUser | | store | POST /users | createUser | | update | PUT /users/:id | updateUser | | destroy | DELETE /users/:id | deleteUser |

Custom method names use the method name as operationId.

Re-exports

For convenience, the adapter re-exports common items from the core package:

import {
  // Decorators
  Api,
  Endpoint,

  // Response helpers
  ok,
  created,
  accepted,
  noContent,
  badRequest,
  unauthorized,
  forbidden,
  notFound,
  conflict,
  unprocessableEntity,
  tooManyRequests,
  internalServerError,
  serviceUnavailable,
  binary,
  apiError,

  // Configuration
  defineConfig,
  getConfig,

  // Types
  type ApiOptions,
  type EndpointOptions,
  type ApiResponse,
  type BinaryResponse,
  type EndpointMetadata,
  type ApiMetadata,
} from '@kontract/adonis'

Utilities

stripNestedIds(schema)

Removes $id from nested schemas to prevent AJV conflicts when the same schema is used multiple times.

import { stripNestedIds } from '@kontract/adonis'

const cleanedSchema = stripNestedIds(schema)

getDefaultValidator()

Get or create the singleton AJV validator instance.

import { getDefaultValidator } from '@kontract/adonis'

const validator = getDefaultValidator()

resetDefaultValidator()

Reset the default validator (useful for testing).

import { resetDefaultValidator } from '@kontract/adonis'

beforeEach(() => {
  resetDefaultValidator()
})

Integration Example

Complete example with AdonisJS:

// start/routes.ts
import router from '@adonisjs/core/services/router'
import { registerDecoratorRoutes, validate, OpenApiBuilder } from '@kontract/adonis'

// Import all controllers
const controllers = import.meta.glob('../app/controllers/**/*.ts', { eager: true })

// Register decorator-based routes
registerDecoratorRoutes(router, { validate })

// Serve OpenAPI spec
router.get('/docs/json', async ({ response }) => {
  const builder = new OpenApiBuilder({
    title: 'My API',
    description: 'API documentation',
    version: '1.0.0',
    servers: [{ url: 'http://localhost:3333', description: 'Development' }],
  })

  return response.json(builder.build())
})

Peer Dependencies

  • @adonisjs/core ^6.0.0
  • @sinclair/typebox >=0.32.0
  • ajv ^8.0.0 (optional, required for validation)
  • @adonisjs/lucid ^21.0.0 (optional, required for Lucid serializers)

License

MIT