DEV Community

Cover image for Adding live documentation to YOUR REST API with Swagger and Nest.js
Chris Noring for ITNEXT

Posted on • Updated on

Adding live documentation to YOUR REST API with Swagger and Nest.js

We'll want to document our code and our APIs. What's stopping us is usually time or that we know that the documentation and the actual code will drift and that's just worse than no documentation at all. So we need something that changes with us, that something is Swagger, the fact that it enables you to run queries against it is an added bonus.

This is what it can feel like sometimes with documentation. You spend all this time writing documentation and then someone, might be you, might be your colleague does a change and suddenly, you are out of sync, feels like a struggle, no?

Swagger is live documentation though, it changes as the code changes. So hopefully you will give it a shot after reading this article.

In this article, we will show how easy it is to set up Swagger in your Nest.js app. It's just one config place and a few DTOs that needs documenting.

We will show the following:

  • Scaffolding a Nest.js app and some needed artifacts like modules, services, DTOs
  • Setting up Swagger and see how easy it is to get your endpoints documented
  • Exploring Swaggers features like executing queries and inspect results
  • Improving our documentation even more by adding decorators to our DTOs.

Resources

Scaffolding our Nest.js project

Let's create a new Nest.js project by using the excellent Nest CLI. If you haven't got it installed go and do so by running the following command in the terminal:

npm i -g @nestjs/cli
Enter fullscreen mode Exit fullscreen mode

Done?

Ok, good. Let's continue.

To create a nest project we just have to call nest new [project name] so lets do just that:

nest new swagger-demo
Enter fullscreen mode Exit fullscreen mode

It should look like this:

Next step is to set up a route. Let's do this in a modular way and create a module, service and a DTO. Lots of typing you say? No not really as we are using the CLI. Let's check what the CLI can do with:

nest --help
Enter fullscreen mode Exit fullscreen mode

It's telling us to type pretty much:

nest generate|g [options] <schematic> [name] [path]
Enter fullscreen mode Exit fullscreen mode

Sounds a bit cryptic but to create a module we would need to type:

nest g mo cats
Enter fullscreen mode Exit fullscreen mode

We need a controller as well that will respond to our requests. So that would be:

nest g co cats
Enter fullscreen mode Exit fullscreen mode

For a service we would type:

nest g s cats
Enter fullscreen mode Exit fullscreen mode

Note two things about the controller + service creation. They are created under the cats directory, they come with tests :) and they have both registered themselves with the module, look at the UPDATE row at the bottom row.

Lastly, we want to create a DTO, a data transfer object that will hold our properties. We do that by typing the following:

nest g cl cat cats
Enter fullscreen mode Exit fullscreen mode

Before we run this let's discuss what we are typing. We are saying to create a class cl, called cat under path cats. We do this to ensure that all related things end up in one place.

 Making the route work

So far we have a bunch of files, but we need the route to work so we need to do the following:

  1. Add id and name to our cat model
  2. Ensure the Service has a getCats() method that returns a list of cats
  3. Make the controller inject the cats service and call getCats()
  4. Take our API for a spin and ensure /cats work

 Adding id and name to our Model

Ensure src/cats/cat.ts looks like this:

export class Cat {
  id: number;
  name: string;
}
Enter fullscreen mode Exit fullscreen mode

Update our Service

We need to add two methods getCats() and createCat(), this will ensure once we add Swagger on this that we have a GET and a POST request.

import { Injectable } from '@nestjs/common';
import { Cat } from './cat';

@Injectable()
export class CatsService {
  cats: Array<Cat> = [{ id: 1, name: 'Cat'}];

  getCats() {
    return this.cats;
  }

  createCat(cat: Cat) {
    this.cats = [ ...this.cats, {...cat}];
  }
}
Enter fullscreen mode Exit fullscreen mode

Make Controller use the Service

Our controller should look like this:

import { Controller, Get, Post, Body } from '@nestjs/common';
import { CatsService } from './cats.service';
import { Cat } from './cat';


@Controller('cats')
export class CatsController {
  constructor(private srv: CatsService) {}

  @Get()
  getCats() {
    return this.srv.getCats();
  }

  @Post()
  createCat(@Body() cat: Cat) {
    this.srv.createCat(cat);
  }
}
Enter fullscreen mode Exit fullscreen mode

The above simply ensure we are using our CatsService for either getting a list of cats or for adding a cat.

Take it for a Spin

We need to ensure our route works before we start showing Swagger. So run:

npm start
Enter fullscreen mode Exit fullscreen mode

and head to the browser on http://localhost:3000/cats. It should look like so:

Adding Swagger

