DEV Community

Paige Niedringhaus
Paige Niedringhaus

Posted on • Originally published at paigeniedringhaus.com on

Setting Up Swagger to API Test In a JavaScript Application

Swagger logo

Introduction

If you’re like me, and you’ve ever worked with a REST API (application program interface) that doesn’t have a UI attached to it, you’ve probably stumbled more than once wondering what inputs and auth tokens the API needs to give you back the data you’re expecting and not some cryptic 400 HTTP error message.

Sometimes I’ve got no idea if I need some kind of authorization header (or how to send it), if the API will accept POSTs or PUTs (and what parameters are required versus what’s optional), and if it wants application/json or some other XML formatted encoding instead.

Wouldn’t it be nice to be to take some of that uncertainty out of the equation? Wouldn’t it be nice if, instead of being faced with an empty Postman or Insomnia API testing application waiting for you to configure it and provide it with everything it needs, an API route told you exactly what it needed? Request body vs. query parameter, integer vs. string, JWT auth header vs. cookie — all of it, just laid out, all the possible routes visible.

Sounds too good to be true? It’s not.

Before I joined my current team of developers, I had no idea such a magical API-assistant like this existed, but it does. And it’s called Swagger.

Today, we'll cover how to set up Swagger inside of a JavaScript or Node.js application and document the actual code so that you can see exactly what REST API routes are available to you and what they need to work in the browser.

I’ll give you all the details below, but here’s an example of what Swagger can provide for your JavaScript web application’s API routes.

Swagger dashboard

Yes, your API too can be this lovely, alphabetized and color-coded with Swagger’s help.


Meet Swagger, your API's new friend

Swagger’s Wikipedia page sums up what it is best:

Swagger is an open-source software framework backed by a large ecosystem of tools that helps developers design, build, document, and consume RESTful Web services. While most users identify Swagger by the Swagger UI tool, the Swagger toolset includes support for automated documentation, code generation, and test-case generation. — Wikipedia, Swagger (software)

If this doesn’t make total sense now, read on. It will make sense once you see it in practice.

One of the loveliest things about Swagger is the fact that it can be used with most any programming language: from JavaScript to Haskell to Go to C++ to Python to Rust, the list goes on and on. The amount of supported languages is really staggering.

My first introduction to Swagger was through Java’s Spring Boot applications. By including one line in the build.gradle dependencies: compile(‘io.springfox:springfox-swagger-ui:2.9.2’), one annotation in the Java config files: @EnableSwagger2, and the location of the swagger.basePackage in the application.yml, you’re provided with a beautiful API UI whenja the Spring Boot application compiles and runs.

The Swagger UI can be reached by typing in something like http://localhost:8081/swagger-ui.html into the browser. Then, voilà, you’ve got an interactive, browser-based UI for your API service that you can test to your heart’s content.

Spring Boot based Swagger

Here’s an example of what I see when I open up a Spring Boot Swagger HTML page.

Swagger presents a user with the routes in the project, it shows exactly what the shape of the data is and what types of properties are required for each parameter, it lets you know if headers are needed and exactly what kinds they are, if a request body or query param is optional or required, as well as possible HTTP response methods and what each one means. It’s amazing — all that for just three little updates to my Java code.

It seemed like a piece of cake with Spring Boot, and I (naively) thought it would be just as easy to implement Swagger with my full stack, MERN, JavaScript application. Silly me…

Swagger in JavaScript, there's an npm package for that, right?

Well, the short answer to that question is: yes. The longer answer to that question is: there are many packages. The longest answer is what I’ll tell you now.

Unlike Spring Boot, which has, what could be considered a defacto Swagger package, the world of JavaScript and Swagger is more fractured and fragmented. From my exploration of npm and the Internet, there didn’t appear to be just one answer to generate Swagger docs and display them in the UI format I’d come to know and love.

So I read some blogs, read some documentation around OpenAPI specification (formerly known as Swagger Specification), and upon learning there was no maintained Swagger docs generator for JavaScript, I ultimately decided I would use Swagger JSDoc to create my Swagger documentation for my routes, and combine it with Swagger UI Express to generate the UI interface in the browser. Now let me tell you a bit more about each of these modules.

