Table of Contents
When it comes to back-end applications, exposing documentation is essential — it's fundamental for a better understanding of how endpoints work and what they can or cannot do.
We won't focus on implementation details here, so let's assume your application is minimally configured and has at least one API route exposed.
Specification
The first thing we need to consider is the definition of rules and behaviors for a given endpoint. This includes:
- Supported HTTP verb;
- Input parameters;
- Success and/or error response models;
- Authentication method;
- Permissions when available.
Now, you might be wondering: "Ok, we need to gather a lot of definitions for an API — how do we do that in practice?" (If you haven't asked yourself that yet, now's the time...) The answer is: Open API!
What is OpenAPI?
OpenAPI Specification (OAS) is a set of concepts and standards for defining the structure and behavior of APIs over the HTTP protocol. The core idea is to have an agnostic way to describe these rules, typically defined in a YAML or JSON file.
Key highlights when using OAS specifications:
- Easier identification of services and API contracts through standardization.
- Ability to generate code and tests based on the file definitions.
- Infrastructure automation.
Using it in our code
Each language and framework will have its own way of implementing OpenAPI specifications, but in general they all follow the same approach: creating a file with the rules for every resource exposed by the API.
Here's an example:
openapi: 3.0.0
info:
title: Sample API
description: A sample API to demonstrate OpenAPI specification
version: 1.0.0
servers:
- url: https://api.example.com/v1
description: Main production server
tags:
- name: Users
description: Operations related to users
- name: Products
description: Operations related to products
paths:
/users:
get:
summary: Get all users
tags:
- Users
responses:
'200':
description: A list of users
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/User'
'500':
description: Internal server error
components:
schemas:
User:
type: object
properties:
id:
type: string
example: 12345
name:
type: string
example: John Doe
email:
type: string
example: john.doe@example.com
createdAt:
type: string
format: date-time
example: 2023-10-01T12:00:00Z
In the example above, there's only one endpoint available: Get /users. You could spend hours writing each one by hand, but there are faster and more efficient ways to do this.
Automating the process
There are many ways to automate the generation of the specification file. We'll use a strategy that I personally find efficient while still giving us control and transparency over the code.
We'll use a NodeJS application as an example, along with two libraries to handle the automation process:
The setup process is pretty straightforward. First, we need to create a file in the project root — I named it swagger.ts:
// swagger.ts
import swaggerJSDoc from 'swagger-jsdoc'
const swaggerDefinition = {
openapi: '3.0.0',
info: {
title: 'Gen AI API',
version: '1.0.0',
description:
'This is an Express service that provides authorization functionality and includes gen-AI features using RAG, Redis, Postgres, and Langchain.',
},
servers: [
{
url: 'http:localhost:3000',
},
],
tags: [
{
name: 'Auth',
description: 'Endpoints related to authentication',
},
{
name: 'Users',
description: 'Endpoints related to users management',
},
{
name: 'AI',
description: 'Endpoints related to gen-AI features',
},
],
components: {
securitySchemes: {
bearerAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
},
},
},
}
const options = {
swaggerDefinition,
apis: ['./src/modules/**/application/routes/*.ts'],
}
const swaggerSpec = swaggerJSDoc(options)
export default swaggerSpec
In the code above we define some metadata for the general API description. Worth noting the tags field — this array of objects lets you add sections to group endpoints by type.
If you want to better understand the allowed values for the specification file, check out the official documentation.
Now, for each route in the application, we'll add a comment with the specification for that specific route. This is important because the application will read the route files indicated in the apis field shown in the swagger.ts file from the previous example.
// route.ts
import express from 'express'
const router = express.Router()
/**
* @swagger
* /status:
* get:
* summary: Returns a message to validade if API server is running
* responses:
* 200:
* description: A successful response
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* example: Systems up and running!
*/
router.get('/', (_, res) => {
res.status(200).send({ message: 'Systems up and running!' })
})
export default router
By adding the @swagger annotation at the start of the comment, we're indicating that this block should be used to include that route in the documentation — which will eventually be merged with the rest of the content.
Now let's expose an endpoint to view the documentation. Go to your application's app.ts file (or equivalent) and add:
import express from 'express'
import swaggerUi from 'swagger-ui-express'
const app = express()
const port = process.env['PORT'] || config.port
...
app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec))
...
app.listen(port, () => {
console.log(`Server listening to http://localhost:${port}`)
})
Simple as that — once the application is running, the documentation will be available at the /docs endpoint.
Extra: Automating the automation using AI agents
So far so good, but there's one more thing: we still need a bit of manual effort to write the comments for each endpoint. Thanks to existing generative AI models, we can simplify this process even further by asking the AI to generate the comment in the format we need.
Check out the prompt below:
Generate the JSDocs comments using OpenAPI for the following API specs:
"""
endpoint name: /users/{id}
path parameter type: string
method: PUT
request body: {
"name": "John Doe", "email": "john.doe@email.com",
}
summary: Update a user by given id and return it
200_response: {
"success": true,
"data": [
{
"id": "367b2539-bef4-412b-b94d-c9d2178dcdaa",
"name": "John Doe",
"email": "john.doe@gmail.com",
"createdAt": "2024-09-30T21:04:18.656Z"
}
]
}
400_response: {
"success": false,
"error": {
"message": [
"Invalid email address",
"name field is required"
]
}
}
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer <token>"
}
"""
More context: I'm using `swagger-ui-express` and `swagger-jsdoc` libraries to setup the Swagger documentation.
Give me only the response, without explanations.
Here we're providing all the necessary information for this endpoint along with response examples, and also specifying which libraries we're using to generate the documentation. These extra details are important because they give the model more context, resulting in more accurate responses.
Check out the agent's response to this prompt:
/**
* @swagger
* /users/{id}:
* put:
* summary: Update a user by given id and return it
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: string
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* name:
* type: string
* example: John Doe
* email:
* type: string
* example: john.doe@email.com
* responses:
* 200:
* description: A successful response
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: true
* data:
* type: object
* properties:
* id:
* type: string
* example: 367b2539-bef4-412b-b94d-c9d2178dcdaa
* name:
* type: string
* example: John Doe
* email:
* type: string
* example: john.doe@gmail.com
* createdAt:
* type: string
* format: date-time
* example: 2024-09-30T21:04:18.656Z
* 400:
* description: Invalid request
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: false
* error:
* type: object
* properties:
* message:
* type: array
* items:
* type: string
* example: ["Invalid email address", "name field is required"]
* security:
* - bearerAuth: []
*/
All we need to do now is review and make any necessary adjustments to the generated comment and paste it right above the API endpoint definition.
Wrapping up
In this article we learned a bit more about the OpenAPI standard and how we can automate the process of documenting an API, as well as how to use AI agents to make that process even easier.
I hope this content brought you some new knowledge or even some insights for new ideas — thanks for taking the time to read it, and I'll see you in the next one! :)
Check out below how the documentation generation turned out for my project, and if you'd like to understand it better and look at the implementation — just follow this link.
