Skip to main content

Documenting your Express API with Swagger

 We all know about the importance of documenting your APIs. In terms of Node APIs, whether they were built on top of Express or any other framework, you’ve got plenty of open source options out there. These include apiDoc, docbox, and others.

In this tutorial, however, we’re going to explore Swagger usage along with an Express API.

Swagger is an open source set of tools that enable you to design, build, document, and use RESTful web services. It was created to be mostly agnostic, which means that you can use it with pretty much any of your favorite languages and frameworks.

In our example, we’ll be making use of the two libraries: swagger-ui-express and swagger-jsdoc.

The first is a module that allows you to feed a Swagger UI (auto-generated views based on the swagger-ui project) from a swagger.json file, or from an inline object.

The second project is about integrating Swagger using JSDoc comments throughout your code. This is pretty useful, especially when you have extensive APIs and dozens of models.

Application setup

For this tutorial, we won’t cover anything related to Express API building. I’m going to supply this ready-to-use example that you must clone to your local machine before proceeding to implementation.

It’s a simple API that allows you to manage an in-memory list of books. Feel free to increment it with your customizations.

Once you have this in your app, run the commands below in the terminal:

npm install
npm i swagger-ui-express swagger-jsdoc

These are going to download the required dependencies and add addiitonal Swagger ones.

Next, add the following imports to the beginning of the server.js file:

var express = require("express"),
  bodyParser = require("body-parser"),
  swaggerJsdoc = require("swagger-jsdoc"),
  swaggerUi = require("swagger-ui-express");

Those are the two respective objects representing the libraries we’ve imported. Add the following code before the app’s listen function:

const options = {
  definition: {
    openapi: "3.0.0",
    info: {
      title: "LogRocket Express API with Swagger",
      version: "0.1.0",
      description:
        "This is a simple CRUD API application made with Express and documented with Swagger",
      license: {
        name: "MIT",
        url: "https://spdx.org/licenses/MIT.html",
      },
      contact: {
        name: "LogRocket",
        url: "https://logrocket.com",
        email: "info@email.com",
      },
    },
    servers: [
      {
        url: "http://localhost:3000/books",
      },
    ],
  },
  apis: ["./routes/books.js"],
};

const specs = swaggerJsdoc(options);
app.use(
  "/api-docs",
  swaggerUi.serve,
  swaggerUi.setup(specs)
);

As you see in the first line, this configuration object sets an OpenAPI version to 3.0.0. Swagger makes use of the Open API Specification, which is a standard, language-agnostic interface for RESTful APIs allowing humans and machines to understand the capabilities of a web service without having to access the source code or inspect the network traffic.

You can refer to the official docs for all available settings for each version. Here, we’re using just the basics: API info, name, title, description, license, the contact of the API owner, etc.

The API’s property is essential because it searches for the model and endpoint definitions, so make sure to inform it correctly.

Finally, we’re using the swaggerJsdoc function to scan through the options passed in as a param and return the converted Swagger specification object. This one, in turn, can be used along with the swaggerUi setup process.

You can now start the application via the npm start command.  You’ll see the following screen when accessing the http://localhost:3000/api-docs/ URL:

The Swagger UI.
First impressions: Swagger UI

Note that we still don’t have any operations defined in the spec. This happens because we need to map those operations to the routes explicitly. Otherwise, Swagger can’t figure out the API endpoints on its own.

Optionally, you can add a search bar to your UI in case your API has too many operations. For this, change the implementation to the following:

app.use(
  "/api-docs",
  swaggerUi.serve,
  swaggerUi.setup(specs, { explorer: true })
);

Now, the search bar will show up:

The Swagger UI with the search bar enabled.
Swagger UI with search bar enabled.

Creating the model

Like many significant frameworks and API architectures, data is encapsulated into models to become more easily accessible. Swagger also expects your APIs to have models, and for you to define them.

Go to routes/books.js and place the following code at the beginning of the file:

/
  @swagger
   components:
     schemas:
       Book:
         type: object
         required:
           - title
           - author
           - finished
         properties:
           id:
             type: integer
             description: The auto-generated id of the book.
           title:
             type: string
             description: The title of your book.
           author:
             type: string
             description: Who wrote the book?
           finished:
             type: boolean
             description: Have you finished reading it?
           createdAt:
             type: string
             format: date
             description: The date of the record creation.
         example:
            title: The Pragmatic Programmer
            author: Andy Hunt / Dave Thomas
            finished: true
 /

Remember the JSDocs we’ve talked about? JSDocs now enters the scene and helps us to set up the rest of the Swagger spec definitions through the @swagger annotation.

Here, you can define as many schemas as you want. In our case, we’re just defining the domain Books.

The required property receives the list of attributes that are obligatory to be filled in the requests. This step is essential for letting people know what they must send when using your API.

The properties property describes the detailed information over your model attributes. Each attribute must have a name followed by its type, description (optional), and a format (you can validate values too). For a complete list of the available data types, please refer to Swagger Data Types.

Finally, you can provide an example of request data for this schema model. That’s going to be useful later.

When you restart the app and refresh the page, you’ll see the screen below:

A screenshot of a new book schema.
New book schema added.

Much better, isn’t it?

However, we still don’t have any operations. Let’s fix that. Right after the previous JSDoc comment, add the following:

/
  @swagger
  tags:
    name: Books
    description: API to manage your books.
 /

/
  @swagger
  path:
   /books/:
     post:
       summary: Creates a new book
       tags: [Books]
       requestBody:
         required: true
         content:
           application/json:
             schema:
               $ref: '#/components/schemas/Book'
       responses:
         "200":
           description: The created book.
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/Book'
 /