Swagger JSDoc

Once again, Swagger JSDoc’s own words best describe it:

swagger-jsdoc enables you to integrate Swagger using JSDoc comments in your code. Just add @swagger on top of your DocBlock and declare the meaning of your code in YAML complying to the OpenAPI specification. — Swagger JSDoc, npm

This means that above each existing API route, you write in your Swagger specs YAML-style (so indentation and colons count) for that route with the @swagger annotation, and this module will then translate that into the UI rendered by my second module: Swagger UI Express.

This module is also especially good for already written APIs (which my project happens to be). I was adding in Swagger documentation as an afterthought — not as part of the initial architecture when I first built the app.

the swagger-jsdoc project assumes that you want document your existing/living/working code in a way to "give life" to it, generating a specification which can then be fed into other Swagger tools, and not the vice-versa.— Swagger JSDoc, npm

If I had decided to add Swagger earlier in the process, I would have looked at Swagger Editor or Swagger Node.

Swagger UI Express

Swagger UI Express's documentation is much less verbose and involved than Swagger JSDoc, because the challenging part really is the writing of the Swagger specifications.

As Swagger UI Express’s docs say, it:

Adds middleware to your express app to serve the Swagger UI bound to your Swagger document. This acts as living documentation for your API hosted from within your app. — Swagger UI Express, npm

Basically, you follow a prescribed formula to feed the Swagger documentation into the Swagger UI Express module and it generates the HTML UI. And thankfully, the documentation for how to do this is both up to date and accurate. It makes it really simple.

Now that I’ve given a little more background on the two solutions I chose to make Swagger work with JavaScript, and more specifically, my Express/Node.js server application, it’s time to talk about how to implement it.

Let's set up Swagger in JavaScript, how hard can it be?

Once I got the hang of how to write the route specifications, implementing Swagger wasn’t too tough, but to start out with the easiest part, I’ll go over the Swagger UI Express and Swagger JSDoc server setup first.

If you’d like to download my whole project and run it locally or just see the source code, here’s a link to the repo or you can click the link of each file name above a code snippet to see it in GitHub.

Server.js setup

server.js

// more JS imports here

import swaggerJSDoc from 'swagger-jsdoc';
import swaggerUi from 'swagger-ui-express';

const app = express();

const API_PORT = process.env.API_PORT || 3000;

const swaggerDefinition = {
  info: {
    title: 'MySQL Registration Swagger API',
    version: '1.0.0',
    description: 'Endpoints to test the user registration routes',
  },
  host: 'localhost:3003',
  basePath: '/',
  securityDefinitions: {
    bearerAuth: {
      type: 'apiKey',
      name: 'Authorization',
      scheme: 'bearer',
      in: 'header',
    },
  },
};

const options = {
  swaggerDefinition,
  apis: ['./routes/*.js'],
};

const swaggerSpec = swaggerJSDoc(options);

app.get('/swagger.json', (req, res) => {
  res.setHeader('Content-Type', 'application/json');
  res.send(swaggerSpec);
});

app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));

// more JS code here
Enter fullscreen mode Exit fullscreen mode

As you can see, both swaggerJSDoc and swaggerUiExpress are imported into the file (this can also be done with require if you choose not to use an ES6 transpiler in Node.js like I did).

This is where the magic of Swagger UI Express transforms Swagger JSDoc’s swaggerSpec into a nice browser UI.

The swaggerDefinition object is created (this is part of the setup required by swagger-jsdoc). Its properties include info like a title for the app, a version and a description. It also needs a host (in my case, port 3003 is the port exposed from my Docker container for the API) and a basePath. Since I also have some routes that are only accessible to the user if they’re logged in with a valid JWT token, I also had to create one securityDefinitions object with bearerAuth info: type, name, scheme, and in.

Here is the swaggerDefinition code:

