DEV Community

rabindratamang
rabindratamang

Posted on

Building Production-Ready OpenAPI with Node.js: A Step-by-Step Guide

APIs power modern web applications, and building one that’s robust, secure, and maintainable is critical for production environments. In this tutorial, we'll build a production-ready Node.js API that uses OpenAPI for standardized documentation and request/response validation. Our project features two versions of the API—with v1 endpoints secured using JWT authentication and v2 endpoints open for testing. We’ve also integrated middleware for security, rate limiting, CORS, logging, and error handling.

You can check out the full project on GitHub and see the hosted API docs on Render.


Table of Contents


Project Overview

In our project, we have set up two versions of the API:

  • v1 (Protected): These endpoints are secured with JWT authentication. You’ll need to register and log in to receive a token, which grants you access to protected routes.
  • v2 (Open): These endpoints are open to all, making it easier to test and experiment with API calls.

Both versions share a common functionality: managing an in-memory array of resource objects. The expected structure of each resource is:

{
  "id": 0,
  "name": "string",
  "value": "string"
}
Enter fullscreen mode Exit fullscreen mode

Each API endpoint (GET, POST, PUT, PATCH, DELETE) adheres strictly to this schema using OpenAPI validation.


Getting Started

Prerequisites

  • Node.js (v14 or higher)
  • npm (Node Package Manager)

Installation

  1. Clone the Repository:
   git clone https://github.com/rabindratamang/openapi-project.git
   cd openapi-project
Enter fullscreen mode Exit fullscreen mode
  1. Install Dependencies:
   npm install
Enter fullscreen mode Exit fullscreen mode
  1. Environment Variables:

Create a .env file in the project root with the following content:

   PORT=3000
   JWT_SECRET=your_jwt_secret_key
Enter fullscreen mode Exit fullscreen mode
  1. Start the Server:
   npm start
Enter fullscreen mode Exit fullscreen mode

The server will start on http://localhost:3000, and you can access the API documentation at http://localhost:3000/api-docs.


Understanding the Code

Project Structure

Our project is organized to ensure scalability and maintainability:

openapi-project/
│-- src/
│   │-- config/ 
│   │   └── auth.js         # JWT configuration
│   │-- controllers/
│   │   ├── authController.js  # Authentication logic
│   │   ├── v1Controller.js    # Business logic for v1 resources
│   │   └── v2Controller.js    # Business logic for v2 resources
│   │-- routes/
│   │   ├── v1Routes.js        # Routes for API v1 (protected)
│   │   └── v2Routes.js        # Routes for API v2 (open)
│   │-- middlewares/
│   │   └── errorHandler.js    # Centralized error handling
│   │-- utils/
│   │   └── logger.js          # Logging utility
│   └── app.js                 # Express app initialization (if needed)
│-- swagger/
│   └── swagger.yaml           # OpenAPI spec defining endpoints, schemas, and security
│-- .env
│-- .gitignore
│-- package.json
│-- README.md
│-- server.js                # Entry point for the server
Enter fullscreen mode Exit fullscreen mode

This structure helps you to easily extend your application and keep the concerns separate (routing, business logic, error handling, etc.).


Swagger/OpenAPI Specification

Our swagger/swagger.yaml file is the cornerstone of the API documentation and validation. It defines:

  • Security Schemes: JWT authentication for v1 endpoints.
  • Resource Schema: Enforcing a strict schema for every resource object.

Here’s a snippet from our OpenAPI spec:

components:
  schemas:
    Resource:
      type: object
      required:
        - id
        - name
        - value
      properties:
        id:
          type: integer
          example: 0
        name:
          type: string
          example: "string"
        value:
          type: string
          example: "string"
Enter fullscreen mode Exit fullscreen mode

This schema guarantees that any object processed by our API exactly follows this structure. If a request has extra or missing fields, the express-openapi-validator middleware will reject it.


JWT Authentication & API Versioning

