This content originally appeared on DEV Community and was authored by Gleidson Leite da Silva
Maintaining accurate and up-to-date API documentation is a common challenge for developers, especially when working in teams. Manually updating Swagger files for each API route can lead to frequent merge conflicts and inconsistencies between the actual API behavior and the documentation. These issues not only slow down development but can also lead to confusion and errors when using the API.
The solution? Automate the generation of Swagger documentation using Joi validation schemas in Node.js with Express. By doing this, you ensure that your documentation stays in sync with your codebase, while eliminating the need for manual updates.
In this article, we will walk through:
- Setting up Joi validation and Swagger documentation automation.
- Solving the problem of manual Swagger updates and merge conflicts.
- Ensuring robust API contract validation for all routes.
Let’s break it down step by step.
The Problem: Manual Swagger Maintenance
When developing an API, it's common to manually update Swagger documentation for each route. In large teams, this leads to conflicts when multiple developers edit the same file, often resulting in lost changes, inconsistent documentation, and wasted time resolving merge conflicts.
By automating Swagger documentation generation using Joi, we can solve this problem. Joi allows us to validate incoming requests to ensure they match our expectations, and at the same time, we can use those same Joi schemas to generate Swagger documentation automatically.
This approach ensures that documentation is always accurate, in sync with the codebase, and reflects the actual structure of the API.
Step 1: Setting Up Joi Validation Middleware
The first step is creating middleware that validates incoming requests using Joi. This middleware ensures that the request's parameters, headers, body, and query strings are valid according to the schema you define. Additionally, the same Joi schema will be used to automatically generate Swagger documentation.
Here’s the middleware:
const Joi = require("joi");
const schemasEnum = Object.freeze({
BODY: 'body',
PARAMS: 'params',
QUERY: 'query',
HEADERS: 'headers'
});
const validationMiddleware = (schema) => {
return (request, response, nextFunction) => {
const validationOptions = {
abortEarly: false,
allowUnknown: true,
stripUnknown: true
};
const schemas = Object.values(schemasEnum);
const validationResults = {};
for (const key of schemas) {
if (schema[key]) {
const { error, value } = schema[key].validate(request[key], validationOptions);
if (error) {
return response.status(400).json({
error: `Validation error in ${key}: ${error.details.map((x) => x.message).join(', ')}`
});
} else {
validationResults[key] = value;
}
}
}
Object.assign(request, validationResults);
nextFunction();
};
};
module.exports = { validationMiddleware };
This middleware validates incoming requests based on the schema defined for each route. If the validation fails, it returns an error; otherwise, it allows the request to proceed.
Step 2: Registering Routes with Joi and Automatically Generating Swagger
Next, we create a function that registers routes and automatically generates Swagger documentation using the joi-to-swagger library. This function applies the validation middleware and generates the Swagger documentation at the same time.
Here’s how the registerRoute
function works:
const { validationMiddleware } = require("../middlewares/validationMiddelware");
const convert = require("joi-to-swagger");
const routes = [];
const registerRoute = (router, {
method,
path,
schema = {},
handler,
middlewares = [],
summary,
description,
tags = []
}) => {
if (schema && Object.keys(schema).length > 0) {
middlewares.unshift(validationMiddleware(schema));
}
router[method](path, ...middlewares, handler);
const parameters = [];
const requestBody = {};
if (schema.params) {
const { swagger: paramsSwagger } = convert(schema.params);
parameters.push(...Object.keys(paramsSwagger.properties).map(key => ({
name: key,
in: 'path',
required: schema.params._flags.presence === 'required',
schema: paramsSwagger.properties[key],
description: "paramsSwagger.properties[key].description"
})));
}
if (schema.query) {
const { swagger: querySwagger } = convert(schema.query);
parameters.push(...Object.keys(querySwagger.properties).map(key => ({
name: key,
in: 'query',
required: schema.query._flags.presence === 'required',
schema: querySwagger.properties[key],
description: "querySwagger.properties[key].description"
})));
}
if (schema.body) {
const { swagger: bodySwagger } = convert(schema.body);
requestBody.content = {
'application/json': {
schema: bodySwagger
}
};
}
routes.push({
path,
method,
summary,
description,
tags,
parameters,
requestBody
});
};
module.exports = { registerRoute, routes };
This function registers the route, applies the Joi validation middleware, and generates Swagger documentation for the route automatically. The joi-to-swagger library is responsible for converting the Joi schema into Swagger format.
Step 3: Defining Joi Schemas for Routes
Now, let’s define the Joi validation schema for a route. In this example, we define a schema for depositing a balance. It validates the userId
parameter and the amount
in the request body.
Here’s the Joi schema:
const Joi = require("joi");
const { appErrorSchema } = require("../../../shared/infra/http/schemas/AppErrorSchema");
const depositBalanceParamsSchema = Joi.object({
userId: Joi.number().integer().required().description("User id"),
});
const depositBalanceBodySchema = Joi.object({
amount: Joi.number().positive().greater(0).required().description("Amount to deposit"),
});
const depositBalanceResponsesSchema = {
204: Joi.any().empty(),
400: appErrorSchema,
404: appErrorSchema,
};
module.exports = { depositBalanceParamsSchema, depositBalanceBodySchema, depositBalanceResponsesSchema };
In this schema:
- The
userId
must be a required integer. - The
amount
must be a positive number greater than 0. - The responses include validation for status codes
204
,400
, and404
using an error schema.
Step 4: Registering the Deposit Balance Route
Now that we have the schema, let’s create a route that validates the incoming request and automatically generates Swagger documentation:
const { Router } = require("express");
const { DepositBalanceController } = require("../../../useCases/depositBalance/DepositBalanceController");
const { registerRoute } = require("../../../../shared/infra/http/routes/registerRoute");
const { depositBalanceParamsSchema, depositBalanceBodySchema, depositBalanceResponsesSchema } = require("../../../useCases/depositBalance/DepositBalanceSchema");
const balancesRouter = Router();
const depositBalanceController = new DepositBalanceController();
registerRoute(balancesRouter, {
description: "\"Deposit balance\","
handler: depositBalanceController.handle,
method: "post",
path: "/balances/deposit/:userId",
summary: "Deposit balance",
schema: {
params: depositBalanceParamsSchema,
body: depositBalanceBodySchema,
responses: depositBalanceResponsesSchema
},
tags: ["Balances"]
});
module.exports = { balancesRouter };
This route handles deposit requests and validates them using the Joi schema defined earlier. The Swagger documentation is automatically generated based on the Joi schema.
Step 5: Generating the Swagger Document
To serve the Swagger documentation, we aggregate the registered routes and generate the Swagger document based on the route definitions.
const { routes } = require("../routes/registerRoute");
const swaggerDocument = {
openapi: '3.0.0',
info: {
title: "'API Documentation',"
version: '1.0.0'
},
paths: {}
};
routes.forEach(route => {
const path = route.path.replace(/:([a-zA-Z0-9_]+)/g, '{$1}');
if (!swaggerDocument.paths[path]) {
swaggerDocument.paths[path] = {};
}
swaggerDocument.paths[path][route.method] = {
summary: route.summary,
description: "route.description,"
tags: route.tags,
parameters: route.parameters,
requestBody: route.requestBody
};
});
module.exports = { swaggerDocument };
This code dynamically builds the Swagger document using the registered routes.
Step 6: Setting Up the Express Server
To complete the implementation, we set up the Express server and include the Swagger documentation endpoint.
Here’s the server.js file:
// server.js
const app = require('./app');
init();
async function init() {
try {
app.listen(3001, () => {
console.log('Express App Listening on Port 3001');
});
} catch (error) {
console.error(`An error occurred: ${JSON.stringify(error)}`);
process.exit(1);
}
}
And the app.js file:
// app.js
require("express-async-errors");
const express = require('express');
const bodyParser = require('body-parser');
const { appRouter } =
require("./modules/shared/infra/http/routes/app.routes");
const swaggerUi = require("swagger-ui-express");
const { swaggerDocument } = require("./swagger/swaggerConfig");
const app = express();
app.use(bodyParser.json());
// Use your app's routes
app.use(appRouter);
// Serve the Swagger documentation
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerDocument));
module.exports = app;
Once the server is running, you can access the Swagger documentation at:
http://localhost:3001/api-docs
How to Install and Run
To run the project and set up the automated Swagger documentation, follow these steps:
- Install Dependencies: Make sure you install all the required libraries, including express, joi, swagger-ui-express, and joi-to-swagger.
npm install express joi joi-to-swagger swagger-ui-express body-parser express-async-errors
These libraries are essential for setting up the Express server, validating incoming requests with Joi, and generating Swagger documentation automatically.
- Run the Server: Once the dependencies are installed, you can run the server using:
node server.js
This will start your Express server and listen on port 3001 (or any other port you specify).
- Access Swagger Documentation: Open your browser and navigate to the following URL to view the automatically generated Swagger documentation:
http://localhost:3001/api-docs
This endpoint serves the interactive Swagger UI, allowing you to test and explore your API routes directly in the browser.
Conclusion
Automating Swagger documentation with Joi streamlines the documentation process, eliminates merge conflicts, and ensures that your API documentation is always in sync with your actual code. By using Joi validation schemas to automatically generate Swagger documentation, you save time and improve the accuracy of your API contracts.
With the steps outlined in this article, you can easily integrate automatic Swagger generation into your Node.js projects, making your development workflow smoother and more efficient.
In summary:
- Joi simplifies request validation and ensures that incoming data conforms to the expected format.
- Swagger documentation is automatically generated from Joi schemas, eliminating manual updates.
- Developers can focus on writing code without worrying about synchronizing documentation and API contracts.
This approach provides a robust solution for both API validation and documentation, allowing teams to work more efficiently and with fewer errors.
This content originally appeared on DEV Community and was authored by Gleidson Leite da Silva
Gleidson Leite da Silva | Sciencx (2024-10-05T22:54:27+00:00) Automating Swagger Documentation with Joi in Node.js: Simplify Your API Documentation Workflow. Retrieved from https://www.scien.cx/2024/10/05/automating-swagger-documentation-with-joi-in-node-js-simplify-your-api-documentation-workflow/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.