Careeroza
Product CRUD API
The Full Controller
An in-memory version of the ShopEase product API. In the medium level the array is replaced by a database.
src/products/products.controller.ts
import { Body, Controller, Delete, Get, HttpCode, Param, ParseIntPipe, Patch, Post, Query } from '@nestjs/common';
import { ProductsService } from './products.service';
import { CreateProductDto } from './dto/create-product.dto';
import { UpdateProductDto } from './dto/update-product.dto';
@Controller('products')
export class ProductsController {
constructor(private readonly productsService: ProductsService) {}
@Get()
findAll(@Query('category') category?: string) {
return this.productsService.findAll(category);
}
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.productsService.findOne(id);
}
@Post()
create(@Body() dto: CreateProductDto) {
return this.productsService.create(dto);
}
@Patch(':id')
update(@Param('id', ParseIntPipe) id: number, @Body() dto: UpdateProductDto) {
return this.productsService.update(id, dto);
}
@Delete(':id')
@HttpCode(204)
remove(@Param('id', ParseIntPipe) id: number) {
this.productsService.remove(id);
}
}The Service
src/products/products.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { CreateProductDto } from './dto/create-product.dto';
import { UpdateProductDto } from './dto/update-product.dto';
export interface Product { id: number; name: string; description?: string; price: number; stock: number; category: string; }
@Injectable()
export class ProductsService {
private products: Product[] = [
{ id: 1, name: 'boAt Airdopes 141', price: 1299, stock: 120, category: 'audio' },
{ id: 2, name: 'Redmi Note 13', price: 17999, stock: 35, category: 'mobiles' },
];
private nextId = 3;
findAll(category?: string) {
return category ? this.products.filter((p) => p.category === category) : this.products;
}
findOne(id: number) {
const product = this.products.find((p) => p.id === id);
if (!product) throw new NotFoundException(`Product ${id} not found`);
return product;
}
create(dto: CreateProductDto) {
const product = { id: this.nextId++, ...dto };
this.products.push(product);
return product;
}
update(id: number, dto: UpdateProductDto) {
const product = this.findOne(id);
Object.assign(product, dto);
return product;
}
remove(id: number) {
this.findOne(id);
this.products = this.products.filter((p) => p.id !== id);
}
}Testing Every Endpoint
Example
curl http://localhost:3000/products
curl -X POST http://localhost:3000/products -H "Content-Type: application/json" \
-d '{"name":"OnePlus Nord Buds 2","price":2299,"stock":60,"category":"audio"}'
curl -X PATCH http://localhost:3000/products/3 -H "Content-Type: application/json" -d '{"stock":55}'
curl "http://localhost:3000/products?category=audio"
curl -i -X DELETE http://localhost:3000/products/1
curl -i http://localhost:3000/products/1Example output
[{"id":1,"name":"boAt Airdopes 141","price":1299,"stock":120,"category":"audio"},{"id":2,"name":"Redmi Note 13","price":17999,"stock":35,"category":"mobiles"}]
{"id":3,"name":"OnePlus Nord Buds 2","price":2299,"stock":60,"category":"audio"}
{"id":3,"name":"OnePlus Nord Buds 2","price":2299,"stock":55,"category":"audio"}
[{"id":1,"name":"boAt Airdopes 141","price":1299,"stock":120,"category":"audio"},{"id":3,"name":"OnePlus Nord Buds 2","price":2299,"stock":55,"category":"audio"}]
HTTP/1.1 204 No Content
HTTP/1.1 404 Not Found
{"message":"Product 1 not found","error":"Not Found","statusCode":404}Exercise
Exercise: Add GET /products/:id/stock that returns { id, stock, inStock } where inStock is true when stock is greater than 0.
Show answer
@Get(':id/stock')
stock(@Param('id', ParseIntPipe) id: number) {
const { stock } = this.productsService.findOne(id);
return { id, stock, inStock: stock > 0 };
}