const swaggerDefinition = {
  info: {
    title: 'MySQL Registration Swagger API',
    version: '1.0.0',
    description: 'Endpoints to test the user registration routes',
  },
  host: 'localhost:3003',
  basePath: '/',
  securityDefinitions: {
    bearerAuth: {
      type: 'apiKey',
      name: 'Authorization',
      scheme: 'bearer',
      in: 'header',
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

Once that’s defined, it’s gathered together into another object called options, which includes the path for your route files. In my case, I have a folder called routes where they all live, so that’s where I told Swagger to look.

const options = {
  swaggerDefinition,
  apis: ['./routes/*.js'],
};
Enter fullscreen mode Exit fullscreen mode

After that, the swaggerSpec variable is initialized, which wraps the options object in swaggerJSDoc(). Then, the Express server is given a route to see the full swagger.json schema that gets created by the swaggerSpec.

const swaggerSpec = swaggerJSDoc(options);

app.get('/swagger.json', function(req, res) {
  res.setHeader('Content-Type', 'application/json');
  res.send(swaggerSpec);
});
Enter fullscreen mode Exit fullscreen mode

But to me, this is secondary to the piece of the code, below, which is what actually combines the swaggerSpec with the swaggerUi module that creates the Swagger UI I know and love.

app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));
Enter fullscreen mode Exit fullscreen mode

Right, and that’s that. It was a bit more setup than I initially thought, but as I said, the documentation for setting all this up is decent, and if you get stuck, there’s tutorials like this one to help you out.

User schema

Before I could write my routes, referencing my User object, I needed to go to the actual object declaration file and tell Swagger what made up my user. After that, I’d be able to reference that schema and those defining properties in the different routes to ensure things were copacetic.

I used Sequelize as the ORM for my MySQL database, so I had a User schema defined for that reason. If you’d like to learn more about Sequelize, you can read this blog post I wrote.

Here’s what my user.js file ended up looking like. Everything between the

/**
 * @swagger 
 * schema definition info goes here...
 */
Enter fullscreen mode Exit fullscreen mode

is part of the schema definition that will be referenced in my routes.

user.js

/* eslint-disable indent */
/**
 * @swagger
 * definitions:
 * User:
 * type: object
 * properties:
 * id:
 * type: integer
 * first_name:
 * type: string
 * last_name:
 * type: integer
 * email:
 * type: string
 * username:
 * type: string
 * password:
 * type: string
 * format: password
 * resetPasswordToken:
 * type: string
 * resetPasswordExpires:
 * type: string
 * format: date-time
 * required:
 * - email
 * - username
 * - password
 */

module.exports = (sequelize, type) => sequelize.define('user', {
    id: {
      type: type.INTEGER,
      primaryKey: true,
      autoIncrement: true,
    },
    first_name: type.STRING,
    last_name: type.STRING,
    email: {
      type: type.STRING,
      allowNull: false,
    },
    username: {
      type: type.STRING,
      allowNull: false,
    },
    password: {
      type: type.STRING,
      allowNull: false,
    },
    resetPasswordToken: type.STRING,
    resetPasswordExpires: type.DATE,
  });
Enter fullscreen mode Exit fullscreen mode

As you can see, each of the object’s properties is named ( id, username, resetPasswordToken, etc.), and its data type ( string, integer, etc.) is defined right underneath it. The last thing of note is the required object properties.

For me, these match the actual User object’s properties that have the requirement allowNull: false. That is username, password, and email for my app.

Route schema

The next thing to cover is writing the Swagger documentation for each of the routes.

I actually have nine different routes in my application, but I’ll be showing the documentation for three.

One will be a route requiring no authorization, the second will be one requiring a JWT token for authorization, and the third one will need a different randomly generated string to verify it is legit. I’ll explain that one in more detail when I get there.

loginUser.js - the unsecured route

/* eslint-disable no-console */
import jwt from 'jsonwebtoken';
import passport from 'passport';
import jwtSecret from '../config/jwtConfig';
import User from '../sequelize';

/**
 * @swagger
 * /loginUser:
 * post:
 * tags:
 * - Users
 * name: Login
 * summary: Logs in a user
 * produces:
 * - application/json
 * consumes:
 * - application/json
 * parameters:
 * - name: body
 * in: body
 * schema:
 * $ref: '#/definitions/User'
 * type: object
 * properties:
 * username:
 * type: string
 * password:
 * type: string
 * format: password
 * required:
 * - username
 * - password
 * responses:
 * '200':
 * description: User found and logged in successfully
 * '401':
 * description: Bad username, not found in db
 * '403':
 * description: Username and password don't match
 */

module.exports = app => {
  app.post('/loginUser', (req, res, next) => {
    passport.authenticate('login', (err, users, info) => {
      if (err) {
        console.error(`error ${err}`);
      }
      if (info !== undefined) {
        console.error(info.message);
        if (info.message === 'bad username') {
          res.status(401).send(info.message);
        } else {
          res.status(403).send(info.message);
        }
      } else {
        req.logIn(users, () => {
          User.findOne({
            where: {
              username: req.body.username,
            },
          }).then(user => {
            const token = jwt.sign({ id: user.id }, jwtSecret.secret, {
              expiresIn: 60 * 60,
            });
            res.status(200).send({
              auth: true,
              token,
              message: 'user found & logged in',
            });
          });
        });
      }
    })(req, res, next);
  });
};
Enter fullscreen mode Exit fullscreen mode

My first Swagger documentation example is for an unsecured route: my user login route, to be precise. Once again, all the Swagger documentation is written in this fashion, and be warned: spacing/indentation and colons do matter.

/**
 * @swagger
 * documentation for route goes here...
 */
Enter fullscreen mode Exit fullscreen mode

Swagger documentation is written in YAML syntax, so it cares about proper indentation for each line and it also cares that there are colons for most lines — if the spacing is off or the colon is missing, the terminal running the Node server code will throw errors until it’s fixed (this tripped me up a few times as I was going through this exercise).

Here’s the full Swagger documentation for the loginUser() route.

/**
 * @swagger
 * /loginUser:
 * post:
 * tags:
 * - Users
 * name: Login
 * summary: Logs in a user
 * produces:
 * - application/json
 * consumes:
 * - application/json
 * parameters:
 * - name: body
 * in: body
 * schema:
 * $ref: '#/definitions/User'
 * type: object
 * properties:
 * username:
 * type: string
 * password:
 * type: string
 * format: password
 * required:
 * - username
 * - password
 * responses:
 * '200':
 * description: User found and logged in successfully
 * '401':
 * description: Bad username, not found in db
 * '403':
 * description: Username and password don't match
 */
Enter fullscreen mode Exit fullscreen mode

As you can see, the route is defined first (the actual URL route Swagger will have to hit when it’s run in the browser), then the type of HTTP call is defined ( get, post, delete).

The summary section appears next to the route in the UI, and it nicely describes what the route is used for.

If there were numerous different schemas defined, the tags section would be more useful in organizing different route groups. Since I only have users it doesn’t really matter much for me.

In case there’s something besides JSON being produced or consumed, that is defined.

And then the parameters that the user will input are laid out underneath. Since my JSON is being passed in a request body, in: body is defined. If it were being passed in the query params it would say in: query instead.

The only two properties of the user object which is defined and referenced with $ref: '#/definitions/User' are username and password. Since both are required for this route to work the required field is set underneath and both properties are referenced (note that must be inline with the schema column for the required fields to take effect in Swagger).

Finally, the HTTP responses we can expect from the server are laid out with descriptions of what each one means. They’re pretty self-explanatory.

Now that I’ve covered an unsecured route, let’s move on to a JWT secured route.

findUser.js - the secured route

/* eslint-disable no-console */
import passport from 'passport';
import User from '../sequelize';

/**
 * @swagger
 * /findUser:
 * get:
 * tags:
 * - Users
 * name: Find user
 * summary: Finds a user
 * security:
 * - bearerAuth: []
 * consumes:
 * - application/json
 * produces:
 * - application/json
 * parameters:
 * - in: query
 * name: username
 * schema:
 * type: string
 * required:
 * - username
 * responses:
 * '200':
 * description: A single user object
 * schema:
 * $ref: '#/definitions/User'
 * '401':
 * description: No auth token / no user found in db with that name
 * '403':
 * description: JWT token and username from client don't match
 */

module.exports = (app) => {
  app.get('/findUser', (req, res, next) => {
    passport.authenticate('jwt', { session: false }, (err, user, info) => {
      if (err) {
        console.log(err);
      }
      if (info !== undefined) {
        console.log(info.message);
        res.status(401).send(info.message);
      } else if (user.username === req.query.username) {
        User.findOne({
          where: {
            username: req.query.username,
          },
        }).then((userInfo) => {
          if (userInfo != null) {
            console.log('user found in db from findUsers');
            res.status(200).send({
              auth: true,
              first_name: userInfo.first_name,
              last_name: userInfo.last_name,
              email: userInfo.email,
              username: userInfo.username,
              password: userInfo.password,
              message: 'user found in db',
            });
          } else {
            console.error('no user exists in db with that username');
            res.status(401).send('no user exists in db with that username');
          }
        });
      } else {
        console.error('jwt id and username do not match');
        res.status(403).send('username and jwt token do not match');
      }
    })(req, res, next);
  });
};
Enter fullscreen mode Exit fullscreen mode

This route is my findUser route, it gets triggered after the user has logged in and the profile page component starts to mount on the client side to gather all the user info from the database.

For this route’s Swagger documentation, once again, it defines the URL path, the post type, the tags, summary, produces / consumes specs, params, etc.

What’s different is that this route includes the security field, which is where the securityDefinitions I set up in the server.js file come back into play. I am passing a JWT (JSON Web Token) back and forth between my client and server to verify a user’s permissions and authorization, so by including

/**
 * @swagger
 * ...
 * security:
 * - bearerAuth: []
 * ...
 */
Enter fullscreen mode Exit fullscreen mode

it secures the route and requires a user to include a JWT before successfully making the call. bearerAuth is just what I named the object in the server.js file, it can be named however you please, the two just have to match in the server and route files, so Swagger knows how to form the request correctly.

Once the JWT token is passed in and the request is sent, the Passport.js middleware I have picks up the authorization header, decodes the JWT token and verifies it’s good. If you’d like to learn more about setting up Passport with a React / Node.js project, you can read my blog post here.

Here’s the code snippet of the Swagger documentation, I want to point out a couple more things about it that differ from the previous route.

/**
 * @swagger
 * /findUser:
 * get:
 * tags:
 * - Users
 * name: Find user
 * summary: Finds a user
 * security:
 * - bearerAuth: []
 * consumes:
 * - application/json
 * produces:
 * - application/json
 * parameters:
 * - in: query
 * name: username
 * schema:
 * type: string
 * required:
 * - username
 * responses:
 * '200':
 * description: A single user object
 * schema:
 * $ref: '#/definitions/User'
 * '401':
 * description: No auth token / no user found in db with that name
 * '403':
 * description: JWT token and username from client don't match
 */
Enter fullscreen mode Exit fullscreen mode

Aside from the security option, the other difference is in the parameters. As I mentioned in the previous route, if the user input is coming through the request’s query parameters, instead of the request body, the parameters look just a smidge different.

Here’s the piece to focus on — under parameters the -in field becomes query instead of body. This tells Swagger to pass whatever properties are needed in the query params of the request.

/** 
 * @swagger
 * ...
 * parameters:
 * - in: query
 * ...
 */
Enter fullscreen mode Exit fullscreen mode

The other thing to notice is in the responses section at the bottom of the documentation. For the 200 HTTP response, it actually produces an application/json object that holds all the user information. I need to reference the User schema so that Swagger understands how to map the response object it receives without having to write out all the object properties again.

/** 
* @swagger 
* ...
* responses:
* 200:
* description: A single user object
* schema:
* $ref: '#/definitions/User' 
* ...
*/
Enter fullscreen mode Exit fullscreen mode

Good, now I’ve detailed a secure route and an unsecured route with Swagger, now it’s time for a third, slightly different route. One that coincides with a password reset email.

resetPassword.js - the randomly generated string route

/* eslint-disable no-console */
/* eslint-disable max-len */
import Sequelize from 'sequelize';
import User from '../sequelize';

// eslint-disable-next-line prefer-destructuring
const Op = Sequelize.Op;

/**
 * @swagger
 * /reset:
 * get:
 * tags:
 * - Users
 * name: Reset Password Link
 * summary: Create validation string in reset password link to verify user's allowed to reset their password
 * consumes:
 * - application/json
 * parameters:
 * - name: resetPasswordToken
 * in: query
 * schema:
 * type: string
 * required:
 * - resetPasswordToken
 * responses:
 * '200':
 * description: User's password reset link is valid
 * '403':
 * description: Password reset link is invalid or has expired
 */

module.exports = (app) => {
  app.get('/reset', (req, res) => {
    User.findOne({
      where: {
        resetPasswordToken: req.query.resetPasswordToken,
        resetPasswordExpires: {
          [Op.gt]: Date.now(),
        },
      },
    }).then((user) => {
      if (user == null) {
        console.error('password reset link is invalid or has expired');
        res.status(403).send('password reset link is invalid or has expired');
      } else {
        res.status(200).send({
          username: user.username,
          message: 'password reset link a-ok',
        });
      }
    });
  });
};
Enter fullscreen mode Exit fullscreen mode

This is the last route example I’ll cover here, if you’d like to see all my routes (which include all of the basic CRUD-functionality and then some), you can see my GitHub repo.

I include this because this route actually requires a non-expired, randomly generated string which is sent in the body of an email to a user if they’ve forgotten their login password. It’s a little different and a little tricky, so I thought it was worth going over.

If you’d like to read more about sending password reset emails using Node, you can see this blog I wrote.

Here’s the code snippet of the Swagger documentation below.

/**
 * @swagger
 * /reset:
 * get:
 * tags:
 * - Users
 * name: Reset Password Link
 * summary: Create validation string in reset password link to verify user's allowed to reset their password
 * consumes:
 * - application/json
 * parameters:
 * - name: resetPasswordToken
 * in: query
 * schema:
 * type: string
 * required:
 * - resetPasswordToken
 * responses:
 * '200':
 * description: User's password reset link is valid
 * '403':
 * description: Password reset link is invalid or has expired
 */
Enter fullscreen mode Exit fullscreen mode

There’s really not any big differences that you can see, but you will notice, under the parameters the in references a query param agin, meaning that the random string attached the URL link that’s sent to a user’s email address will need to be included to verify the password reset is authorized.

I’ll show this one in the testing section, and it should make more sense there.

Summing it up

Now, you have seen how to setup both swagger-jsdoc and swagger-ui-express in a JavaScript project. I modified the main server.js file, added a data model to the User object schema and wrote Swagger documentation for three separate Node.js routes.

To get to this point, I ended up referencing the OpenAPI Specification docs a lot and the documentation around both npm modules. Don’t be afraid to use either — they’re actually pretty darn good, and there’s a ton of stuff you can add to make your Swagger docs even more helpful.

Ok, that’s finished. Let’s move on to testing the API in the browser.

Testing Swagger in the browser

Testing the API in the browser via Swagger was actually a very good exercise for me, because it uncovered some logic flaws I was making in my code, and it forced me to rethink how I was handling certain situations (like sending error codes and messages from the server to the client).

So, first things first, fire up the whole application or the API individually, whichever you choose. I like to just load the whole thing, because I wrote a single docker-compose.yml file which takes care of it all by writing docker-compose up in the terminal after the initial docker-compose build.

Once the API gets up and running, you can access the in-browser interface, by putting this URL in: http://localhost:3003/api-docs/ — at least, that’s the external port my API’s Docker container is running on, and the / api-docs/ URL path is what I specified in the server.js Swagger UI Express setup.

app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));
Enter fullscreen mode Exit fullscreen mode

