Basic REST API with NestJS 2025
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.