Now we will add Swagger. To get Swagger to work we need to do the following:

  1. Install the needed dependencies
  2. Configure our bootstrap to start using Swagger
  3. Ensure Swagger is rendering in the browser

 Installing Swagger

We need to install through the NPM with the following command:

npm install --save @nestjs/swagger swagger-ui-express
Enter fullscreen mode Exit fullscreen mode

That should set us up nicely, now to our next step, configuring it.

Configuration

Head to main.ts our bootstrap file. In our bootstrap() method it currently looks like so:

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  await app.listen(3000);
Enter fullscreen mode Exit fullscreen mode

We need to add the following between us declaring app and calling listen() on it, namely:

  const options = new DocumentBuilder()
    .setTitle('My API')
    .setDescription('API description')
    .setVersion('1.0')
    .build();
  const document = SwaggerModule.createDocument(app, options);
  SwaggerModule.setup('api', app, document);
Enter fullscreen mode Exit fullscreen mode

First, we create an options object that gets a title, description, version and finally we call build() which ends up creating an options object. Thereafter we create a document instance by calling createDocument() on a SwaggerModule. It takes our app instance and the options object we just created. The last thing we do is calling setup() on the SwaggerModule. The first argument is a path, which means we will found our API docs under http://localhost:3000/api. The next argument is our app and the last argument is the document instance. Our main.ts should now look like this in its entirety:

// main.ts

import { NestFactory } from '@nestjs/core';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const options = new DocumentBuilder()
    .setTitle('My API')
    .setDescription('API description')
    .setVersion('1.0')
    .build();
  const document = SwaggerModule.createDocument(app, options);
  SwaggerModule.setup('api', app, document);

  await app.listen(3000);
}
bootstrap();
Enter fullscreen mode Exit fullscreen mode

Trying out the docs

Lets first start up our app

npm start
Enter fullscreen mode Exit fullscreen mode

Thereafter head to http://localhost:3000/api. Then you should see the following:

This is as expected. We have the default route / set up in the app.controller file. We also have a GET for /cats and a POST also for /cats. So far so good.

The million dollar question then, does it work? Well, let's start with GET and /cats

Click the Try it out button. At this point, it will show the Execute button, click that as well. It should give you the below

It answers with our list of cats. We also get a nice cURL version, if we want to use that instead. We can also see the exact response headers we get back, should we want to verify that.

What about our POST request? Well, let's click that instead and our Try it out.

We get a big edit window in which we type some JSON that corresponds to a new cat we want to create so:

{
  "id": "2",
  "name": "cat2"
}
Enter fullscreen mode Exit fullscreen mode

Hitting our Execute button gives the below response:

As you can see we get a 201, which means we have a new cat. Lets ensure that is the case by hitting our GET /cats in Swagger:

Success, there are now two cats. Let's look at how we can improve next.

 Improve our docs

If we scroll to the bottom of our Swagger docs page we have a category Models. It contains Cat our DTO class. It's completely empty though and that makes for sad reading. We can easily fix this though.

What we need to do is to use the decorator @ApiModelProperty() and apply those to every property of Cat, like so:

Your cats/cat.ts should now look like this:

import { ApiModelProperty } from "@nestjs/swagger";

export class Cat {
  @ApiModelProperty()
  id: number;

  @ApiModelProperty()
  name: string;
}
Enter fullscreen mode Exit fullscreen mode

Let's our app again:

npm start
Enter fullscreen mode Exit fullscreen mode

and go to http://localhost:3000/api and scroll to the bottom:

There we are, now our class properties are included in the docs as well

 Summary

That's it. We got a chance to use lovely Nest again. This time we used a few more commands to learn to scaffold all the files we needed. Most of all we learned how to document our API with Swagger. Documentation that changes as the code changes are worth keeping around. So gift your API some docs as well.

Discussion (6)

Collapse
quocthangit247 profile image
Rainbow007

i don't know how to show a custom type like below:

type ExamDto{
exam1: string;
exam2: number;
}

@ApiModelProperty()
id: ExamDto[];

Can you show me how to display detail id with properties of ExamDto ? Thank a lots <3

Collapse
softchris profile image
Chris Noring Author

can't be done on types I'm afraid. It's impossible to reflect Types on TypeScript in runtime. If you can, I recommend using classes

Collapse
arunmurugan78 profile image
Arun Murugan

Thanks! This article helped me :)

Collapse
aralvesandrade profile image
Alexandre Andrade

import { ApiProperty } from "@nestjs/swagger";

export class Cat {
@ApiProperty()
id: number;

@ApiProperty()
name: string;
}