Let’s analyze it in parts, starting with the Swagger tags. A tag allows you to create a section within the Swagger docs. This means that all the routes assigned to this tag will appear under the same division. It’s an organizational setting.

In our example, all the endpoints will be mapped to the same tag.

Next, it’s time to set up our first route: the book’s creation. It’s pretty straightforward. First, define a title and specify the tag that the path is going to be attached to.

Then, we have the request and the response. Within the request, define three things: define whether the request is required, the content type of the request, and the schema from which it must be processed.

The schemas can be referenced through the #components/schemas Swagger operator.

As for the response, just define the HTTP response codes and the properties for each of them. For now, we’re just worried about the happy path with an HTTP 200.

Go ahead and test the new operation directly within the Swagger UI page:

A gif showing how Swagger works.
Creating a new book with Swagger.

Now, you can see where the example values take place. It’s easier to provide your users with sample data as a reference for when they want to perform stuff.

Below, you can find the code for all the other operations:

/
  @swagger
  path:
   /books/:
     get:
       summary: Lists all the books
       tags: [Books]
       responses:
         "200":
           description: The list of books.
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/Book'
     post:
       summary: Creates a new book
       tags: [Books]
       requestBody:
         required: true
         content:
           application/json:
             schema:
               $ref: '#/components/schemas/Book'
       responses:
         "200":
           description: The created book.
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/Book'
   /books/{id}:
     get:
       summary: Gets a book by id
       tags: [Books]
       parameters:
         - in: path
           name: id
           schema:
             type: integer
           required: true
           description: The book id
       responses:
         "200":
           description: The list of books.
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/Book'
         "404":
           description: Book not found.
     put:
       summary: Updates a book
       tags: [Books]
       parameters:
         - in: path
           name: id
           schema:
             type: integer
           required: true
           description: The book id
       requestBody:
         required: true
         content:
           application/json:
             schema:
               $ref: '#/components/schemas/Book'
       responses:
         "204":
           description: Update was successful.
         "404":
           description: Book not found.
     delete:
       summary: Deletes a book by id
       tags: [Books]
       parameters:
         - in: path
           name: id
           schema:
             type: integer
           required: true
           description: The book id
       responses:
         "204":
           description: Delete was successful.
         "404":
           description: Book not found.
 /

Ideally, those mappings should be placed above each of the Express routing functions. However, we’re concentrating them in a single place for the sake of simplicity.

Note that we’re segregating the operations under two main categories: the ones that receive an id parameter and the ones that do not.
That’s necessary for Swagger to understand how to match the routes with the proper path params.

Whenever you have parameters in your endpoints, regardless of their type, you must inform the details under the parameters property.

Here’s the result with all the endpoints correctly mapped:

The books API.
All the mapped endpoints on Swagger.

Conclusion

You may test each endpoint individually to make sure it’s working as precisely as your Postman requests.

Swagger is capable of way more than merely documenting your APIs. A quick read over the official docs will give you a better understanding of its power. Remember that documenting should be part of your team culture. Otherwise your docs won’t always be up to date.

Good luck!

Comments

Popular posts from this blog

How to use Ngx-Charts in Angular ?

Charts helps us to visualize large amount of data in an easy to understand and interactive way. This helps businesses to grow more by taking important decisions from the data. For example, e-commerce can have charts or reports for product sales, with various categories like product type, year, etc. In angular, we have various charting libraries to create charts.  Ngx-charts  is one of them. Check out the list of  best angular chart libraries .  In this article, we will see data visualization with ngx-charts and how to use ngx-charts in angular application ? We will see, How to install ngx-charts in angular ? Create a vertical bar chart Create a pie chart, advanced pie chart and pie chart grid Introduction ngx-charts  is an open-source and declarative charting framework for angular2+. It is maintained by  Swimlane . It is using Angular to render and animate the SVG elements with all of its binding and speed goodness and uses d3 for the excellent math functio...

Understand Angular’s forRoot and forChild

  forRoot   /   forChild   is a pattern for singleton services that most of us know from routing. Routing is actually the main use case for it and as it is not commonly used outside of it, I wouldn’t be surprised if most Angular developers haven’t given it a second thought. However, as the official Angular documentation puts it: “Understanding how  forRoot()  works to make sure a service is a singleton will inform your development at a deeper level.” So let’s go. Providers & Injectors Angular comes with a dependency injection (DI) mechanism. When a component depends on a service, you don’t manually create an instance of the service. You  inject  the service and the dependency injection system takes care of providing an instance. import { Component, OnInit } from '@angular/core'; import { TestService } from 'src/app/services/test.service'; @Component({ selector: 'app-test', templateUrl: './test.component.html', styleUrls: ['./test.compon...

How to solve Puppeteer TimeoutError: Navigation timeout of 30000 ms exceeded

During the automation of multiple tasks on my job and personal projects, i decided to move on  Puppeteer  instead of the old school PhantomJS. One of the most usual problems with pages that contain a lot of content, because of the ads, images etc. is the load time, an exception is thrown (specifically the TimeoutError) after a page takes more than 30000ms (30 seconds) to load totally. To solve this problem, you will have 2 options, either to increase this timeout in the configuration or remove it at all. Personally, i prefer to remove the limit as i know that the pages that i work with will end up loading someday. In this article, i'll explain you briefly 2 ways to bypass this limitation. A. Globally on the tab The option that i prefer, as i browse multiple pages in the same tab, is to remove the timeout limit on the tab that i use to browse. For example, to remove the limit you should add: await page . setDefaultNavigationTimeout ( 0 ) ;  COPY SNIPPET The setDefaultNav...