Let's Build an Article Posting API with NestJS ─ Basics of Introducing Prisma and Implementing CRUD
Introduction
In this article, we will walk through how to implement an article posting API using NestJS and Prisma, explaining each step carefully from the basics.
NestJS is attracting attention as a framework with excellent extensibility and maintainability, and it is a technology that can be safely adopted even for medium- to large-scale applications. Prisma, on the other hand, is a type-safe and intuitive ORM that works extremely well with TypeScript and has been adopted in many projects in recent years.
Intended readers
This article is aimed at readers who already have a basic understanding of NestJS’s structure. In particular:
- Those who know the basic project structure of NestJS but are not yet familiar with how to build practical APIs
- Those who want to try Prisma and experience how to apply it to real API development
- Those who want to learn an API structure that is “useful in real-world projects,” not just for simple behavior checks
Goal of this article
In this article, we aim to achieve the following:
- Introduce Prisma and be able to define and migrate an article (Post) model
- Build CRUD APIs (list, create, detail, update, delete) for article posting on NestJS
- Implement with a robust structure by incorporating validation and DTO design
By understanding this structure, you should improve your “on-site adaptability” so that your code can withstand real product development.
We will implement up to the point where you can actually create and retrieve articles via API tests using Postman.
The code implemented in this article is stored in the following repository, so please refer to it together.
Overview of this series and where this article fits
Series structure
- Start Web App Development with NestJS ─ Learn Project Structure and Configuration Management by Building a Blog Site
- Let's Build an Article Posting API with NestJS ─ Basics of Introducing Prisma and Implementing CRUD ←
This article - Enhancing Application Reliability ─ Logging, Error Handling, and Test Strategy
- Deep Dive into Prisma and DB Design ─ Models, Relations, and Operational Design
- Build the UI with React and Deploy to Production ─ Docker and Environment Setup
Launching PostgreSQL with Docker
First, we will start PostgreSQL on Docker so that NestJS + Prisma can connect to it.
Create docker-compose.yml in the project root
services:
db:
image: postgres:15
container_name: nestjs-postgres
ports:
- "5432:5432"
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: nestjs_blog
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:
- If you persist data with a volume, the data will not be lost even if the container is restarted.
Startup command
This will start the container in the background.
docker-compose up -d
Check DB connection
docker exec -it nestjs-postgres psql -U postgres -d nestjs_blog
Once inside the container, check the list of databases.
\l
If nestjs_blog is displayed, you are good to go.
There is no particular need to work directly inside the container, so exit for now.
\q
Add connection information to .env
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/nestjs_blog"
With this connection string, Prisma Client will be able to access PostgreSQL running on Docker.
Introducing Prisma and Initial Setup
As the ORM (Object-Relational Mapping) that forms the foundation of our article posting API, we will use Prisma. Prisma is a modern ORM tightly integrated with TypeScript, and its major feature is that it allows type-safe and intuitive DB operations.
First, let’s introduce Prisma into the NestJS project and set up the connection to the database.
Installing Prisma
First, install the required packages. Run the following commands at the root of your NestJS project.
cd backend
npm install prisma --save-dev
npm install @prisma/client
prismais the CLI and migration tool@prisma/clientis the library used by the application to interact with the DB
Run prisma init
Next, run the Prisma initialization command.
npx prisma init
prisma/schema.prisma: The model definition file will be created.
generator client {
provider = "prisma-client-js"
output = "../generated/prisma"
}
If it is configured like this, the output destination of the Prisma Client generated later will be /generate, and NestJS will not be able to recognize it correctly, so delete this setting.
generator client {
provider = "prisma-client-js"
- output = "../generated/prisma"
}
Model Definition: Creating the Article (Post) Model
Now that Prisma has been introduced and the connection settings are complete, the next step is to define a model for article posting.
Here, we will create a model named Post and design the fields required for article posting. We assume that an article will include a title, body, creation date, update date, and so on.
Edit schema.prisma
Edit backend/prisma/schema.prisma as follows.
model Post {
id Int @id @default(autoincrement())
title String
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
With this definition, the Post model will include the following fields:
| Field name | Type | Notes |
|---|---|---|
| id | Int | Primary key. Auto-incrementing integer |
| title | String | Article title |
| content | String | Article body |
| createdAt | DateTime | Creation timestamp. Automatically set to the current time |
| updatedAt | DateTime | Update timestamp. Automatically updated when the record is updated |
In Prisma, you can define the behavior of fields such as default values and automatic updates using annotations like @default and @updatedAt.
Running the migration
After defining the model, run a migration to create the table in the database.
cd backend
npx prisma migrate dev --name init
When you run this command, the following will happen:
- A migration history will be generated in the
migrations/directory - A
Posttable will be created in PostgreSQL based on the Prisma model definition @prisma/clientwill be automatically regenerated, enabling type-safe DB access
Confirming table creation (using Prisma Studio)
Now that the migration is complete, let’s use Prisma Studio to check the table creation status.
Prisma Studio is a web-based data browser provided by Prisma, which allows you to conveniently inspect the contents of your local DB via a GUI.
Start it with the following command:
npx prisma studio
When you run the command, your browser will open, and you can confirm that the Post table is displayed.
The table name appears in the left sidebar, and by clicking it you can view the contents and add or edit records.
Since you can check things via a GUI, it’s easy to intuitively confirm whether the migration has been applied correctly.
Of course, you can also check via the CLI, but for efficiency and visibility, we will basically use Prisma Studio for confirmation from now on.
Next, we will implement APIs (CRUD operations) to actually read and write data using this Post model. With the model and table in place, the backbone of the backend is now visible.
Generating Prisma Client and Integrating It into NestJS
We have defined the article model and created the table in the database via migration, so the data structure is now ready.
The next step is to prepare a mechanism to access that database.
What is Prisma Client?
In Prisma, Prisma Client is automatically generated from the model definitions written in schema.prisma. This is the code used to access the database.
prisma.post.findMany() // Retrieve all records from the Post table
prisma.post.create({ data: { title, content } }) // Create a new record
In this way, it becomes a client library that allows type-safe DB operations.
This Prisma Client is generated inside the @prisma/client package and can be used from NestJS as well.
Generating Prisma Client
You can generate it with the following command:
cd backend
npx prisma generate
The generated files are included in node_modules/@prisma/client, where Prisma’s type information is also defined.
How to use Prisma in NestJS?
In NestJS, instead of directly calling new PrismaClient(), it is common to use NestJS’s dependency injection (DI) mechanism to share it across the application.
To do this, we create a PrismaService that wraps Prisma Client.
What is PrismaService?
PrismaService is a class that plays the following roles:
- Extends PrismaClient so it can be reused across the entire app
- Registers as a NestJS “service” so it can be used in Controllers and other Services
- Connects to the DB when the app starts and disconnects when it shuts down
import { PrismaClient } from '@prisma/client';
import { Injectable } from '@nestjs/common';
import { OnModuleDestroy, OnModuleInit } from '@nestjs/common/interfaces/hooks';
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
async onModuleInit() {
await this.$connect(); // Connect to DB when the app starts
}
async onModuleDestroy() {
await this.$disconnect(); // Disconnect when the app shuts down
}
}
This allows the entire application to share a single PrismaService instance.
We then create a module that provides PrismaService and import it where needed so that Prisma’s features can be used wherever required. This is in line with NestJS’s design philosophy.
import { Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';
@Module({
providers: [PrismaService],
exports: [PrismaService], // Make it available to other modules
})
export class PrismaModule {}
By importing this module with imports: [PrismaModule] in the PostModule and AppModule that we will create later, PrismaService becomes available anywhere via NestJS’s dependency injection.
Summary so far
| Concept | Role |
|---|---|
| Prisma Client | Type-safe DB operation library generated by Prisma |
| PrismaService | Service that centrally manages Prisma Client in NestJS (connection, disconnection, injection management) |
| PrismaModule | NestJS module that provides and shares PrismaService |
Creating CRUD APIs (Implementing Controller / Service)
Now that the Post model has been defined and the table created, we will implement CRUD functionality for article posting.
Here, we will use NestJS Controllers and Services to build the following five endpoints:
- Get article list (GET
/posts) - Create a new article (POST
/posts) - Get article details (GET
/posts/:id) - Update an article (PATCH
/posts/:id) - Delete an article (DELETE
/posts/:id)
We will use Prisma Client to perform all of these operations in a type-safe manner.
Creating PostModule
First, generate the module, controller, and service for articles all at once.
cd backend
npx nest g resource posts
This will automatically generate the necessary files under src/posts/.
posts.controller.tsposts.service.tsposts.module.tsdto/create-post.dto.tsdto/update-post.dto.ts
At this stage, a basic skeleton for CRUD APIs has been set up.
Aligning DTO types with the model
The DTO needs to correspond to PostCreateInput, so modify it as follows:
export class CreatePostDto {
+ title: string;
+ content: string;
}
We will explain DTOs in more detail later.
Injecting PrismaService
In PostService, we use PrismaService for database operations.
In NestJS, to inject service dependencies, we write the constructor of PostService as follows:
import { Injectable } from '@nestjs/common';
import { CreatePostDto } from './dto/create-post.dto';
import { UpdatePostDto } from './dto/update-post.dto';
import { PrismaService } from 'src/prisma/prisma.service';
@Injectable()
export class PostsService {
constructor(private readonly prisma: PrismaService) {}
create(createPostDto: CreatePostDto) {
return this.prisma.post.create({ data: createPostDto });
}
findAll() {
return this.prisma.post.findMany();
}
findOne(id: number) {
return this.prisma.post.findUnique({ where: { id } });
}
update(id: number, updatePostDto: UpdatePostDto) {
return this.prisma.post.update({
where: { id },
data: updatePostDto,
});
}
remove(id: number) {
return this.prisma.post.delete({ where: { id } });
}
}
Since there are many changes, we have shown the final code.
In NestJS, to inject a service, the module must “provide” that service.
In this case, we chose a structure where PrismaService is provided and exported by PrismaModule.
import { Module } from '@nestjs/common';
import { PostService } from './post.service';
import { PostController } from './post.controller';
+ import { PrismaModule } from 'src/prisma/prisma.module';
@Module({
+ imports: [PrismaModule],
controllers: [PostController],
API Testing with Postman
After finishing the code changes, start NestJS.
cd backend
npm run start:dev
This time, we will use PostMan to access the API and check whether we can register and retrieve posts.
There is a Postman VSCode extension, so I use that to perform behavior checks.
① Create a new article (POST /posts)
- Method: POST
- URL: http://localhost:3000/posts
- Headers:
- Content-Type: application/json
- Body (raw / JSON):
{
"title": "My first article",
"content": "This is the first post created with a NestJS + Prisma API."
}
If the request succeeds, you will receive a 201 Created status and the JSON of the created article.
② Get article list (GET /posts)
- Method: GET
- URL: http://localhost:3000/posts
The articles you created will be returned as an array.
③ Get article details (GET /posts/:id)
- Method: GET
- URL: http://localhost:3000/posts/1
You can retrieve a single article corresponding to the specified ID.
④ Update an article (PATCH /posts/:id)
- Method: PATCH
- URL: http://localhost:3000/posts/1
- Headers:
- Content-Type: application/json
- Body (raw / JSON):
{
"title": "The article title has been updated"
}
Partial updates are also possible. The response will contain the updated data.
⑤ Delete an article (DELETE /posts/:id)
- Method: DELETE
- URL: http://localhost:3000/posts/1
If successful, you will receive a 200 OK status and the information of the deleted article.
After that, if you run the article list retrieval again, you can confirm that no records are returned.
Validation and DTO Design
To ensure that only valid data is sent to the article posting API, we will design DTOs and validation.
In NestJS, you can concisely implement validation logic by adding class-validator decorators to DTOs.
What is a DTO?
A DTO (Data Transfer Object) is a type definition that explicitly describes the structure of requests and responses.
In NestJS, defining DTOs as classes not only enables TypeScript type checking but also makes them targets for validation.
For example, the data structure required when creating an article can be designed as follows.
Install packages
npm install class-validator class-transformer
Add validation to CreatePostDto
Edit the CLI-generated create-post.dto.ts as follows:
+ import { IsString, IsNotEmpty } from 'class-validator';
export class CreatePostDto {
+ @IsString()
+ @IsNotEmpty()
title: string;
+ @IsString()
+ @IsNotEmpty()
content: string;
}
This requires that the JSON sent to the API has the following format and that the fields are not empty:
{
"title": "Article Posting API with NestJS and Prisma",
"content": "By applying validation, you can ensure a reliable data structure."
}
UpdatePostDto
To support partial updates, properties in UpdatePostDto are made optional.
You can leave the CLI-generated code as is.
import { PartialType } from '@nestjs/mapped-types';
import { CreatePostDto } from './create-post.dto';
export class UpdatePostDto extends PartialType(CreatePostDto) {}
Enabling validation
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
+ import { ValidationPipe } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
+ app.useGlobalPipes(new ValidationPipe());
await app.listen(process.env.PORT ?? 3000);
}
bootstrap().catch((err) => {
console.error('Application failed to start', err);
});
Behavior check
Confirm that NestJS automatically detects validation errors and returns an error response.
Create a new article (POST /posts)
- Method: POST
- URL: http://localhost:3000/posts
- Headers:
- Content-Type: application/json
- Body (raw / JSON):
{}
If you send empty data to POST /posts, you will receive a response like the following:
Conclusion
In this article, we built a simple article posting API from scratch using NestJS and Prisma.
After introducing Prisma and confirming the connection with PostgreSQL, we proceeded step by step through model definition, migration, Prisma Client integration, REST API implementation, and validation design using DTOs.
With the structure we have built so far, we now have:
- Type-safe and highly maintainable data operations
- Endpoint design that follows REST principles
- A validation mechanism that prevents invalid requests
In other words, we have completed a solid and practical foundation for a backend API.
With this foundation in place, you should be able to smoothly incorporate more advanced features such as:
- Implementing authentication and authorization to associate logged-in users with articles
- Designing relations with other entities such as users, comments, and tags
- Performing advanced DB operations using Prisma middleware and transactions
- Automatically generating API documentation with Swagger
Backend development based on NestJS and Prisma offers an excellent balance of type safety, flexibility, and readability, making it well-suited for team development.
I hope this article helps you update and improve your own development style.
Next time
So far, we have implemented all the basic functions of an article posting API. Next time, we will go a step further and explore techniques to turn this into a more “practical application.”
- Logging and formatting: Use NestJS’s Logger to make important events easier to track
- Unified error handling: Standardize exception handling with a custom ExceptionFilter
- Introducing a test strategy: How to write unit tests for the Service layer and E2E tests via the Controller
- Environment-specific configuration management: Use a dedicated .env.test and separate configurations
As a first step toward building a “trustworthy API” ready for production, the next part will focus on practical techniques to enhance application reliability.
Questions about this article 📝
If you have any questions or feedback about the content, please feel free to contact us.Go to inquiry form
Related Articles
Robust Authorization Design for GraphQL and REST APIs: Best Practices for RBAC, ABAC, and OAuth 2.0
2024/05/13Chat App (with Image/PDF Sending and Video Call Features)
2024/07/15Management Dashboard Features (Graph Display, Data Import)
2024/06/02Improving the Reliability of a NestJS App ─ Logging, Error Handling, and Testing Strategy
2024/09/11Deepening DB Design with NestJS × Prisma ─ Models, Relations, and Operational Design
2024/09/12NestJS × React × Railway: Implementing a Blog UI and Deploying to Production
2024/10/25Getting Started with Web App Development Using NestJS and React ─ Learning Project Structure and Configuration Management by Building a Blog Site
2025/04/11Tutorial for Implementing Authentication with Next.js and Auth.js
2024/09/13










