Using transformers
Generate OpenAPI response schemas from AdonisJS Transformers. Automatically document serialized API responses with support for items, collections, and pagination.
Experimental Feature
This feature is experimental and is not considered part of the stable API. It is likely to undergo implementation changes in future releases.
If you are using AdonisJS Transformers to serialize your API responses, you can automatically generate OpenAPI schemas from your transformers.
Transformers pick, reshape, and nest your model properties. The TransformerTypeLoader reads that same structure so your documentation always matches the actual response shape.
Setup
Before getting started, make sure that you have HTTP Transformers already configured in your project. You can read more on the official documentation.
Update the serializer
You need to update your existing serializer to extend OpenAPISerializer. This serializer defines how your API wraps data and structures pagination metadata in your OpenAPI document.
import {
,
,
,
} from '@foadonis/openapi/transformers'
import { type } from '@adonisjs/lucid/types/querybuilder'
export default class extends <{
: 'data'
:
}> {
: 'data' = 'data'
(: unknown): {
if (!this.()) {
throw new ('Invalid pagination metadata')
}
return
}
/**
* Defines the OpenAPI schema for the pagination metadata.
*/
(): <> {
return
}
}
export const = new () // Make sure to export the serializerOpenAPISerializer extends BaseSerializer from AdonisJS, so you can use the same serializer for
both runtime serialization and OpenAPI schema generation.
If your API does not wrap responses in a data key, set wrap to undefined:
export default class ApiSerializer extends OpenAPISerializer<{
PaginationMetaData: SimplePaginatorMetaKeys
}> {
wrap: undefined
// ...
}Register the type loader
Add the TransformerTypeLoader to your OpenAPI configuration:
import { defineConfig } from '@foadonis/openapi'
import { TransformerTypeLoader } from '@foadonis/openapi/transformers'
import { serializer } from '#providers/api_provider'
export default defineConfig({
ui: 'scalar',
document: {
info: {
title: 'My API',
version: '1.0.0',
},
},
loaders: [TransformerTypeLoader({ serializer })],
})Usage
Define your transformers
Create your transformers as usual. The schema generator reads the transformer structure to determine which fields and relationships are included in the response.
import { BaseTransformer } from '@adonisjs/core/transformers'
import Post from '#models/post'
import UserTransformer from '#transformers/user_transformer'
export default class PostTransformer extends BaseTransformer<Post> {
toObject() {
return {
...this.pick(this.resource, ['id', 'title']),
user: UserTransformer.transform(this.resource.user),
}
}
}Use transformers as response types
Use the static .schema() method on your transformer to generate the response type for your OpenAPI documentation:
import { ApiResponse } from '@foadonis/openapi/decorators'
import PostTransformer from '#transformers/post_transformer'
import Post from '#models/post'
export default class PostsController {
@ApiResponse({ type: PostTransformer.schema(Post) })
show() {
// ...
}
}The .schema() method accepts the following arguments:
| Argument | Description |
|---|---|
Model | The model class (or [Model] for a collection) |
paginated | Set to true to generate a paginated response schema |
Single item
Returns the schema for a single transformed item, optionally wrapped in the serializer's wrap key.
@ApiResponse({ type: PostTransformer.schema(Post) })Collection
Wrap the model in an array to generate a collection schema:
@ApiResponse({ type: PostTransformer.schema([Post]) })Paginated
Pass true as the second argument to generate a paginated response schema with metadata:
@ApiResponse({ type: PostTransformer.schema(Post, true) })This generates a schema that includes both the data array and the pagination metadata defined in your serializer.
Using Variants
You can use the same API as usual to use variants:
@ApiResponse({ type: PostTransformer.schema(Post).useVariant('toDetailed') })This generates a schema using the toDetailed method of your transformer and store it as PostDetailed.
to and for.Generated schemas
The transformer type loader automatically generates component schemas based on your transformer structure. For example, given a PostTransformer that picks id and title and includes a UserTransformer, the following schemas are generated:
With data wrapper:
{
"type": "object",
"properties": {
"data": { "$ref": "#/components/schemas/PostObject" }
},
"required": ["data"]
}Without data wrapper:
{ "$ref": "#/components/schemas/PostObject" }The PostObject component schema only includes the fields selected by the transformer:
{
"type": "object",
"properties": {
"id": { "type": "number" },
"title": { "type": "string" },
"user": { "$ref": "#/components/schemas/UserObject" }
},
"required": ["id", "title", "user"]
}Limitations
HttpContext is not available
As OpenAPI schema is generated without any scope and should be idempotent the HttpContext is not available.
Schema cannot be updated
The OpenAPISerializer works by reusing the schema generated for the resource meaning that your transformer should not perform any logic.
class User {
@ApiProperty()
declare firstName: string
@ApiProperty()
declare lastName: string
}
class UserTransformer extends BaseTransformer<User> {
toObject() {
return {
...this.pick('firstName', 'lastName'),
fullName: `${this.firstName} ${this.lastName}`, // This will not work
}
}
}Instead you should define a getter on your class to define the schema:
class User {
@ApiProperty()
declare firstName: string
@ApiProperty()
declare lastName: string
@ApiProperty()
get fullName(): string {
return `${this.firstName} ${this.lastName}`
}
}
class UserTransformer extends BaseTransformer<User> {
toObject() {
return {
...this.pick('firstName', 'lastName', 'fullName'),
}
}
}