REST API docs with OpenAPI & Swagger UI

A Step-by-Step Guide to Documenting an Expressjs REST API with OpenAPI & Swagger UI

Joshua Ondieki
6 min readMay 21, 2023
not sure if REST stands for relaxation

Documenting your REST API is a crucial step in the development process. It helps developers create comprehensive API documentation that other developers can easily understand. This article will explore how to document your Express REST API with OpenAPI & Swagger UI.

An example is available at GitHub: express-api-with-openapi-docs

We’ll write our API docs in YAML format using OpenAPI
OpenAPI Specification docs are available here

We’ve mentioned a few terms that may be foreign to you; so let’s define them.

OpenAPI

OpenAPI is an open standard for describing APIs that allows you to provide an API specification encoded in a JSON or YAML document. It provides a comprehensive dictionary of terms that reflects commonly-understood concepts in the world of APIs, embedding the fundamentals of HTTP and JSON

YAML

YAML (YAML Ain’t Markup Language) is a data serialization language used to create key-value pair configuration files and app APIs. It’s a superset of JSON and is formatted using line breaks and whitespace to improve readability.

Understanding YAML syntax if you’re familiar with JavaScript objects and or JSON is pretty straightforward. Here’s a YAML Cheat Sheet

Swagger UI

Swagger UI is an open-source tool that generates a web page that documents the APIs generated by the Swagger specification. It allows anyone to visualize and interact with the API’s resources without having any of the implementation logic in place. Swagger UI is user-friendly and easy to understand, with all logic complexity kept behind the screen. It is automatically generated from your OpenAPI (formerly known as Swagger) Specification.

EXPRESS REST API (TypeScript)

Our example API has a couple routes:
1. GET /
2. POST /
3. PUT /?wishName=’Name of wish you’d like to update’
4. PATCH /?wishName=’Name of wish you’d like to patch’
5. DELETE /:wishName
6. GET /protected

OpenAPI Docs (YAML)

There are two ways you can use to document your API with Swagger UI.

  1. Writing comments in your code on top of every endpoint. Swagger will read these comments and generate a beautiful UI.
/**
* @openapi
* 3.0.0
* /:
* get:
* summary: Returns a message saying hello world
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* description: hello world message
*/
app.get('/', (req, res)=>{
res.status(200).json({message: 'Hello World'})
})

2. Alternatively, you can keep all your endpoints docs in a .yaml file separate from your code. This is what we’ll cover in this article.

The first thing we need to do is set up our server to serve the Swagger UI on a route of your choosing. We’ll use /docs here.

We’ll need to install 2 Node.js packages:

  1. swagger-ui-express — for the beautiful UI
  2. yamljs — to load our YAML file as a JavaScript Object
// ... other imports ...
import path from 'path'
import swaggerUI from 'swagger-ui-express'
import YAML from 'yamljs'

// load api.yaml file, which is in the root directory of our project, as a JavaScript object
const swaggerJsDocs = YAML.load(path.resolve(__dirname, '../api.yaml'))

const app = express()

app.use(json())

// setup docs from our specification file and serve on the /docs route
app.use('/docs', swaggerUI.serve, swaggerUI.setup(swaggerJsDocs))

// ... routes and other app code
openapi: 3.0.0
info:
title: Express API With OpenAPI Docs
description: A simple express REST API with OpenAPI docs generated by Swagger UI.
version: 1.0.0
servers:
- url: /

With those 2 new lines and imports in our express app file and simple api.yaml, your app /docs should display:

We will add paths to our YAML file. This is where our endpoints will be documented. And just like most code, it can get repetitive, so we’ll use reusable components

# ...
paths:

components:
securitySchemes:
schemas:
parameters:
responses:
examples:

Here’s a link to the final YAML docs. Don’t worry if some of it still doesn’t make sense; every bit of it is further explained below.

Boilerplate OpenAPI YAML file

SECURITY SCHEMES

There are multiple types of security schemes but in our wishlist app case, we are using a simple apiKey called secret in the request headers.
apiKeyAuth is basically a variable name to reference it in our paths. The name should match the header name used in the REST API; e.g. secret, token, X-API-Key, etc.
Use the LOCK or Authorize button to add the API key which will be sent with requests you make.

# ...
components:
securitySchemes:
apiKeyAuth:
type: apiKey
in: header
name: secret
# ...

SCHEMAS

Data types for data that is sent in requests and responses are described using schemas.
In our wishlist app, a wish is an object with a name and a description. Wishes is an array of wish objects

# ...
components:
# ...
schemas:
Wish:
type: object
properties:
name:
type: string
description: name of the wish
description:
type: string
description: further description of the wish
example: { name: Space Tour, description: I wish to travel space. }
Wishes:
type: array
items:
$ref: '#/components/schemas/Wish'
example:
- name: unicorn
description: A ridiculous unicorn desc
- name: Space Tour
description: I wish to travel space.
# ...

Schemas appear on the Swagger UI.
$ref is basically referencing another component.

PARAMETERS

Parameters are basically variables that are sent as part of a request, either in path, query, header, or cookie. parameters is an array; a dash (-) is used to list each parameter.
Some endpoints in our wishlist app require the wishName to be sent as a URL query or as part of the URL path.

# ...
components:
# ...
parameters:
wishNameQuery:
in: query
name: wishName
schema:
type: string
description: Name of a wish
required: true
wishNameParam:
in: path
name: wishName
schema:
type: string
description: Name of a wish
required: true
# ...

RESPONSES

500 server error, 400, and 404 are responses that are common in our wishlist app; so we’ve added them as reusable response components to be referenced in our paths.

# ...
components:
# ...
responses:
NotFound:
description: Not Found
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: { message: Not Found }
ServerError:
description: SERVER ERROR
content:
application/json:
schema:
type: object
properties:
message:
type: string
error:
type: string
example: { message: "Oops! It's not you, it's us.", error: A server error occured. }
# ...

EXAMPLES

Examples are used to make the docs clearer. They also provide data that users, developers, or testing tools can use to make requests quickly. They can be written in the examples components or used directly in schemas or paths.

# ...
components:
# ...
examples:
unicornWish:
value:
name: unicorn
description: A ridiculous unicorn desc
spaceWish:
value:
name: Space Tour
description: I wish to travel space.
# ...

PATHS

A single path can have multiple methods. Below we have an example with 3 paths.

# ...
paths:
/:
get:
# ...
post:
# ...
put:
# has parameter in query
# ...
patch:
# has parameter in query
# ...
/{params}:
delete:
# has parameter in path
# ...
/protected:
get:
security:
- apiKeyAuth: []
# ...
# ...

A method may have properties like:

# ...
paths:
# ...
/:
patch:
tags:
- wish
summary: patch a wish
description: updates the name and or description of wish
parameters:
- $ref: '#/components/parameters/wishNameQuery'
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Wish'
responses:
200:
# ...
404:
# ...
# ...

Useful resources & Tips.

  1. The official OpenAPI docs.
  2. YAML Cheat Sheet
  3. Official PetStore Example
  4. Create a nodemon.json file with the below content. This will ensure while your development server is up, it’s also watching for changes in YAML files.
{
"ext": ".js,.mjs,.ejs,.json,.yaml"
}

--

--