NestJS is one of the prominent Node.js frameworks, and it has recently gained a lot of developer love and traction. This article will teach you how to build a backend REST API with NestJS, Prisma, PostgreSQL and Swagger.
Table Of Contents
- Introduction
- Prerequisites
- Generate the NestJS project
- Create a PostgreSQL instance
- Set up Prisma
- Set up Swagger
- Implement CRUD operations for
Article
model - Update Swagger response types
- Summary and final remarks
Introduction
In this tutorial, you will learn how to build the backend REST API for a blog application called "Median" (a simple Medium clone). You will get started by creating a new NestJS project. Then you will start your own PostgreSQL server and connect to it using Prisma. Finally, you will build the REST API and document it with Swagger.
Technologies you will use
You will be using the following tools to build this application:
- NestJS as the backend framework
- Prisma as the Object-Relational Mapper (ORM)
- PostgreSQL as the database
- Swagger as the API documentation tool
- TypeScript as the programming language
Prerequisites
Assumed knowledge
This is a beginner friendly tutorial. However, this tutorial assumes:
- Basic knowledge of JavaScript or TypeScript (preferred)
- Basic knowledge of NestJS
Note: If you're not familiar with NestJS, you can quickly learn the basics by following the overview section in the NestJS docs.
Development environment
To follow along with this tutorial, you will be expected to:
- ... have Node.js installed.
- ... have Docker or PostgreSQL installed.
- ... have the Prisma VSCode Extension installed. (optional)
- ... have access to a Unix shell (like the terminal/shell in Linux and macOS) to run the commands provided in this series. (optional)
Note 1: The optional Prisma VSCode extension adds some really nice IntelliSense and syntax highlighting for Prisma.
Note 2: If you don't have a Unix shell (for example, you are on a Windows machine), you can still follow along, but the shell commands may need to be modified for your machine.
Generate the NestJS Project
The first thing you will need is to install the NestJS CLI. The NestJS CLI comes in very handy when working with a NestJS project. It comes with built-in utilities that help you initialize, develop and maintain your NestJS application.
You can use the NestJS CLI to create an empty project. To start, run the following command in the location where you want the project to reside:
npx @nestjs/cli new medianCopy
The CLI will prompt you to choose a package manager for your project — choose npm. Afterward, you should have a new NestJS project in the current directory.
Open the project in your preferred code editor (we recommend VSCode). You should see the following files:
median ├── node_modules ├── src │ ├── app.controller.spec.ts │ ├── app.controller.ts │ ├── app.module.ts │ ├── app.service.ts │ └── main.ts ├── test │ ├── app.e2e-spec.ts │ └── jest-e2e.json ├── README.md ├── nest-cli.json ├── package-lock.json ├── package.json ├── tsconfig.build.json └── tsconfig.json
Most of the code you work on will reside in the src
directory. The NestJS CLI has already created a few files for you. Some of the notable ones are:
src/app.module.ts
: The root module of the application.src/app.controller.ts
: A basic controller with a single route:/
. This route will return a simple'Hello World!'
message.src/main.ts
: The entry point of the application. It will start the NestJS application.
You can start your project by using the following command:
npm run start:devCopy
This command will watch your files, automatically recompiling and reloading the server whenever you make a change. To verify the server is running, go to the URL http://localhost:3000/
. You should see an empty page with the message 'Hello World!'
.
Note: You should keep the server running in the background as you go through this tutorial.
Create a PostgreSQL instance
You will be using PostgreSQL as the database for your NestJS application. This tutorial will show you how to install and run PostgreSQL on your machine through a Docker container.
Note: If you don't want to use Docker, you can set up a PostgreSQL instance natively or get a hosted PostgreSQL database on Heroku.
First, create a docker-compose.yml
file in the main folder of your project:
touch docker-compose.ymlCopy
This docker-compose.yml
file is a configuration file that will contain the specifications for running a docker container with PostgreSQL setup inside. Create the following configuration inside the file:
# docker-compose.ymlversion: '3.8'services:postgres:image: postgres:13.5restart: alwaysenvironment:- POSTGRES_USER=myuser- POSTGRES_PASSWORD=mypasswordvolumes:- postgres:/var/lib/postgresql/dataports:- '5432:5432'volumes:postgres:Copy
A few things to understand about this configuration:
- The
image
option defines what Docker image to use. Here, you are using thepostgres
image version 13.5. - The
environment
option specifies the environment variables passed to the container during initialization. You can define the configuration options and secrets – such as the username and password – the container will use here. - The
volumes
option is used for persisting data in the host file system. - The
ports
option maps ports from the host machine to the container. The format follows a'host_port:container_port'
convention. In this case, you are mapping the port5432
of the host machine to port5432
of thepostgres
container.5432
is conventionally the port used by PostgreSQL.
Make sure that nothing is running on port 5432
of your machine. To start the postgres
container, open a new terminal window and run the following command in the main folder of your project:
docker-compose upCopy
If everything worked correctly, the new terminal window should show logs that the database system is ready to accept connections. You should see logs similar to the following inside the terminal window:
...postgres_1 | 2022-03-05 12:47:02.410 UTC [1] LOG: listening on IPv4 address "0.0.0.0", port 5432postgres_1 | 2022-03-05 12:47:02.410 UTC [1] LOG: listening on IPv6 address "::", port 5432postgres_1 | 2022-03-05 12:47:02.411 UTC [1] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"postgres_1 | 2022-03-05 12:47:02.419 UTC [1] LOG: database system is ready to accept connections
Congratulations 🎉. You now have your own PostgreSQL database to play around with!
Note: If you close the terminal window, it will also stop the container. You can avoid this if you add a
-d
option to the end of the command, like this:docker-compose up -d
. This will indefinitely run the container in the background.
Set up Prisma
Now that the database is ready, it's time to set up Prisma!
Initialize Prisma
To get started, first install the Prisma CLI as a development dependency. The Prisma CLI will allow you to run various commands and interact with your project.
npm install -D prismaCopy
You can initialize Prisma inside your project by running:
npx prisma initCopy
This will create a new prisma
directory with a schema.prisma
file. This is the main configuration file that contains your database schema. This command also creates a .env
file inside your project.
Set your environment variable
Inside the .env
file, you should see a DATABASE_URL
environment variable with a dummy connection string. Replace this connection string with the one for your PostgreSQL instance.
// .envDATABASE_URL="postgres://myuser:mypassword@localhost:5432/median-db"Copy
Note: If you didn't use docker (as shown in the previous section) to create your PostgreSQL database, your connection string will be different from the one shown above. The connection string format for PostgreSQL is available in the Prisma Docs.
Understand the Prisma schema
If you open prisma/schema.prisma
, you should see the following default schema:
// prisma/schema.prisma
generator client { provider = "prisma-client-js"}
datasource db { provider = "postgresql" url = env("DATABASE_URL")}
This file is written in the Prisma Schema Language, which is a language that Prisma uses to define your database schema. The schema.prisma
file has three main components:
- Data source: Specifies your database connection. The above configuration means that your database provider is PostgreSQL and the database connection string is available in the
DATABASE_URL
environment variable. - Generator: Indicates that you want to generate Prisma Client, a type-safe query builder for your database. It is used to send queries to your database.
- Data model: Defines your database models. Each model will be mapped to a table in the underlying database. Right now there are no models in your schema, you will explore this part in the next section.
Note: For more information on Prisma schema, check out the Prisma docs.
Model the data
Now it's time to define the data models for your application. For this tutorial, you will only need an Article
model to represent each article on the blog.
Inside the prisma/prisma.schema
file, add a new model to your schema named Article
:
// prisma/schema.prismamodel Article {id Int @id @default(autoincrement())title String @uniquedescription String?body Stringpublished Boolean @default(false)createdAt DateTime @default(now())updatedAt DateTime @updatedAt}Copy
Here, you have created an Article
model with several fields. Each field has a name (id
, title
, etc.), a type (Int
, String
, etc.), and other optional attributes (@id
, @unique
, etc.). Fields can be made optional by adding a ?
after the field type.
The id
field has a special attribute called @id
. This attribute indicates that this field is the primary key of the model. The @default(autoincrement())
attribute indicates that this field should be automatically incremented and assigned to any newly created record.
The published
field is a flag to indicate whether an article is published or in draft mode. The @default(false)
attribute indicates that this field should be set to false
by default.
The two DateTime
fields, createdAt
and updatedAt
, will track when an article is created and when it was last updated. The @updatedAt
attribute will automatically update the field with the current timestamp whenever an article is modified.date the field with the current timestamp any time an article is modified.
Migrate the database
With the Prisma schema defined, you will run migrations to create the actual tables in the database. To generate and execute your first migration, run the following command in the terminal:
npx prisma migrate dev --name "init"Copy
This command will do three things:
- Save the migration: Prisma Migrate will take a snapshot of your schema and figure out the SQL commands necessary to carry out the migration. Prisma will save the migration file containing the SQL commands to the newly created
prisma/migrations
folder. - Execute the migration: Prisma Migrate will execute the SQL in the migration file to create the underlying tables in your database.
- Generate Prisma Client: Prisma will generate Prisma Client based on your latest schema. Since you did not have the Client library installed, the CLI will install it for you as well. You should see the
@prisma/client
package insidedependencies
in yourpackage.json
file. Prisma Client is a TypeScript query builder auto-generated from your Prisma schema. It is tailored to your Prisma schema and will be used to send queries to the database.
Note: You can learn more about Prisma Migrate in the Prisma docs.
If completed successfully, you should see a message like this :
The following migration(s) have been created and applied from new schema changes:
migrations/ └─ 20220528101323_init/ └─ migration.sql
Your database is now in sync with your schema....✔ Generated Prisma Client (3.14.0 | library) to ./node_modules/@prisma/client in 31ms
Check the generated migration file to get an idea about what Prisma Migrate is doing behind the scenes:
-- prisma/migrations/20220528101323_init/migration.sql
-- CreateTableCREATE TABLE "Article" ( "id" SERIAL NOT NULL, "title" TEXT NOT NULL, "description" TEXT, "body" TEXT NOT NULL, "published" BOOLEAN NOT NULL DEFAULT false, "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Article_pkey" PRIMARY KEY ("id"));
-- CreateIndexCREATE UNIQUE INDEX "Article_title_key" ON "Article"("title");
Note: The name of your migration file will be slightly different.
This is the SQL needed to create the Article
table inside your PostgreSQL database. It was automatically generated and executed by Prisma based on your Prisma schema.
Seed the database
Currently, the database is empty. So you will create a seed script that will populate the database with some dummy data.
Firstly, create a seed file called prisma/seed.ts
. This file will contain the dummy data and queries needed to seed your database.
touch prisma/seed.tsCopy
Then, inside the seed file, add the following code:
// prisma/seed.tsimport { PrismaClient } from '@prisma/client';// initialize Prisma Clientconst prisma = new PrismaClient();async function main() {// create two dummy articlesconst post1 = await prisma.article.upsert({where: { title: 'Prisma Adds Support for MongoDB' },update: {},create: {title: 'Prisma Adds Support for MongoDB',body: 'Support for MongoDB has been one of the most requested features since the initial release of...',description:"We are excited to share that today's Prisma ORM release adds stable support for MongoDB!",published: false,},});const post2 = await prisma.article.upsert({where: { title: "What's new in Prisma? (Q1/22)" },update: {},create: {title: "What's new in Prisma? (Q1/22)",body: 'Our engineers have been working hard, issuing new releases with many improvements...',description:'Learn about everything in the Prisma ecosystem and community from January to March 2022.',published: true,},});console.log({ post1, post2 });}// execute the main functionmain().catch((e) => {console.error(e);process.exit(1);}).finally(async () => {// close Prisma Client at the endawait prisma.$disconnect();});Copy
Inside this script, you first initialize Prisma Client. Then you create two articles using the prisma.upsert()
function. The upsert
function will only create a new article if no article matches the where
condition. You are using an upsert
query instead of a create
query because upsert
removes errors related to accidentally trying to insert the same record twice.
You need to tell Prisma what script to execute when running the seeding command. You can do this by adding the prisma.seed
key to the end of your package.json
file:
// package.json// ..."scripts": {// ...},"dependencies": {// ...},"devDependencies": {// ...},"jest": {// ...},"prisma": {"seed": "ts-node prisma/seed.ts"}Copy
The seed
command will execute the prisma/seed.ts
script that you previously defined. This command should work automatically because ts-node
is already installed as a dev dependency in your package.json
.
Execute seeding with the following command:
npx prisma db seedCopy
You should see the following output:
Running seed command `ts-node prisma/seed.ts` ...{ post1: { id: 1, title: 'Prisma Adds Support for MongoDB', description: "We are excited to share that today's Prisma ORM release adds stable support for MongoDB!", body: 'Support for MongoDB has been one of the most requested features since the initial release of...', published: false, createdAt: 2022-04-24T14:20:27.674Z, updatedAt: 2022-04-24T14:20:27.674Z }, post2: { id: 2, title: "What's new in Prisma? (Q1/22)", description: 'Learn about everything in the Prisma ecosystem and community from January to March 2022.', body: 'Our engineers have been working hard, issuing new releases with many improvements...', published: true, createdAt: 2022-04-24T14:20:27.705Z, updatedAt: 2022-04-24T14:20:27.705Z }}
🌱 The seed command has been executed.
Note: You can learn more about seeding in the Prisma Docs.
Create a Prisma service
Inside your NestJS application, it is good practice to abstract away the Prisma Client API from your application. To do this, you will create a new service that will contain Prisma Client. This service, called PrismaService
, will be responsible for instantiating a PrismaClient
instance and connecting to your database.
The Nest CLI gives you an easy way to generate modules and services directly from the CLI. Run the following command in your terminal:
npx nest generate module prismanpx nest generate service prismaCopy
Note 1: If necessary, refer to the NestJS docs for an introduction to services and modules.
Note 2: In some cases running the
nest generate
command with the server already running may result in NestJS throwing an exception that says:Error: Cannot find module './app.controller'
. If you run into this error, run the following command from the terminal:rm -rf dist
and restart the server.
This should generate a new subdirectory ./src/prisma
with a prisma.module.ts
and prisma.service.ts
file. The service file should contain the following code:
// src/prisma/prisma.service.tsimport { INestApplication, Injectable } from '@nestjs/common';import { PrismaClient } from '@prisma/client';@Injectable()export class PrismaService extends PrismaClient {}Copy
The Prisma module will be responsible for creating a singleton instance of the PrismaService
and allow sharing of the service throughout your application. To do this, you will add the PrismaService
to the exports
array in the prisma.module.ts
file:
// src/prisma/prisma.module.tsimport { Module } from '@nestjs/common';import { PrismaService } from './prisma.service';@Module({providers: [PrismaService],exports: [PrismaService],})export class PrismaModule {}Copy
Now, any module that imports the PrismaModule
will have access to PrismaService
and can inject it into its own components/services. This is a common pattern for NestJS applications.
With that out of the way, you are done setting up Prisma! You can now get to work on building the REST API.
Set up Swagger
Swagger is a tool to document your API using the OpenAPI specification. Nest has a dedicated module for Swagger, which you will be using shortly.
Get started by installing the required dependencies:
npm install --save @nestjs/swagger swagger-ui-expressCopy
Now open main.ts
and initialize Swagger using the SwaggerModule
class:
// src/main.tsimport { NestFactory } from '@nestjs/core';import { AppModule } from './app.module';import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';async function bootstrap() {const app = await NestFactory.create(AppModule);const config = new DocumentBuilder().setTitle('Median').setDescription('The Median API description').setVersion('0.1').build();const document = SwaggerModule.createDocument(app, config);SwaggerModule.setup('api', app, document);await app.listen(3000);}bootstrap();Copy
While the application is running, open your browser and navigate to http://localhost:3000/api
. You should see the Swagger UI.
Implement CRUD operations for Article
model
In this section, you will implement the Create, Read, Update, and Delete (CRUD) operations for the Article
model and any accompanying business logic.
Generate REST resources
Before you can implement the REST API, you will need to generate the REST resources for the Article
model. This can be done quickly using the Nest CLI. Run the following command in your terminal:
npx nest generate resourceCopy
You will be given a few CLI prompts. Answer the questions accordingly:
What name would you like to use for this resource (plural, e.g., "users")?
articlesWhat transport layer do you use?
REST APIWould you like to generate CRUD entry points?
Yes
You should now find a new src/articles
directory with all the boilerplate for your REST endpoints. Inside the src/articles/articles.controller.ts
file, you will see the definition of different routes (also called route handlers). The business logic for handling each request is encapsulated in the src/articles/articles.service.ts
file. Currently, this file contains dummy implementations.
If you open the Swagger API page again, you should see something like this:
The SwaggerModule
searches for all @Body()
, @Query()
, and @Param()
decorators on the route handlers to generate this API page.
Add PrismaClient
to the Articles
module
To access PrismaClient
inside the Articles
module, you must add the PrismaModule
as an import. Add the following imports
to ArticlesModule
:
// src/articles/articles.module.tsimport { Module } from '@nestjs/common';import { ArticlesService } from './articles.service';import { ArticlesController } from './articles.controller';import { PrismaModule } from 'src/prisma/prisma.module';@Module({controllers: [ArticlesController],providers: [ArticlesService],imports: [PrismaModule],})export class ArticlesModule {}Copy
You can now inject the PrismaService
inside the ArticlesService
and use it to access the database. To do this, add a constructor to articles.service.ts
like this:
// src/articles/articles.service.tsimport { Injectable } from '@nestjs/common';import { CreateArticleDto } from './dto/create-article.dto';import { UpdateArticleDto } from './dto/update-article.dto';import { PrismaService } from 'src/prisma/prisma.service';@Injectable()export class ArticlesService {constructor(private prisma: PrismaService) {}// CRUD operations}Copy
Define GET /articles
endpoint
The controller for this endpoint is called findAll
. This endpoint will return all published articles in the database. The findAll
controller looks like this:
// src/articles/articles.controller.ts
@Get()findAll() { return this.articlesService.findAll();}
You need to update ArticlesService.findAll()
to return an array of all published articles in the database:
// src/articles/articles.service.ts@Injectable()export class ArticlesService {constructor(private prisma: PrismaService) {}create(createArticleDto: CreateArticleDto) {return 'This action adds a new article';}findAll() {return `This action returns all articles`;return this.prisma.article.findMany({ where: { published: true } });}Copy
The findMany
query will return all article
records that match the where
condition.
You can test out the endpoint by going to http://localhost:3000/api
and
clicking on the GET/articles dropdown menu. Press Try it out and then Execute to see the result.
Note: You can also run all requests in the browser directly or through a REST client (like Postman). Swagger also generates the curl commands for each request in case you want to run the HTTP requests in the terminal.
Define GET /articles/drafts
endpoint
You will define a new route to fetch all unpublished articles. NestJS did not automatically generate the controller route handler for this endpoint, so you have to write it yourself.
// src/articles/articles.controller.ts@Controller('articles')export class ArticlesController {constructor(private readonly articlesService: ArticlesService) {}@Post()create(@Body() createArticleDto: CreateArticleDto) {return this.articlesService.create(createArticleDto);}@Get('drafts')findDrafts() {return this.articlesService.findDrafts();}// ...}Copy
Your editor should show an error that no function called articlesService.findDrafts()
exists. To fix this, implement the findDrafts
method in ArticlesService
:
// src/articles/articles.service.ts@Injectable()export class ArticlesService {constructor(private prisma: PrismaService) {}create(createArticleDto: CreateArticleDto) {return 'This action adds a new article';}findDrafts() {return this.prisma.article.findMany({ where: { published: false } });}// ...}Copy
The GET /articles/drafts
endpoint will now be available in the Swagger API page.
Note: I recommend testing out each endpoint through the Swagger API page once you finish implementing it.
Define GET /articles/:id
endpoint
The controller route handler for this endpoint is called findOne
. It looks like this:
// src/articles/articles.controller.ts
@Get(':id')findOne(@Param('id') id: string) { return this.articlesService.findOne(+id);}
The route accepts a dynamic id
parameter, which is passed to the findOne
controller route handler. Since the Article
model has an integer id
field, the id
parameter needs to be casted to a number using the +
operator.
Now, update the findOne
method in the ArticlesService
to return the article with the given id:
// src/articles/articles.service.ts@Injectable()export class ArticlesService {constructor(private prisma: PrismaService) {}create(createArticleDto: CreateArticleDto) {return 'This action adds a new article';}findAll() {return this.prisma.article.findMany({ where: { published: true } });}findOne(id: number) {return `This action returns a #${id} article`;return this.prisma.article.findUnique({ where: { id } });}}Copy
Once again, test out the endpoint by going to http://localhost:3000/api
. Click on the GET /articles/{id} dropdown menu. Press Try it out, add a valid value to the id parameter, and press Execute to see the result.
Define POST /articles
endpoint
This is the endpoint for creating new articles. The controller route handler for this endpoint is called create
. It looks like this:
// src/articles/articles.controller.ts
@Post()create(@Body() createArticleDto: CreateArticleDto) { return this.articlesService.create(createArticleDto);}
Notice that it expects arguments of type CreateArticleDto
in the request body. A DTO (Data Transfer Object) is an object that defines how the data will be sent over the network. Currently, the CreateArticleDto
is an empty class. You will add properties to it to define the shape of the request body.
// src/articles/dto/create-article.dto.tsimport { ApiProperty } from '@nestjs/swagger';export class CreateArticleDto {@ApiProperty()title: string;@ApiProperty({ required: false })description?: string;@ApiProperty()body: string;@ApiProperty({ required: false, default: false })published?: boolean = false;}Copy
The @ApiProperty
decorators are required to make the class properties visible to the SwaggerModule
. More information about this is available in the NestJS docs.
The CreateArticleDto
should now be defined in the Swagger API page under Schemas. The shape of UpdateArticleDto
is automatically inferred from the CreateArticleDto
definition. So UpdateArticleDto
is also defined inside Swagger.
Now update the create
method in the ArticlesService
to create a new article in the database:
// src/articles/articles.service.ts@Injectable()export class ArticlesService {constructor(private prisma: PrismaService) {}create(createArticleDto: CreateArticleDto) {return 'This action adds a new article';return this.prisma.article.create({ data: createArticleDto });}// ...}Copy
Define PATCH /articles/:id
endpoint
This endpoint is for updating existing articles. The route handler for this endpoint is called update
. It looks like this:
// src/articles/articles.controller.ts
@Patch(':id')update(@Param('id') id: string, @Body() updateArticleDto: UpdateArticleDto) { return this.articlesService.update(+id, updateArticleDto);}
The updateArticleDto
definition is defined as a PartialType
of CreateArticleDto
. So it can have all the properties of CreateArticleDto
.
// src/articles/dto/update-article.dto.ts
import { PartialType } from '@nestjs/swagger';import { CreateArticleDto } from './create-article.dto';
export class UpdateArticleDto extends PartialType(CreateArticleDto) {}
Just like before, you must update the corresponding service method for this operation:
// src/articles/articles.service.ts@Injectable()export class ArticlesService {constructor(private prisma: PrismaService) {}// ...update(id: number, updateArticleDto: UpdateArticleDto) {return `This action updates a #${id} article`;return this.prisma.article.update({where: { id },data: updateArticleDto,});}// ...}Copy
The article.update
operation will try to find an Article
record with the given id
and update it with the data of updateArticleDto
.
If no such Article
record is found in the database, Prisma will return an error. In such cases, the API does not return a user-friendly error message. You will learn about error handling with NestJS in a future tutorial.
Define DELETE /articles/:id
endpoint
This endpoint is to delete existing articles. The route handler for this endpoint is called remove
. It looks like this:
// src/articles/articles.controller.ts
@Delete(':id')remove(@Param('id') id: string) { return this.articlesService.remove(+id);}
Just like before, go to ArticlesService
and update the corresponding method:
// src/articles/articles.service.ts@Injectable()export class ArticlesService {constructor(private prisma: PrismaService) { }// ...remove(id: number) {return `This action removes a #${id} article`;return this.prisma.article.delete({ where: { id } });}}Copy
That was the last operation for the articles
endpoint. Congratulations your API is almost ready! 🎉
Group endpoints together in Swagger
Add an @ApiTags
decorator to the ArticlesController
class, to group all the articles
endpoints together in Swagger:
// src/articles/articles.controller.tsimport { ApiTags } from '@nestjs/swagger';@Controller('articles')@ApiTags('articles')export class ArticlesController {// ...}Copy
The API page now has the articles
endpoints grouped together.
Update Swagger response types
If you look at the Responses tab under each endpoint in Swagger, you will find that the Description is empty. This is because Swagger does not know the response types for any of the endpoints. You're going to fix this using a few decorators.
First, you need to define an entity that Swagger can use to identify the shape of the returned entity
object. To do this, update the ArticleEntity
class in the articles.entity.ts
file as follows:
// src/articles/entities/article.entity.tsimport { Article } from '@prisma/client';import { ApiProperty } from '@nestjs/swagger';export class ArticleEntity implements Article {@ApiProperty()id: number;@ApiProperty()title: string;@ApiProperty({ required: false, nullable: true })description: string | null;@ApiProperty()body: string;@ApiProperty()published: boolean;@ApiProperty()createdAt: Date;@ApiProperty()updatedAt: Date;}Copy
This is an implementation of the Article
type generated by Prisma Client, with @ApiProperty
decorators added to each property.
Now, it's time to annotate the controller route handlers with the correct response types. NestJS has a set of decorators for this purpose.
// src/articles/articles.controller.tsimport { ApiCreatedResponse, ApiOkResponse, ApiTags } from '@nestjs/swagger';import { ArticleEntity } from './entities/article.entity';@Controller('articles')@ApiTags('articles')export class ArticlesController {constructor(private readonly articlesService: ArticlesService) {}@Post()@ApiCreatedResponse({ type: ArticleEntity })create(@Body() createArticleDto: CreateArticleDto) {return this.articlesService.create(createArticleDto);}@Get()@ApiOkResponse({ type: ArticleEntity, isArray: true })findAll() {return this.articlesService.findAll();}@Get('drafts')@ApiOkResponse({ type: ArticleEntity, isArray: true })findDrafts() {return this.articlesService.findDrafts();}@Get(':id')@ApiOkResponse({ type: ArticleEntity })findOne(@Param('id') id: string) {return this.articlesService.findOne(+id);}@Patch(':id')@ApiOkResponse({ type: ArticleEntity })update(@Param('id') id: string, @Body() updateArticleDto: UpdateArticleDto) {return this.articlesService.update(+id, updateArticleDto);}@Delete(':id')@ApiOkResponse({ type: ArticleEntity })remove(@Param('id') id: string) {return this.articlesService.remove(+id);}}Copy
You added the @ApiOkResponse
for GET
, PATCH
and DELETE
endpoints and @ApiCreatedResponse
for POST
endpoints. The type
property is used to specify the return type. You can find all the response decorators that NestJS provides in the NestJS docs.
Now, Swagger should properly define the response type for all endpoints on the API page.
Summary and final remarks
Congratulations! You've built a rudimentary REST API using NestJS. Throughout this tutorial you:
- Built a REST API with NestJS
- Smoothly integrated Prisma in a NestJS project
- Documented your REST API using Swagger and OpenAPI
One of the main takeaways from this tutorial is how easy it is to build a REST API with NestJS and Prisma. This is an incredibly productive stack for rapidly building well structured, type-safe and maintainable backend applications.
You can find the source code for this project on GitHub. Please feel free to raise an issue in the repository or submit a PR if you notice a problem. You can also reach out to me directly on Twitter.