← Back to Blog

Building a REST API with NestJS and Prisma

Tasin Ishmam
Tasin Ishmam
June 3, 2022
Updated July 8, 2026
20 min read

NestJS is one of the most popular Node.js frameworks for building structured, type-safe server-side applications, and Prisma ORM gives it a type-safe database layer. This tutorial is the first part of a five-part series on building a REST API with NestJS and Prisma ORM. In it, you will generate a NestJS project, connect it to PostgreSQL with Prisma ORM 7, build CRUD endpoints for a blog application called "Median", and document the API with Swagger.

Updated (July 2026): This tutorial has been fully revised for Prisma ORM 7 and NestJS 11. Prisma 7 is Rust-free and uses driver adapters, generates the Prisma Client into your project (instead of node_modules), and is configured through a prisma.config.ts file. Every command and code block below was run end-to-end against prisma@7.8, @prisma/client@7.8 and @nestjs/core@11. If you're starting fresh, pair Prisma ORM with Prisma Postgres for a managed database that works out of the box.

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 a PostgreSQL database and connect to it using Prisma. Finally, you will build the REST API and document it with Swagger.

The final application

Technologies you will use

You will be using the following tools to build this application:

  • NestJS as the backend framework
  • Prisma ORM as the Object-Relational Mapper (ORM)
  • PostgreSQL as the database (via Prisma Postgres or your own instance)
  • 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 (v18 or higher) installed.
  • ... have a PostgreSQL database. The easiest option is Prisma Postgres; you can also run Postgres locally with Docker or a native install.
  • ... 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 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 median

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
  ├── eslint.config.mjs
  ├── 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:dev

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. You have a couple of options for getting one.

Option A: Prisma Postgres (recommended). Create a free Prisma Postgres database in seconds, with no local setup, by running:

npx create-db

This prints a DATABASE_URL connection string (along with a link to claim the database so you can keep it). Copy the DATABASE_URL; you'll paste it into your .env file in the next section. You can also create a Prisma Postgres database from the Prisma Console.

Note: Prefer to run Postgres locally with no cloud at all? npx prisma dev starts a local Prisma Postgres server in your terminal. It works for this tutorial, but the local server doesn't support the prisma migrate dev workflow you'll use later, so run npx prisma db push instead of migrate dev if you go this route. For the smoothest path through every step below, use npx create-db (Option A) or Docker (Option B).

Option B: Docker. If you prefer to run Postgres yourself with Docker, create a docker-compose.yml file in the main folder of your project:

touch docker-compose.yml

Add the following configuration inside the file:

# docker-compose.yml

services:
  postgres:
    image: postgres:17
    restart: always
    environment:
      - POSTGRES_USER=myuser
      - POSTGRES_PASSWORD=mypassword
    volumes:
      - postgres:/var/lib/postgresql/data
    ports:
      - '5432:5432'

volumes:
  postgres:

A few things to understand about this configuration:

  • The image option defines what Docker image to use. Here, you are using the postgres image version 17.
  • 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 port 5432 of the host machine to port 5432 of the postgres container. 5432 is conventionally the port used by PostgreSQL.

Make sure that nothing is running on port 5432 of your machine, then start the container:

docker compose up -d

Congratulations 🎉. You now have your own PostgreSQL database to play around with!

Set up Prisma

Now that the database is ready, it's time to set up Prisma!

Initialize Prisma

To get started, install the Prisma CLI along with two helpers you'll use later, tsx (to run the TypeScript seed script) and dotenv (to load environment variables), as development dependencies:

npm install -D prisma tsx dotenv

You can initialize Prisma inside your project by running:

npx prisma init --datasource-provider postgresql

This creates a new prisma directory with a schema.prisma file, a prisma.config.ts file, and a .env file. In Prisma 7, prisma.config.ts is the central configuration file; it's where your database connection URL and other settings now live.

Set your environment variable

Open the .env file and set DATABASE_URL to your database's connection string.

If you're using Prisma Postgres (Option A), paste the DATABASE_URL that npx create-db printed. It looks like this:

# .env
DATABASE_URL="postgres://<user>:<password>@db.prisma.io:5432/postgres?sslmode=require"

If you're using Docker (Option B), use the connection string for that instance:

# .env
DATABASE_URL="postgresql://myuser:mypassword@localhost:5432/median-db?schema=public"

Note: The exact host, port and credentials depend on your setup. The connection string format for PostgreSQL is available in the Prisma Docs.

Now open prisma.config.ts. Prisma generated it to read DATABASE_URL from your environment via dotenv. It should look like this:

// prisma.config.ts

// This file was generated by Prisma, and assumes you have installed the following:
// npm install --save-dev prisma dotenv
import "dotenv/config";
import { defineConfig } from "prisma/config";

export default defineConfig({
  schema: "prisma/schema.prisma",
  migrations: {
    path: "prisma/migrations",
  },
  datasource: {
    url: process.env["DATABASE_URL"],
  },
});

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"
  output   = "../generated/prisma"
}

datasource db {
  provider = "postgresql"
}

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 provider is set to postgresql. Notice the connection URL is no longer here; in Prisma 7 it lives in prisma.config.ts.
  • Generator: Indicates that you want to generate Prisma Client, a type-safe query builder for your database. In Prisma 7, the prisma-client generator outputs the Client as source files into your project (here, ../generated/prisma) rather than into node_modules. This makes the generated code transparent and bundler-friendly.
  • 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 add one in the next section.

Because NestJS compiles to CommonJS, add one option to the generator so the generated Client matches that module format:

// prisma/schema.prisma

generator client {
  provider     = "prisma-client"
  output       = "../generated/prisma"
  moduleFormat = "cjs"
}

datasource db {
  provider = "postgresql"
}

Note: Without moduleFormat = "cjs", you may hit a runtime error like ReferenceError: exports is not defined in ES module scope when NestJS loads the generated Client. Setting it to "cjs" keeps the Client aligned with NestJS's build output. For more on the 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/schema.prisma file, add a new model to your schema named Article:

// prisma/schema.prisma

model Article {
  id          Int      @id @default(autoincrement())
  title       String   @unique
  description String?
  body        String
  published   Boolean  @default(false)
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt
}

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.

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

This command will do two things:

  1. 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.
  2. Execute the migration: Prisma Migrate will execute the SQL in the migration file to create the underlying tables in your 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/
  └─ 20260625101323_init/
    └─ migration.sql

Your database is now in sync with your schema.

After the migration completes, generate Prisma Client so it reflects your latest schema:

npx prisma generate

You should see output like this:

✔ Generated Prisma Client (7.8.0) to ./generated/prisma

Note: In Prisma 7, migrate dev applies migrations but does not regenerate Prisma Client into your configured output folder. Run npx prisma generate after every schema change; without it, new models are missing from the Client at runtime. Check the generated migration file to get an idea about what Prisma Migrate is doing behind the scenes:

-- prisma/migrations/20260625101323_init/migration.sql

-- CreateTable
CREATE 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")
);

-- CreateIndex
CREATE 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.

In Prisma 7, the Prisma Client is Rust-free and talks to your database through a driver adapter. You need two runtime packages: @prisma/client (the runtime that your generated Client imports) and @prisma/adapter-pg (the PostgreSQL driver adapter). Install both:

npm install @prisma/client @prisma/adapter-pg

Note: @prisma/adapter-pg pulls in the pg driver (and its types) for you, so there's no separate pg install. If you skip @prisma/client, the app fails at runtime with Cannot find module '@prisma/client/runtime/client'.

Now create a seed file called prisma/seed.ts:

touch prisma/seed.ts

Then, inside the seed file, add the following code:

// prisma/seed.ts

import 'dotenv/config';
import { PrismaPg } from '@prisma/adapter-pg';
import { PrismaClient } from '../generated/prisma/client';

// initialize Prisma Client with the PostgreSQL driver adapter
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL });
const prisma = new PrismaClient({ adapter });

async function main() {
  // create two dummy articles
  const 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 function
main()
  .catch((e) => {
    console.error(e);
    process.exit(1);
  })
  .finally(async () => {
    // close Prisma Client at the end
    await prisma.$disconnect();
  });

Inside this script, you first initialize the PostgreSQL driver adapter and pass it to a new Prisma Client instance. 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. In Prisma 7, this is configured in prisma.config.ts (not package.json). Add a seed key under migrations:

// prisma.config.ts

import "dotenv/config";
import { defineConfig } from "prisma/config";

export default defineConfig({
  schema: "prisma/schema.prisma",
  migrations: {
    path: "prisma/migrations",
    seed: "tsx prisma/seed.ts",
  },
  datasource: {
    url: process.env["DATABASE_URL"],
  },
});

The seed command will execute the prisma/seed.ts script using tsx.

Execute seeding with the following command:

npx prisma db seed

You should see the following output:

Running seed command `tsx prisma/seed.ts` ...
{
  post1: {
    id: 1,
    title: 'Prisma Adds Support for MongoDB',
    ...
    published: false,
    createdAt: 2026-06-25T07:42:14.175Z,
    updatedAt: 2026-06-25T07:42:14.175Z
  },
  post2: {
    id: 2,
    title: "What's new in Prisma? (Q1/22)",
    ...
    published: true,
    createdAt: 2026-06-25T07:42:14.206Z,
    updatedAt: 2026-06-25T07:42:14.206Z
  }
}

🌱  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 (with the driver adapter) 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 prisma
npx nest generate service prisma

Note: If necessary, refer to the NestJS docs for an introduction to services and modules.

This should generate a new subdirectory ./src/prisma with a prisma.module.ts and prisma.service.ts file. Update the service file to instantiate Prisma Client with the PostgreSQL adapter and connect when the module initializes:

// src/prisma/prisma.service.ts

import { Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaPg } from '@prisma/adapter-pg';
import { PrismaClient } from '../../generated/prisma/client';

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
  constructor() {
    super({
      adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL }),
    });
  }

  async onModuleInit() {
    await this.$connect();
  }
}

Because the driver adapter reads DATABASE_URL from the environment at runtime, you need to make sure your .env file is loaded when the app boots. Use the NestJS ConfigModule for this. Install it:

npm install @nestjs/config

Then register it globally in your root AppModule (you'll add the ArticlesModule later in this tutorial):

// src/app.module.ts

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { PrismaModule } from './prisma/prisma.module';

@Module({
  imports: [ConfigModule.forRoot({ isGlobal: true }), PrismaModule],
})
export class AppModule {}

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, add the PrismaService to the exports array in the prisma.module.ts file:

// src/prisma/prisma.module.ts

import { Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';

@Module({
  providers: [PrismaService],
  exports: [PrismaService],
})
export class PrismaModule {}

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 dependency:

npm install @nestjs/swagger

Note: In older versions you also had to install swagger-ui-express separately. With @nestjs/swagger v11 it's included, so a single install is enough.

Now open main.ts and initialize Swagger using the SwaggerModule class:

// src/main.ts

import { 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(process.env.PORT ?? 3000);
}
bootstrap();

While the application is running, open your browser and navigate to http://localhost:3000/api. You should see the Swagger UI.

Swagger User Interface

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 resource

You will be given a few CLI prompts. Answer the questions accordingly:

  1. What name would you like to use for this resource (plural, e.g., "users")? articles
  2. What transport layer do you use? REST API
  3. Would 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.

Generating the resource also imports ArticlesModule into your AppModule. Your app.module.ts should now look like this:

// src/app.module.ts

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { ArticlesModule } from './articles/articles.module';
import { PrismaModule } from './prisma/prisma.module';

@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true }),
    ArticlesModule,
    PrismaModule,
  ],
})
export class AppModule {}

If you open the Swagger API page again, you should see something like this:

Auto-generated "articles" endpoints

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.ts

import { Module } from '@nestjs/common';
import { ArticlesService } from './articles.service';
import { ArticlesController } from './articles.controller';
import { PrismaModule } from '../prisma/prisma.module';

@Module({
  controllers: [ArticlesController],
  providers: [ArticlesService],
  imports: [PrismaModule],
})
export class ArticlesModule {}

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.ts

import { Injectable } from '@nestjs/common';
import { CreateArticleDto } from './dto/create-article.dto';
import { UpdateArticleDto } from './dto/update-article.dto';
import { PrismaService } from '../prisma/prisma.service';

@Injectable()
export class ArticlesService {
  constructor(private prisma: PrismaService) {}

  // CRUD operations
}

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

findAll() {
  return this.prisma.article.findMany({ where: { published: true } });
}

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

@Get('drafts')
findDrafts() {
  return this.articlesService.findDrafts();
}

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

findDrafts() {
  return this.prisma.article.findMany({ where: { published: false } });
}

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

findOne(id: number) {
  return this.prisma.article.findUnique({ where: { id } });
}

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.ts

import { 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;
}

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

create(createArticleDto: CreateArticleDto) {
  return this.prisma.article.create({ data: createArticleDto });
}

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

update(id: number, updateArticleDto: UpdateArticleDto) {
  return this.prisma.article.update({
    where: { id },
    data: updateArticleDto,
  });
}

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

remove(id: number) {
  return this.prisma.article.delete({ where: { id } });
}

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.ts

import { ApiTags } from '@nestjs/swagger';

@Controller('articles')
@ApiTags('articles')
export class ArticlesController {
  // ...
}

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 article.entity.ts file as follows:

// src/articles/entities/article.entity.ts

import { Article } from '../../../generated/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;
}

This is an implementation of the Article type generated by Prisma Client, with @ApiProperty decorators added to each property. Note that the Article type is imported from your generated Client (../../../generated/prisma/client), not from @prisma/client.

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.ts

import {
  Controller,
  Get,
  Post,
  Body,
  Patch,
  Param,
  Delete,
} from '@nestjs/common';
import { ArticlesService } from './articles.service';
import { CreateArticleDto } from './dto/create-article.dto';
import { UpdateArticleDto } from './dto/update-article.dto';
import { 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);
  }
}

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.

Frequently asked questions

Summary and final remarks

Congratulations! You've built a rudimentary REST API using NestJS and Prisma 7. Throughout this tutorial you:

  • Built a REST API with NestJS
  • Smoothly integrated Prisma 7 (with a PostgreSQL driver adapter) into 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.

In the next part of this series, you will add input validation and transformation to this API. If you want to go deeper on the Prisma side first, start with the Prisma getting started guide, learn more about Prisma Migrate, or create a Prisma Postgres database for your next NestJS project.

Looking ahead: Prisma Next is a TypeScript-native rewrite of Prisma ORM, built for AI coding agents and currently in early access. It becomes Prisma 8 at general availability; until then, Prisma 7 stays the production choice. To try it, run npm create prisma@next or read the early access docs.

Build your next app with Prisma

Start free. Scale when you’re ready.

Try Prisma
Share this article