
Building a REST API with NestJS (2025 Guide)
In this tutorial, I will show you how to develop a basic REST API using NestJS.
Requirements review
Now, suppose you are given the following database design:
You are required to create a REST API with the following endpoints:
Products:
Method | Endpoint | Description |
---|---|---|
GET | /api/products | Get paginated list of products |
GET | /api/products/{id} | Get a specific product by ID |
POST | /api/products | Create a new product |
PUT | /api/products/{id} | Update an existing product |
DELETE | /api/products/{id} | Delete a product |
Categories:
Method | Endpoint | Description |
---|---|---|
GET | /api/categories | Get paginated list of categories |
GET | /api/categories/{id} | Get a specific category by ID |
POST | /api/categories | Create a new category |
PUT | /api/categories/{id} | Update an existing category |
DELETE | /api/categories/{id} | Delete a category |
Example of responses:
GET /api/products
{
"content": [
{
"id": 1,
"name": "Smartphone X",
"description": "A high-end smartphone with an excellent camera.",
"price": 999.99,
"categories": [
{
"id": 2,
"name": "Electronics"
},
{
"id": 5,
"name": "Mobile Phones"
}
]
}
],
"pageNo": 0,
"pageSize": 10,
"totalElements": 1,
"totalPages": 1,
"last": true
}
GET /api/products/{id}
{
"id": 1,
"name": "Smartphone X",
"description": "A high-end smartphone with an excellent camera.",
"price": 999.99,
"categories": [
{
"id": 2,
"name": "Electronics"
},
{
"id": 5,
"name": "Mobile Phones"
}
]
}
POST /api/products
// Body
{
"name": "Smartwatch Z",
"description": "A waterproof smartwatch with fitness tracking.",
"price": 199.99,
"categories": [2, 7]
}
// Response
{
"id": 2,
"name": "Smartwatch Z",
"description": "A waterproof smartwatch with fitness tracking.",
"price": 199.99,
"categories": [
{
"id": 2,
"name": "Electronics"
},
{
"id": 7,
"name": "Wearables"
}
]
}
PUT /api/products/{id}
// Body
{
"name": "Smartwatch Z Pro",
"description": "Upgraded smartwatch with longer battery life.",
"price": 249.99,
"categories": [2, 7]
}
// Response
{
"id": 2,
"name": "Smartwatch Z Pro",
"description": "Upgraded smartwatch with longer battery life.",
"price": 249.99,
"categories": [
{
"id": 2,
"name": "Electronics"
},
{
"id": 7,
"name": "Wearables"
}
]
}
DELETE /api/products/{id}
{
"message": "Product deleted successfully"
}
The database must be implemented using PostgreSQL.
Database
If you don’t have a PostgreSQL database, install Docker on your computer and use the file docker-compose.local-dev.yaml
to create a PostgreSQL server and a database.
Add a file .env
in the root of the project with the following content:
# App
PORT=3000
CLIENT_URL="http://localhost:5173"
# DB Postgress
POSTGRES_DB_NAME=products-api
POSTGRES_DB_HOST=localhost
POSTGRES_DB_PORT=5432
POSTGRES_DB_USERNAME=admin
POSTGRES_DB_PASSWORD=admin
Then run this command in the root of the project:
docker compose -f docker-compose.local-dev.yaml up -d
Start coding
Project setup
Install NestJS:
npm i -g @nestjs/cli
Create the project with this command:
nest new products-api
Select the package manager you want to use, I will use npm
:
Which package manager would you ❤️ to use? npm
This command will create a folder products-api
with a minimal NestJS project.
Now we can install the dependencies:
npm ci
And run the project:
npm run start
The app will be up and running on port 3000.
You can test the endpoints using Postman or the REST Client extension in VSCode
I will use the REST Client extension:
In the root of the project add a folder rest-client
. Inside it add a file products.http
and place this content:
@base_url=http://localhost:3000
# Get all products
GET {{base_url}}
You will see something like this:
Project directory structure
Create the following folders inside src
:
- core
- database
- products
- categories
Project configuration
Database
Install the following packages:
npm install @nestjs/typeorm typeorm pg @nestjs/config
pg
: Driver for communicating our NestJS app with the PostgreSQL database.typeorm
: Object Relational Mapper (ORM) for TypeScript.@nestjs/typeorm
: Package provided by NestJS to integrate TypeORM in our app.@nestjs/config
: Package provided by NestJS to use environment variables.
Create the following files inside the folder /database/
and paste the content:
/entities/base.ts
:
import { CreateDateColumn, DeleteDateColumn, PrimaryGeneratedColumn, UpdateDateColumn } from "typeorm";
export abstract class BaseModel {
@PrimaryGeneratedColumn()
id: number;
@CreateDateColumn({
name: "created_at",
type: "timestamptz",
default: () => "CURRENT_TIMESTAMP",
})
createdAt: Date;
@UpdateDateColumn({
name: "updated_at",
type: "timestamptz",
default: () => "CURRENT_TIMESTAMP",
onUpdate: "CURRENT_TIMESTAMP",
})
updatedAt: Date;
@DeleteDateColumn({
name: "deleted_at",
type: "timestamptz",
})
deletedAt: Date;
}
/entities/product.ts
:
import { BaseModel } from "@src/database/entities/base";
import { Entity, Column, ManyToMany, JoinTable } from "typeorm";
import { Category } from "./category";
@Entity({ name: "products" })
export class Product extends BaseModel {
@Column()
name: string;
@Column()
description: string;
@Column("decimal", { precision: 10, scale: 2 })
price: number;
@ManyToMany(() => Category, (category) => category.products, {
cascade: true,
})
@JoinTable({
name: "product_categories",
joinColumn: { name: "product_id", referencedColumnName: "id" },
inverseJoinColumn: { name: "category_id", referencedColumnName: "id" },
})
categories: Category[];
}
/entities/category.ts
:
import { BaseModel } from "@src/database/entities/base";
import { Entity, Column, ManyToMany } from "typeorm";
import { Product } from "./product";
@Entity({ name: "categories" })
export class Category extends BaseModel {
@Column()
name: string;
@ManyToMany(() => Product, (product) => product.categories)
products: Product[];
}
/providers/postgresql.provider.ts
:
import { Injectable } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { TypeOrmModuleOptions, TypeOrmOptionsFactory } from "@nestjs/typeorm";
import { Product } from "@src/database/entities/product";
import { Category } from "../entities/category";
@Injectable()
export class PostgresqlDdProvider implements TypeOrmOptionsFactory {
constructor(private readonly configService: ConfigService) {}
createTypeOrmOptions(): Promise<TypeOrmModuleOptions> | TypeOrmModuleOptions {
return {
type: "postgres",
host: this.configService.get<"string">("POSTGRES_DB_HOST"),
port: parseInt(this.configService.get<"string">("POSTGRES_DB_PORT") ?? "5432"),
username: this.configService.get<"string">("POSTGRES_DB_USERNAME"),
password: this.configService.get<"string">("POSTGRES_DB_PASSWORD"),
database: this.configService.get<"string">("POSTGRES_DB_NAME"),
entities: [Product, Category],
synchronize: true,
logging: false,
};
}
}
/database.module.ts
:
import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { PostgresqlDdProvider } from "./providers/postgresql.provider";
@Module({
imports: [
TypeOrmModule.forRootAsync({
name: "postgres", // Explicitly set the connection name for PostgreSQL
useClass: PostgresqlDdProvider,
}),
],
exports: [TypeOrmModule],
})
export class DatabaseModule {}
Core
Delete files:
- app.service.ts
- app.controller.ts
- app.controller.spec.ts
Install the following packages:
npm install helmet nestjs-pino @nestjs/throttler
npm install pino-pretty --save-dev
helmet
: Middleware that enhances security by setting various HTTP headers.nestjs-pino
: Logging integration for NestJS that uses the pino logger. Pino is a fast and efficient logging library for Node.js.@nestjs/throttler
: NestJS module that provides rate-limiting functionality for your application.pino-pretty
: Formats Pino’s structured JSON logs into a human-readable, colorized output for easier debugging in development.
Replace the content in main.ts
with the following:
import { NestFactory } from "@nestjs/core";
import { ValidationPipe, Logger } from "@nestjs/common";
import { Logger as PinoLogger } from "nestjs-pino";
import helmet from "helmet";
import { AppModule } from "./app.module";
import * as bodyParser from "body-parser";
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
bufferLogs: true,
bodyParser: true,
});
// Increase payload size limit
app.use(bodyParser.json({ limit: "10mb" }));
app.use(bodyParser.urlencoded({ limit: "10mb", extended: true }));
app.useLogger(app.get(PinoLogger));
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
})
);
app.use(helmet());
app.enableCors({
origin: process.env.CLIENT_URL ?? "*",
credentials: true,
});
await app.listen(process.env.PORT ?? 3000);
const logger = new Logger("Bootstrap");
logger.log(`App is running on ${await app.getUrl()}`);
}
bootstrap();
Replace the content in app.module.ts
with the following:
import { Module } from "@nestjs/common";
import { CoreModule } from "./core/core.module";
import { ProductsModule } from "./products/products.module";
import { CategoriesModule } from "./categories/categories.module";
@Module({
imports: [CoreModule, ProductsModule, CategoriesModule],
})
export class AppModule {}
Create the following file inside the folder /core/
and paste the content:
/core/core.module.ts
:
import { Module } from "@nestjs/common";
import { ConfigModule } from "@nestjs/config";
import { ThrottlerModule } from "@nestjs/throttler";
import { LoggerModule } from "nestjs-pino";
import { DatabaseModule } from "@src/database/database.module";
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
ThrottlerModule.forRoot([
{
ttl: 60000, // Time-to-live in milliseconds
limit: 60, // Maximum requests per window globally
},
]),
LoggerModule.forRoot({
pinoHttp: {
serializers: {
req: () => undefined,
res: () => undefined,
},
autoLogging: false,
level: process.env.NODE_ENV === "production" ? "info" : "debug",
transport:
process.env.NODE_ENV === "production"
? undefined
: {
target: "pino-pretty",
options: {
messageKey: "message",
colorize: true,
},
},
messageKey: "message",
},
}),
DatabaseModule,
],
})
export class CoreModule {}
Products
Install the following packages:
npm install class-validator class-transformer
class-validator
: Package that provides decorators and functions to validate the properties of classes, ensuring that the data meets specified rules. I will use it in DTOs.class-transformer
: Transforms plain JavaScript objects into class instances and vice versa, enabling serialization and deserialization in TypeScript.
Create the following files inside the folder /products/
and paste the content:
/dtos/create-product.dto.ts
:
import { ArrayNotEmpty, IsArray, IsNumber, IsString } from "class-validator";
export class CreateProductDto {
@IsString()
name: string;
@IsString()
description: string;
@IsNumber()
price: number;
@IsArray()
@ArrayNotEmpty()
@IsNumber({}, { each: true })
categories: number[];
}
/dtos/update-product.dto.ts
:
import { IsArray, IsNumber, IsOptional, IsString } from "class-validator";
export class UpdateProductDto {
@IsOptional()
@IsString()
name?: string;
@IsOptional()
@IsString()
description?: string;
@IsOptional()
@IsNumber()
price?: number;
@IsOptional()
@IsArray()
@IsNumber({}, { each: true })
categories?: number[];
}
/dtos/product-response.dto.ts
:
export class ProductResponseDto {
id: number;
name: string;
description: string;
price: number;
categories: CategoryResponseDto[];
}
export class CategoryResponseDto {
id: number;
name: string;
}
/products.mapper.ts
:
import { Injectable } from "@nestjs/common";
import { Product } from "@src/database/entities/product";
import { ProductResponseDto } from "./dtos/product-response.dto";
@Injectable()
export class ProductMapper {
mapEntityToDto(product: Product): ProductResponseDto {
return {
id: product.id,
name: product.name,
description: product.description,
price: product.price,
categories:
product.categories?.map((category) => ({
id: category.id,
name: category.name,
})) || [],
};
}
}
/products.service.ts
:
import { BadRequestException, HttpException, Injectable, InternalServerErrorException, Logger, NotFoundException } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { In, Repository } from "typeorm";
import { Product } from "@src/database/entities/product";
import { CreateProductDto } from "./dtos/create-product.dto";
import { UpdateProductDto } from "./dtos/update-product.dto";
import { Category } from "@src/database/entities/category";
import { ProductMapper } from "./products.mapper";
@Injectable()
export class ProductsService {
private readonly logger = new Logger("ProductsService");
constructor(
@InjectRepository(Product, "postgres")
private readonly productRepository: Repository<Product>,
@InjectRepository(Category, "postgres")
private readonly categoryRepository: Repository<Category>,
private readonly productMapper: ProductMapper
) {}
async getAll(pageNo: number = 0, pageSize: number = 10) {
try {
// Ensure pageNo is a non-negative integer and pageSize is within reasonable limits
if (pageNo < 0 || pageSize <= 0) {
throw new BadRequestException("Invalid page number or page size");
}
// Calculate skip value based on page number and page size
const skip = pageNo * pageSize;
// Get products with pagination
const [products, totalElements] = await this.productRepository.findAndCount({
skip,
take: pageSize,
relations: ["categories"], // To load related categories for each product
});
// Calculate totalPages and whether it's the last page
const totalPages = Math.ceil(totalElements / pageSize);
const last = pageNo + 1 >= totalPages;
// Map the result into the desired format
return {
content: products.map((product) => this.productMapper.mapEntityToDto(product)),
pageNo,
pageSize,
totalElements,
totalPages,
last,
};
} catch (error) {
this.handleError(error, "An error occurred while getting products");
}
}
async getById(id: number) {
try {
const product = await this.productRepository.findOne({
where: { id },
relations: ["categories"],
});
if (!product) {
throw new NotFoundException("Product not found");
}
return this.productMapper.mapEntityToDto(product);
} catch (error) {
this.handleError(error, "An error occurred while fetching product");
}
}
async create(createProductDto: CreateProductDto) {
try {
// Convert category IDs to actual Category entities
const categories = await this.categoryRepository.findBy({
id: In(createProductDto.categories),
});
if (!categories.length) {
throw new BadRequestException("Invalid category IDs");
}
// Create a new product with the categories attached
const newProduct = this.productRepository.create({
...createProductDto,
categories,
});
await this.productRepository.save(newProduct);
return this.productMapper.mapEntityToDto(newProduct);
} catch (error) {
this.handleError(error, "An error occurred while creating product");
}
}
async update(id: number, updateProductDto: UpdateProductDto) {
try {
const { categories, ...updateProductDtoWithoutCategories } = updateProductDto;
// Find the product by ID and preload with the updated values
const product = await this.productRepository.preload({
id,
...updateProductDtoWithoutCategories,
});
if (!product) {
throw new NotFoundException("Product not found");
}
// If the update involves categories, convert category IDs to actual Category entities
if (updateProductDto.categories) {
const categories = await this.categoryRepository.findBy({
id: In(updateProductDto.categories),
});
if (!categories.length) {
throw new BadRequestException("Invalid category IDs");
}
product.categories = categories;
}
await this.productRepository.save(product);
return this.productMapper.mapEntityToDto(product);
} catch (error) {
this.handleError(error, "An error occurred while updating product");
}
}
async delete(id: number) {
try {
const product = await this.productRepository.findOne({ where: { id } });
if (!product) {
throw new NotFoundException("Product not found");
}
await this.productRepository.remove(product);
return { message: "Product deleted successfully" };
} catch (error) {
this.handleError(error, "An error occurred while deleting product");
}
}
private handleError(error: unknown, defaultErrorMessage?: string) {
// Log the error
this.logger.error(error);
// Handle known HTTP exceptions
if (error instanceof HttpException) {
throw error; // Preserve the original exception, don't modify it
}
// Handle unexpected errors
if (error instanceof Error) {
// Handle generic errors
throw new InternalServerErrorException({
message: defaultErrorMessage ?? "An unexpected error occurred",
});
}
// Default to a generic BadRequestException if error is unknown
throw new BadRequestException({
message: defaultErrorMessage ?? "An error occurred in ProductsService",
});
}
}
/products.controller.ts
:
import { Body, Controller, Delete, Get, Param, ParseIntPipe, Post, Put, Query } from "@nestjs/common";
import { ProductsService } from "./products.service";
import { CreateProductDto } from "./dtos/create-product.dto";
import { UpdateProductDto } from "./dtos/update-product.dto";
@Controller("api/products")
export class ProductsController {
constructor(private readonly productsService: ProductsService) {}
@Get("/")
getAll(
@Query("pageNo", new ParseIntPipe({ errorHttpStatusCode: 400 }))
pageNo: number = 0,
@Query("pageSize", new ParseIntPipe({ errorHttpStatusCode: 400 }))
pageSize: number = 10
) {
return this.productsService.getAll(pageNo, pageSize);
}
@Get("/:id")
getById(@Param("id", ParseIntPipe) id: number) {
return this.productsService.getById(id);
}
@Post("/")
create(@Body() createProductDto: CreateProductDto) {
return this.productsService.create(createProductDto);
}
@Put("/:id")
update(@Param("id", ParseIntPipe) id: number, @Body() updateProductDto: UpdateProductDto) {
return this.productsService.update(id, updateProductDto);
}
@Delete("/:id")
delete(@Param("id", ParseIntPipe) id: number) {
return this.productsService.delete(id);
}
}
/products.module.ts
:
import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { Category } from "@src/database/entities/category";
import { Product } from "@src/database/entities/product";
import { ProductsController } from "./products.controller";
import { ProductsService } from "./products.service";
import { ProductMapper } from "./products.mapper";
@Module({
imports: [TypeOrmModule.forFeature([Product, Category], "postgres")],
controllers: [ProductsController],
providers: [ProductsService, ProductMapper],
exports: [ProductsService],
})
export class ProductsModule {}
Categories
Create the following files inside the folder /categories/
and paste the content:
/dtos/create-category.dto.ts
:
import { IsString } from "class-validator";
export class CreateCategoryDto {
@IsString()
name: string;
}
/dtos/update-category.dto.ts
:
import { IsString } from "class-validator";
export class UpdateCategoryDto {
@IsString()
name: string;
}
/dtos/category-response.dto.ts
:
export class CategoryResponseDto {
id: number;
name: string;
}
/categories.mapper.ts
:
import { Injectable } from "@nestjs/common";
import { Category } from "@src/database/entities/category";
import { CategoryResponseDto } from "./dtos/category-response.dto";
@Injectable()
export class CategoryMapper {
mapEntityToDto(category: Category): CategoryResponseDto {
return {
id: category.id,
name: category.name,
};
}
}
/categories.service.ts
:
import { BadRequestException, HttpException, Injectable, InternalServerErrorException, Logger, NotFoundException } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import { Category } from "@src/database/entities/category";
import { CreateCategoryDto } from "./dtos/create-category.dto";
import { UpdateCategoryDto } from "./dtos/update-category.dto";
import { CategoryMapper } from "./categories.mapper";
@Injectable()
export class CategoriesService {
private readonly logger = new Logger("CategoriesService");
constructor(
@InjectRepository(Category, "postgres")
private readonly categoryRepository: Repository<Category>,
private readonly categoryMapper: CategoryMapper
) {}
async getAll(pageNo: number = 0, pageSize: number = 10) {
try {
// Ensure pageNo is a non-negative integer and pageSize is within reasonable limits
if (pageNo < 0 || pageSize <= 0) {
throw new BadRequestException("Invalid page number or page size");
}
// Calculate skip value based on page number and page size
const skip = pageNo * pageSize;
// Get categories with pagination
const [categories, totalElements] = await this.categoryRepository.findAndCount({
skip,
take: pageSize,
});
// Calculate totalPages and whether it's the last page
const totalPages = Math.ceil(totalElements / pageSize);
const last = pageNo + 1 >= totalPages;
// Map the result into the desired format
return {
content: categories.map((category) => this.categoryMapper.mapEntityToDto(category)),
pageNo,
pageSize,
totalElements,
totalPages,
last,
};
} catch (error) {
this.handleError(error, "An error occurred while getting categories");
}
}
async getById(id: number) {
try {
const category = await this.categoryRepository.findOne({
where: { id },
});
if (!category) {
throw new NotFoundException("Category not found");
}
return this.categoryMapper.mapEntityToDto(category);
} catch (error) {
this.handleError(error, "An error occurred while fetching category");
}
}
async create(createCategoryDto: CreateCategoryDto) {
try {
// Create a new category
const newCategory = this.categoryRepository.create(createCategoryDto);
await this.categoryRepository.save(newCategory);
return this.categoryMapper.mapEntityToDto(newCategory);
} catch (error) {
this.handleError(error, "An error occurred while creating category");
}
}
async update(id: number, updateCategoryDto: UpdateCategoryDto) {
try {
const category = await this.categoryRepository.findOne({
where: { id },
});
if (!category) {
throw new NotFoundException("Category not found");
}
category.name = updateCategoryDto.name;
await this.categoryRepository.save(category);
return this.categoryMapper.mapEntityToDto(category);
} catch (error) {
this.handleError(error, "An error occurred while updating category");
}
}
async delete(id: number) {
try {
const category = await this.categoryRepository.findOne({ where: { id } });
if (!category) {
throw new NotFoundException("Category not found");
}
await this.categoryRepository.remove(category);
return { message: "Category deleted successfully" };
} catch (error) {
this.handleError(error, "An error occurred while deleting category");
}
}
private handleError(error: unknown, defaultErrorMessage?: string) {
// Log the error
this.logger.error(error);
// Handle known HTTP exceptions
if (error instanceof HttpException) {
throw error; // Preserve the original exception, don't modify it
}
// Handle unexpected errors
if (error instanceof Error) {
// Handle generic errors
throw new InternalServerErrorException({
message: defaultErrorMessage ?? "An unexpected error occurred",
});
}
// Default to a generic BadRequestException if error is unknown
throw new BadRequestException({
message: defaultErrorMessage ?? "An error occurred in CategoriesService",
});
}
}
/categories.controller.ts
:
import { Body, Controller, Delete, Get, Param, ParseIntPipe, Post, Put, Query } from "@nestjs/common";
import { CategoriesService } from "./categories.service";
import { CreateCategoryDto } from "./dtos/create-category.dto";
import { UpdateCategoryDto } from "./dtos/update-category.dto";
@Controller("api/categories")
export class CategoriesController {
constructor(private readonly categoriesService: CategoriesService) {}
@Get("/")
getAll(
@Query("pageNo", new ParseIntPipe({ errorHttpStatusCode: 400 }))
pageNo: number = 0,
@Query("pageSize", new ParseIntPipe({ errorHttpStatusCode: 400 }))
pageSize: number = 10
) {
return this.categoriesService.getAll(pageNo, pageSize);
}
@Get("/:id")
getById(@Param("id", ParseIntPipe) id: number) {
return this.categoriesService.getById(id);
}
@Post("/")
create(@Body() createCategoryDto: CreateCategoryDto) {
return this.categoriesService.create(createCategoryDto);
}
@Put("/:id")
update(@Param("id", ParseIntPipe) id: number, @Body() updateCategoryDto: UpdateCategoryDto) {
return this.categoriesService.update(id, updateCategoryDto);
}
@Delete("/:id")
delete(@Param("id", ParseIntPipe) id: number) {
return this.categoriesService.delete(id);
}
}
/categories.module.ts
:
import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { Category } from "@src/database/entities/category";
import { CategoriesController } from "./categories.controller";
import { CategoriesService } from "./categories.service";
import { CategoryMapper } from "./categories.mapper";
@Module({
imports: [TypeOrmModule.forFeature([Category], "postgres")],
controllers: [CategoriesController],
providers: [CategoriesService, CategoryMapper],
exports: [CategoriesService],
})
export class CategoriesModule {}
Test the endpoints
Create the following files inside the folder /rest-client/
and paste the content:
/products.http
:
@base_url=http://localhost:3000/api/products
### Get All Products
GET {{base_url}}?pageNo=0&pageSize=10
### Get Product by ID
GET {{base_url}}/1
### Create a New Product
POST {{base_url}}
Content-Type: application/json
{
"name": "Smartphone X",
"description": "A high-end smartphone with an excellent camera.",
"price": 999.99,
"categories": [1, 2]
}
### Update Product
PUT {{base_url}}/1
Content-Type: application/json
{
"name": "Smartphone Y",
"description": "An updated version of Smartphone X.",
"price": 899.99,
"categories": [2]
}
### Delete Product
DELETE {{base_url}}/1
/categories.http
:
@base_url = http://localhost:3000/api/categories
### Get All Categories
GET {{base_url}}?pageNo=0&pageSize=10
### Get Category by ID
GET {{base_url}}/1
### Create a New Category
POST {{base_url}}
Content-Type: application/json
{
"name": "Electronics"
}
### Update Category
PUT {{base_url}}/1
Content-Type: application/json
{
"name": "Updated Electronics"
}
### Delete Category
DELETE {{base_url}}/1
Now run the project with the following command:
npm run start
Conclusion
That’s it. Now, we have a basic REST API working in NestJS. You can download the source code on my GitHub.
Any ideas for a tutorial or suggestions to improve this project? Feel free to share them in the comments!
Thank you for reading.