Speakeasy Logo
Skip to Content
OpenAPI HubFrameworksElysia

How to generate an OpenAPI document with ElysiaJS

This guide walks you through generating an OpenAPI document for an ElysiaJS  API and using Speakeasy to create an SDK based on the generated document.

Here’s what we’ll do:

  1. Add a Swagger endpoint, which uses Scalar UI, to an Elysia Bun app using the Elysia Swagger plugin.
  2. Improve the OpenAPI document to prepare it for code generation.
  3. Convert the JSON OpenAPI document to YAML.
  4. Use the Speakeasy CLI to generate an SDK based on the OpenAPI document.
  5. Add a Speakeasy OpenAPI extension to improve the generated SDK.

We’ll also take a look at how you can use the generated SDK.

Your Elysia project might not be as simple as our example app, but the steps below should translate well to any Elysia project.

The OpenAPI generation pipeline

The Elysia Swagger plugin  generates a Swagger API documentation endpoint. By default, Elysia uses the OpenAPI Specification and Scalar UI , an open-source interactive document UI for OpenAPI.

We’ll first add the Swagger plugin to an existing Elysia app.

Then, we’ll improve the plugin-generated OpenAPI document according to Speakeasy best practices . The quality of an OpenAPI document determines the quality of the SDKs and documentation it’s used to create.

Next, we’ll use Speakeasy to generate an SDK based on the OpenAPI document.

Finally, we’ll use a simplified example to demonstrate how to use the generated SDK and how to add SDK creation to a CI/CD pipeline so that Speakeasy automatically generates fresh SDKs whenever your Elysia API changes in the future.

Requirements

This guide assumes that you have an existing Elysia app and basic familiarity with Elysia.

The following should be installed on your machine:

  • Bun : The Node.js alternative that Elysia is built on.
  • Speakeasy CLI : The tool you’ll use to generate an SDK from the OpenAPI document.

Adding the Swagger plugin to an Elysia project

The Elysia Swagger plugin automatically generates an API documentation page for your server.

First, install the Swagger plugin:

Import the plugin, then register it by passing in an instance of swagger to the use() method and chaining the use() method to the Elysia instance:

In Elysia, a plugin  is a reusable component. In fact, everything in Elysia is a component, including Elysia instances, plugins, routers, stores, and more. Components split apps into small pieces, making it easier to add, remove, or modify app features. It’s important that we use method chaining  for type inference in our Elysia code. In the above code block, we use the onError lifecycle method to catch any error that’s thrown on the server.

Run the Bun development server with bun run dev and open http://localhost:3000/swagger to see the Scalar UI with five API endpoints:

Scalar UI

The API routes are listed in the navigation pane on the left. Click /users/ GET to navigate to the section for the /users/ GET request API endpoint:

GET request API endpoint

Each section shows information about an API endpoint, such as its path parameters, body, and responses.

The code block on the right shows an example curl request. Click the Shell cURL dropdown menu to change the language or library used in the example request:

Change example request dropdown menu

Click the Test Request button to open an API client that lets you test your API endpoints. Then, click Send to test the request:

Scalar API client

In the response, you should get an array containing one user.

The user data in the Elysia server is stored temporarily in a singleton Users class :

This class is added to the Elysia instance using the decorate method:

This adds the Users class to the context  that contains information for each request. You can access the context in route handlers.

Viewing the OpenAPI document and modifying its root object

Open http://localhost:3000/swagger/json to view the OpenAPI document in JSON format:

Add the following TypeScript configuration option to your tsconfig.json file to enable JSON file imports:

Add the following configuration object to the swagger plugin:

This configures the root document object  of the OpenAPI document.

The info object is a required property used to add metadata about the API. The externalDocs object lets you extend your documentation by referencing an external resource.

Note that the API operation for each path has an operationId value that’s named by combining the HTTP request type and the name of the path. The value can be modified using the detail field in a route; however, we won’t modify it in this guide, as Elysia produces consistently named, human-readable operationId values.

The operationId is the identifier for an operation. It is case sensitive and must be unique within the document. The Speakeasy SDK, which we’ll use later in this guide, uses it during code generation to name the method it generates for the operation.

OpenAPI Specification versions supported by Elysia and Speakeasy

Speakeasy currently supports the OpenAPI Specification versions 3.0.x and 3.1.x and recommends you use version 3.1, as it’s fully compatible with JSON Schema , which gives you access to a large ecosystem of tools and libraries.