If you used something different, reference that URL route instead.

Now, if all goes according to plan, you should see a screen like this.

Swagger dashboard

This is what my Swagger interface looks like when I first hit the URL.

At this point, you can click any of the links and test out your API endpoints. I’m going to test my /loginUser endpoint first.

So I click that post, click the “Try It Out” button in the top right of the expanded view, and then replace the two "string" placeholders with a valid username and password (which would be created in the database using the /registerUser route), and hit the big blue "Execute" button below.

Swagger registerUser route

Filling out the request body (note the "*required" next to the body).

As the image above shows our request body inputs are required ("*required"), so if they’re left as empty string, you’ll get back a 403 "Missing Credentials" response from the API.

All the response codes I’ve written out in the Swagger documentation are there to decipher what just happened. Here’s what a successful, 200 HTTP login response looks like.

Swagger registerUser response

A successful, 200 HTTP response.

When a valid username and password are entered in, which you can see from the cURL request box which shows you exactly what the call was to the API (helpful when debugging calls that aren’t working), the /loginUser route gives back a response body object with a token property that is the JWT token that will be stored in the client’s local storage and passed back in the authorization headers for every secured route call.

I’ll show now how to test one of those routes. I’ll use the /findUser route. First, copy the value from the token property returned from the login endpoint, then open up the /findUser input, and click the little lock in the top right hand of the input box. This will open up a modal for you to input the JWT token string.