For v1 endpoints, we secure routes with JWT. The authentication flow typically involves:

  1. User Registration: Creating a new user and generating a JWT token.
  2. User Login: Validating credentials and returning a JWT token.
  3. Protected Routes: Middleware checks the presence and validity of the token before processing the request.

In src/routes/v1Routes.js, routes are defined like so:

const express = require("express");
const router = express.Router();
const v1Controller = require("../controllers/v1Controller");

// Protected routes (JWT authentication middleware applied globally or per-route)
router.get("/resources", v1Controller.getAllResources);
router.post("/resources", v1Controller.createResource);
router.put("/resources/:id", v1Controller.updateResource);
router.patch("/resources/:id", v1Controller.modifyResource);
router.delete("/resources/:id", v1Controller.deleteResource);

module.exports = router;
Enter fullscreen mode Exit fullscreen mode

The OpenAPI spec for these endpoints references the security scheme:

securitySchemes:
  BearerAuth:
    type: http
    scheme: bearer
    bearerFormat: JWT
Enter fullscreen mode Exit fullscreen mode

For v2 endpoints (in src/routes/v2Routes.js), no authentication is required, making it ideal for testing or public APIs.


OpenAPI Validation

To ensure that every request conforms to our defined schemas, we integrate express-openapi-validator. This middleware reads the swagger.yaml file and validates:

  • Request Bodies: Ensuring they contain the exact fields (id, name, value).
  • Responses: Confirming that responses match the spec.

In server.js, we add:

const { OpenApiValidator } = require("express-openapi-validator");
const path = require("path");

app.use(
  OpenApiValidator.middleware({
    apiSpec: path.join(__dirname, "swagger", "swagger.yaml"),
    validateRequests: true,
    validateResponses: true,
  })
);
Enter fullscreen mode Exit fullscreen mode

With this setup, if a client sends a request that doesn't match the schema (for example, missing the name property), the API responds with a clear error message.


Middleware for Security and Logging

We’ve added several middleware packages to enhance security and reliability:

  • Helmet: Sets various HTTP headers to protect the app.
  • Rate Limiter: (Not detailed here, but you can add express-rate-limit to limit request frequency.)
  • CORS: Allows cross-origin requests.
  • Morgan: Logs HTTP requests.
  • Error Handler: Centralizes error responses to make debugging easier.

All these are configured in server.js:

app.use(express.json());
app.use(cors());
app.use(helmet());
app.use(morgan("combined"));
Enter fullscreen mode Exit fullscreen mode

Centralized error handling in src/middlewares/errorHandler.js ensures that any unexpected issues are caught and sent back to the client in a consistent format.


Running and Testing the API

  1. Start the Server:

    Run npm start and open http://localhost:3000/api-docs to interact with the API via Swagger UI.

  2. Register and Login (v1):

    Use the /api/auth/register and /api/auth/login endpoints to receive a JWT token, then use that token to access protected v1 endpoints.

  3. Try Out Endpoints:

    • GET /api/v1/resources: Retrieve all resources (requires JWT).
    • POST /api/v1/resources: Add a new resource (requires JWT).
    • PUT/PATCH/DELETE /api/v1/resources/{id}: Modify or delete a resource by its ID (requires JWT).
    • v2 endpoints function similarly but are open and do not require JWT.
  4. Validation in Action:

    Test the OpenAPI validator by sending an invalid payload. For example, omitting the name field should result in an error response detailing the missing property.


Wrapping Up

Building a production-ready API means planning for both functionality and security from the very start. With our Node.js project, you’ve learned how to:

  • Structure your application for scalability.
  • Document your API using OpenAPI/Swagger.
  • Secure endpoints with JWT authentication.
  • Enforce strict request/response validation using express-openapi-validator.
  • Enhance security with Helmet, CORS, and logging middleware.

I hope this step-by-step guide helps you build robust APIs in your projects. Check out the GitHub repository for the full code, and feel free to share your thoughts and improvements.

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay