OpenAPI - ElysiaJS | ElysiaJS

ID: 2124https://elysiajs.com/patterns/openapi.html
Source

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:

  1. Specify your Elysia root file (if not specified, Elysia will use src/index.ts), and export an instance.
  2. 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)