Swagger JWT input modal

JWT authorization modal.

In here in the "Value" input box enter JWT <token string>.

Swagger JWT input modal filled in with JWT token

Add in JWT in the input box and hit the "Authorize" button.

For my particular application, the Authorization header that goes with each request, needs the JWT ahead of the actual JWT string. That reference is how Passport finds the correct header string to decode and verify if the user’s authorized. It must be included in this format for this API call to work. After this is done, click the "Authorize" button, then the little lock icon will be locked for this route and you can fill out the user info and test your call.

This is what a 200 HTTP response on this secured route will look like.

Successful response for JWT authorized route

A successful call to the secured route using a JWT token in the "Authorization" header.

The cURL request box shows the route with the username query parameter and it shows the authorization JWT token I entered in the lock on the route, which is needed to return the User object. Without the JWT token, or with an invalid token the 401 or 403 HTTP responses would be sent instead.

The final request I’ll show is a Swagger request that deals with a password reset email (which I wrote about implementing here). That one’s a bit fiddly because the URL link that’s sent to the user’s email address to reset the password contains the query string needed to verify its validity AND it’s only good for the next 10 minutes.

So, if a user was to send a password reset email to their inbox, they’d receive an email with the link like the image below.

Email password reset

The reset email the user receives — the random string at the end is what will be needed for Swagger.

From here, the user would need to copy the string after the /reset/ and paste that into the swagger /reset route within 10 minutes of it being sent. This randomly generated string has a timestamp attached to it, and part of the authorization involves checking the string and the timestamp against the database.

If both are valid for that user, here’s what a successful verification through Swagger would look like.

Swagger reset route 200

The successful 200 response to verify the password reset token for this user is valid, which allows them to reach the secured route to reset their password.

If the string is invalid or the link had expired, instead Swagger would show this message.

Swagger reset route 403

The 403 error a user would get if s/he were to submit a password reset token that was either invalid or had expired.

And that, my friends, is how you can test your API using Swagger’s handy interface in the browser.


Conclusion

This is how to implement Swagger in your JavaScript or Node.js application for in-browser API testing. The initial setup isn’t quite as easy as Spring Boot makes it, but for an interpreted language like JavaScript, it really wasn’t that hard, once I got the hang of it.

Check back in a few weeks — I’ll be writing more about JavaScript, React, or something else related to web development.

If you’d like to make sure you never miss an article I write, sign up for my newsletter here: https://paigeniedringhaus.substack.com

Thanks for reading, I hope this helps you more easily document and test your own web apps.


References & Further Resources

Top comments (0)