However, we use OpenAPI Specification version 3.0.3 in this guide, as it is the version Elysia supports.

To check which version you are using, open http://localhost:3000/swagger/json and see the OpenAPI Specification version in the root document object.

Adding example data to a data model and the POST request body

The API routes in the Scalar UI don’t have example values for the path parameters, requests, or responses. The user model doesn’t have example values either. It’s important to add examples to make your API more user-friendly.

Let’s start by adding example values to the userInfo model:

The Elysia schema builder, t, gives compile-time and runtime type safety. It also registers the model as a reusable OpenAPI Components Object  schema, which you can see at the bottom of your OpenAPI document:

You’ll also see the example values in the user model in the Scalar UI:

Example values in user model

The userInfo schema is used in the post() and patch() routes. Elysia HTTP request methods accept three arguments: the path, the function used to respond to the client, and the hook used to define extra metadata. Add an example to the body in the hook object of the post() route:

If you look at your OpenAPI document now, you’ll see that the content of the POST request body has three possible types: application/json, multipart/form-data, or text/plain. To limit it to application/json, set the type in the hook object of the post() route:

Adding extra information to a route using the detail field

The detail field is used to define a route for the OpenAPI document. It extends the OpenAPI Operation Object , which describes an API operation within a path.

Add the following detail field to the hook object of the post() route:

Add the following responses property to the detail object:

The Responses Object  is used to list the possible responses returned from the POST request. There is one possible response listed - a successful response. This response has a schema that defines the content of the response. The id schema is referenced using $ref, the reference identifier that specifies the URI location of the value being referenced. Let’s define this id model.

Add the following idObject model to the users.ts file, below the userInfo model:

Create a reference model  for the model:

A reference model lets us reuse a model by referencing its name.

It’s also good practice to add possible error responses. Add the following 500 response to the responses property:

Add the definition for the errorResponse model below the idObject model:

Create a reference model for the errorResponse model:

You’ll now see example responses for the Create user POST route in Scalar:

Example POST request responses

Adding OpenAPI tags to routes

We recommend adding tags to all your Elysia routes. This allows you to group the routes according to tag in the generated SDK code and documentation.

Adding OpenAPI tags to routes in Elysia

To add OpenAPI tags to a route, use the tags property to pass in an array of tags in the hook object of the post() route:

Adding tags to the root OpenAPI document object and adding metadata to tags

Add the following tags array to the configuration object of the swagger plugin:

This adds a tags array to the root OpenAPI document object. In the above code, we add metadata to the tag by passing in a Tag Object  (instead of a string) to the tag array item.

After adding tags to your routes, you’ll see that they are organized by tags in Scalar:

Routes grouped by tag

Adding example data, extra information, and tags to the other API routes

Let’s improve the other API route operations like we improved the Create user route.

Replace the Get all users route with the following lines of code:

Replace the Get user route with the following lines of code:

Replace the Delete user route with the following lines of code:

Add the definition for the successResponse model below the errorResponse model:

Create a reference model for the successResponse model:

Replace the Update user route with the following lines of code:

Adding a list of servers to the Elysia OpenAPI document

When validating an OpenAPI document, Speakeasy expects a list of servers  at the root of the document.

Add a server by adding a servers property to the configuration object of the swagger plugin:

You can add multiple servers to define different environments or versions. This is useful for separating production and testing environments.

Adding retries to your SDK with x-speakeasy-retries

OpenAPI document extensions  allow us to add vendor-specific functionality to an OpenAPI document.

  • Extension fields must be prefixed with x-.
  • Speakeasy uses extensions that start with x-speakeasy-.

Speakeasy gives you fine-tuned control over the Speakeasy SDK via its range of Speakeasy extensions , which you can use to modify retries, pagination, error handling, and other advanced SDK features.

Let’s add a Speakeasy extension that adds retries to requests from Speakeasy SDKs by adding a top-level x-speakeasy-retries schema to the OpenAPI document. We can also override the retry strategy per operation.

Adding global retries

Apply the Speakeasy retries extension globally by adding the following 'x-speakeasy-retries' property to the configuration object of the swagger plugin:

Adding retries per method

You can create a unique retry strategy for a single route by adding a 'x-speakeasy-retries' property to the route’s hook object:

Creating an SDK based on your OpenAPI document

Before creating an SDK, we need to save the Elysia Swagger plugin-generated OpenAPI document to a file. OpenAPI files are written as JSON or YAML; we’ll save it as a YAML file, as it’s easier to read.

Saving the OpenAPI document to a YAML file using a Bun script

Let’s create a script that uses the JS-YAML  library to convert the JSON OpenAPI document to a YAML string.

Install the library and its types:

Create a script called generateOpenAPIYamlFile.ts in the src folder and add the following lines of code to it:

This script fetches the JSON OpenAPI document from the Scalar OpenAPI document endpoint, converts it to a YAML string, and then saves it as a file.

Add the following script to your package.json file:

Run the development server and then run the generate:openapi script using the following command:

This generates an openapi.yaml file in your root folder.

Linting the OpenAPI document with Speakeasy

The Speakeasy CLI has an OpenAPI linting  command that checks the OpenAPI document for errors and style issues.

Run the linting command:

A lint report will be displayed in the terminal, showing errors, warnings, and hints:

Speakeasy lint report

The Speakeasy linter uses the speakeasy-recommended ruleset by default, but you can configure  a custom ruleset.

Speakeasy also has a VS Code extension , which you can use to help you validate your OpenAPI documents for the creation of production-grade SDKs.

Creating an SDK from the Speakeasy CLI

We’ll use the quickstart command for a guided SDK setup.

Run the command using the Speakeasy CLI:

Following the prompts, provide the OpenAPI document location, name the SDK SDK, and select TypeScript as the SDK language.

In the terminal, you’ll see the steps taken by Speakeasy to generate the SDK.

Speakeasy validates  the OpenAPI document to check that it’s ready for code generation. Validation issues will be printed in the terminal. The generated SDK is saved as a folder in your project.

If you get ESLint styling errors, run the speakeasy quickstart command from outside your project.

Adding SDK generation to your GitHub Actions

The Speakeasy sdk-generation-action repository provides workflows that integrate the Speakeasy CLI into CI/CD pipelines and automatically regenerate client SDKs when the reference OpenAPI document changes.

You can configure Speakeasy to push a new branch to your SDK repositories automatically when the OpenAPI document changes, allowing your engineers to review and merge the SDK changes.

The Speakeasy workflow matrix  provides an overview of how to set up automatic SDK generation.

Using your SDK

Once you’ve generated your SDK, you can publish  it for use. TypeScript SDKs are published as npm packages.

A quick, non-production-ready way to see your SDK in action is to copy your SDK folder to a frontend TypeScript project and use it there.

For example, you can create a Vite project that uses TypeScript:

Then, copy the SDK folder from your Elysia app to the src directory of your TypeScript Vite project and delete the SDK folder in your Elysia project.

In the SDK README.md file, you’ll find the documentation for your Speakeasy SDK.

Note that the SDK is not ready for production use. To get it production-ready, follow the steps outlined in your Speakeasy workspace.

The SDK has Zod as a peer dependency, as can be seen in the sdk-typescript/package.json file.

Install the required Zod version:

Replace the code in the src/main.ts file with the following example code taken from the sdk-typescript/docs/sdk/users/README.md file:

Make sure the Elysia server is running, then run the Vite dev server:

Now, you need to enable CORS in your Elysia dev server.

First, install the CORS plugin:

Import the CORS plugin, then register it by passing the plugin into the use() method and chaining the use() method to the Elysia instance:

Open http://localhost:5173 in your browser, then open your browser dev tools. You should see the following logged in the dev tools console:

The SDK functions are type safe and include TypeScript autocompletion for arguments and outputs.

Consider the following example scenario:

When you try to access a property that doesn’t exist, as in the code block above, you get a TypeScript error:

Further reading

This guide covered the basics of generating an OpenAPI document using Elysia. Here are some resources to help you learn more about OpenAPI, the Elysia Swagger plugin, and Speakeasy:

  • Elysia Swagger plugin : Learn more about using Elysia to generate OpenAPI documents. Elysia has first-class support for OpenAPI and follows the OpenAPI Specification by default.
  • Speakeasy documentation : Speakeasy has extensive documentation covering how to generate SDKs from OpenAPI documents, customize SDKs, and more.
  • Speakeasy OpenAPI reference : Review a detailed reference on the OpenAPI Specification.

Last updated on