OpenAPI
Elysia has first‑class support and follows the OpenAPI schema by default.
Elysia can automatically generate an API documentation page by using an OpenAPI plugin.
Installing the plugin
bun add @elysiajs/openapi
Registering the plugin
import { Elysia } from 'elysia'
import { openapi } from '@elysiajs/openapi'
new Elysia()
.use(openapi())
By default, Elysia uses OpenAPI V3 and Scalar UI.
For OpenAPI plugin configuration, see the OpenAPI plugin page.
OpenAPI from types
This is optional, but we highly recommend it for a much better documentation experience.
By default, Elysia relies on runtime schema to generate OpenAPI documentation.
However, you can also generate OpenAPI documentation from types by using a generator from the OpenAPI plugin as follows:
- Specify your Elysia root file (if not specified, Elysia will use
src/index.ts), and export an instance. - Import a generator and provide a file path from project root to the type generator.
import { Elysia, t } from 'elysia'
import { openapi, fromTypes } from '@elysiajs/openapi'
export const app = new Elysia()
.use(
openapi({
references: fromTypes()
})
)
.get('/', { test: 'hello' as const })
.post(
'/json',
({ body, status }) => body,
{
body: t.Object({
hello: t.String()
})
}
)
.listen(3000)
Elysia will attempt to generate OpenAPI documentation by reading the type of an exported instance to generate OpenAPI documentation.
This will co‑exist with the runtime schema, and the runtime schema will take precedence over the type definition.
Production
In production environment, it’s likely that you might compile Elysia to a single executable with Bun or bundle into a single JavaScript file.
It’s recommended that you pre‑generate the declaration file (.d.ts) to provide type declaration to the generator.
import { Elysia, t } from 'elysia'
import { openapi, fromTypes } from '@elysiajs/openapi'
const app = new Elysia()
.use(
openapi({
references: fromTypes(
process.env.NODE_ENV === 'production'
? 'dist/index.d.ts'
: 'src/index.ts'
)
})
)
.get('/', { test: 'hello' as const })
.post(
'/json',
({ body, status }) => body,
{
body: t.Object({
hello: t.String()
})
}
)
.listen(3000)
Having issues with type generation?
Caveats: Root path
As it’s unreliable to guess the root of the project, it’s recommended to provide the path to the project root to allow the generator to run correctly, especially when using a monorepo.
import { Elysia, t } from 'elysia'
import { openapi, fromTypes } from '@elysiajs/openapi'
export const app = new Elysia()
.use(
openapi({
references: fromTypes(
'src/index.ts',
{
projectRoot: path.join('..', import.meta.dir)
}
)
})
)
.get('/', { test: 'hello' as const })
.post(
'/json',
({ body, status }) => body,
{
body: t.Object({
hello: t.String()
})
}
)
.listen(3000)
Custom tsconfig.json
If you have multiple tsconfig.json files, it’s important that you specify the correct tsconfig.json file to be used for type generation.
import { Elysia, t } from 'elysia'
import { openapi, fromTypes } from '@elysiajs/openapi'
export const app = new Elysia()
.use(
openapi({
references: fromTypes(
'src/index.ts',
{
// This is reference from root of the project
tsconfigPath: 'tsconfig.dts.json'
}
)
})
)
.get('/', { test: 'hello' as const })
.post(
'/json',
({ body, status }) => body,
{
body: t.Object({
hello: t.String()
})
}
)
.listen(3000)
Standard Schema with OpenAPI
Elysia will try to use a native method from each schema to convert to OpenAPI schema.
However, if the schema doesn’t provide a native method, you can provide a custom schema to OpenAPI by providing a mapJsonSchema as follows:
Zod OpenAPI
As Zod doesn’t have a toJSONSchema method on the schema, we need to provide a custom mapper to convert Zod schema to OpenAPI schema.
import openapi from '@elysiajs/openapi'
import * as z from 'zod'
openapi({
mapJsonSchema: {
zod: z.toJSONSchema
}
})
import openapi from '@elysiajs/openapi'
import { zodToJsonSchema } from 'zod-to-json-schema'
openapi({
mapJsonSchema: {
zod: zodToJsonSchema
}
})
Describing route
We can add route information by providing a schema type.
However, sometimes defining only a type does not make it clear what the route might do. You can use detail fields to explicitly describe the route.
import { Elysia, t } from 'elysia'
import { openapi } from '@elysiajs/openapi'
new Elysia()
.use(openapi())
// …
(Additional examples and usage of detail can be found in the full Markdown source: openapi.md)