Kiến thức nền tảng về Web API (HTTP & Auth)

Trước khi bắt tay vào viết code, bạn cần hiểu rõ cách thức hoạt động của giao thức HTTP và các phương pháp bảo mật cơ bản. Đây là nền tảng cốt lõi của bất kỳ hệ thống Backend nào.

1. Mô hình HTTP Request & Response

Giao thức HTTP hoạt động theo mô hình Client-Server. Client (ví dụ: trình duyệt, Postman, ứng dụng điện thoại) gửi một Request yêu cầu dịch vụ, và Server (ứng dụng NestJS của bạn) phản hồi bằng một Response.
Một Request gửi lên Server bao gồm:
  • HTTP Method: Hành động muốn thực hiện (GET, POST, PATCH…).
  • URL/Path: Địa chỉ tài nguyên (ví dụ: /products/12).
  • Params (Tham số):
    • Path Params: Nằm trực tiếp trên URL để định danh tài nguyên (ví dụ: /products/:id -> /products/12).
    • Query Params: Tham số tùy chọn sau dấu ? để lọc, sắp xếp, phân trang (ví dụ: /products?limit=10&page=2).
  • Headers: Metadata chứa thông tin bổ sung như kiểu dữ liệu (Content-Type: application/json), thông tin xác thực (Authorization).
  • Body: Dữ liệu thực tế gửi lên (chủ yếu dùng cho POST, PUT, PATCH dưới định dạng JSON).

2. Các HTTP Method phổ biến (RESTful API)

Trong thiết kế RESTful API, mỗi phương thức HTTP ánh xạ tương ứng với một thao tác CRUD trên tài nguyên:
MethodThao tác CRUDÝ nghĩaVí dụ Endpoint
GETRead (Đọc)Lấy thông tin tài nguyên (không thay đổi dữ liệu trên server).GET /products (Lấy tất cả)
POSTCreate (Tạo)Tạo mới một tài nguyên.POST /products (Gửi kèm Body)
PATCHUpdate (Sửa)Cập nhật một phần dữ liệu của tài nguyên đã tồn tại.PATCH /products/1 (Chỉ cập nhật giá)
PUTUpdate (Sửa)Thay thế toàn bộ dữ liệu của tài nguyên.PUT /products/1 (Thay thế tất cả trường)
DELETEDelete (Xóa)Xóa bỏ một tài nguyên.DELETE /products/1 (Xóa sản phẩm 1)

3. Mã trạng thái HTTP (Status Codes) cốt lõi

Server sử dụng các mã số tiêu chuẩn để phản hồi nhanh trạng thái của request:
  • 200 OK / 201 Created: Yêu cầu thành công (201 dùng khi tạo mới dữ liệu thành công).
  • 400 Bad Request: Dữ liệu gửi lên không hợp lệ (ví dụ: sai định dạng JSON, thiếu trường bắt buộc).
  • 401 Unauthorized: Yêu cầu chưa được xác thực (chưa đăng nhập hoặc token không hợp lệ).
  • 403 Forbidden: Đã xác thực nhưng tài khoản không có quyền truy cập tài nguyên này.
  • 404 Not Found: Tài nguyên yêu cầu không tồn tại trên hệ thống.
  • 500 Internal Server Error: Lỗi phát sinh từ phía Server (bug trong code, lỗi kết nối DB).

4. Xác thực & Phân quyền (Authentication & Authorization)

Bảo mật là yếu tố sống còn của Backend.
  • Xác thực (Authentication - AuthN): Xác định danh tính người dùng là ai (thường qua Đăng nhập bằng Email/Password).
  • Phân quyền (Authorization - AuthZ): Xác định người dùng đó có được phép làm gì (ví dụ: chỉ Admin mới được xóa sản phẩm).
Trong thế giới API hiện đại, JWT (JSON Web Token) là tiêu chuẩn phổ biến nhất để thực hiện cơ chế xác thực Stateless:
  1. Người dùng gửi thông tin đăng nhập thành công.
  2. Server tạo ra một chuỗi mã hóa ký số bí mật gọi là Token (JWT) và trả về cho Client.
  3. Ở những Request sau, Client đính kèm Token này vào Header:
    Authorization: Bearer <your_jwt_token_here>
    
  4. Server giải mã Token này để nhận biết danh tính người dùng mà không cần truy vấn lại cơ sở dữ liệu.

Các bước thực hiện bằng Claude Code

Thay vì phải tự viết từng dòng code, bạn có thể đóng vai trò là “Kiến trúc sư” và sử dụng Claude Code để thực thi việc viết mã nguồn. Dưới đây là quy trình từng bước ra lệnh cho Claude Code xây dựng hệ thống CRUD hoàn chỉnh.
1

Bước 1: Cài đặt các thư viện cơ sở dữ liệu

Hãy ra lệnh cho Claude Code cài đặt các thư viện cần thiết để sử dụng SQLite và TypeORM trong NestJS:Prompt yêu cầu Claude Code:
Hãy cài đặt các thư viện cần thiết để tích hợp TypeORM với SQLite, đồng thời cài đặt các thư viện 'class-validator' và 'class-transformer' để hỗ trợ validate dữ liệu đầu vào.
Claude Code sẽ tự động đề xuất và chạy các lệnh (sau khi bạn xác nhận đồng ý):
npm install @nestjs/typeorm typeorm sqlite3 class-validator class-transformer
npm install --save-dev @types/sqlite3
2

Bước 2: Cấu hình Kết nối Cơ sở dữ liệu

Yêu cầu Claude Code thiết lập cấu hình kết nối SQLite trong module gốc app.module.ts.Prompt yêu cầu Claude Code:
Hãy cấu hình kết nối cơ sở dữ liệu SQLite trong file 'src/app.module.ts'. 
- Đặt tên file database lưu trữ là 'database.sqlite'.
- Cấu hình tự động tải các thực thể (entities).
- Bật chế độ 'synchronize: true' để tự động đồng bộ bảng dữ liệu trong môi trường phát triển.
Mã nguồn sau khi Claude Code chỉnh sửa trong src/app.module.ts sẽ tương đương:
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ProductsModule } from './products/products.module';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'sqlite',
      database: 'database.sqlite',
      entities: [__dirname + '/**/*.entity{.ts,.js}'],
      synchronize: true,
    }),
    ProductsModule,
  ],
})
export class AppModule {}
3

Bước 3: Khởi tạo module sản phẩm (Products)

Yêu cầu Claude Code sử dụng Nest CLI để sinh cấu trúc thư mục chuẩn cho module quản lý sản phẩm.Prompt yêu cầu Claude Code:
Hãy sử dụng Nest CLI để sinh cấu trúc thư mục hoàn chỉnh (gồm Module, Service, Controller) cho đối tượng 'products'.
Claude Code sẽ thực hiện lệnh terminal:
nest g module products
nest g service products
nest g controller products
4

Bước 4: Định nghĩa thực thể Product (Entity)

Yêu cầu Claude Code thiết kế bảng sản phẩm vật lý thông qua định nghĩa Entity.Prompt yêu cầu Claude Code:
Hãy thiết kế một Entity 'Product' trong file 'src/products/entities/product.entity.ts' đại diện cho bảng 'products' trong SQLite:
- id: Khóa chính tự tăng (auto-increment integer).
- name: string, bắt buộc.
- price: decimal (chữ số thập phân), bắt buộc.
- description: string, nullable (cho phép trống).
- isAvailable: boolean, mặc định true.
- createdAt: timestamp, tự động sinh thời gian.
Đồng thời, hãy nhớ đăng ký thực thể này vào đối tượng 'TypeOrmModule.forFeature' trong file 'products.module.ts'.
Đoạn mã src/products/entities/product.entity.ts mà Claude Code tạo ra:
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from 'typeorm';

@Entity('products')
export class Product {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @Column({ type: 'decimal', precision: 10, scale: 2 })
  price: number;

  @Column({ nullable: true })
  description: string;

  @Column({ default: true })
  isAvailable: boolean;

  @CreateDateColumn()
  createdAt: Date;
}
5

Bước 5: Tạo DTOs và cấu hình Validation toàn cục

Yêu cầu Claude Code thiết lập các đối tượng truyền dữ liệu (DTO) để kiểm soát dữ liệu đầu vào chặt chẽ.Prompt yêu cầu Claude Code:
Hãy tạo CreateProductDto và UpdateProductDto trong thư mục 'src/products/dto' sử dụng 'class-validator' để xác thực dữ liệu đầu vào:
- name: phải là string, độ dài tối thiểu 3 ký tự.
- price: phải là số nguyên hoặc số thập phân hợp lệ.
- description: tùy chọn.
- isAvailable: tùy chọn, kiểm tra kiểu boolean.

Hãy viết UpdateProductDto kế thừa từ CreateProductDto bằng cách sử dụng 'PartialType' từ '@nestjs/mapped-types'.

Đồng thời, kích hoạt 'ValidationPipe' toàn cục trong file 'src/main.ts' để cơ chế validate tự động hoạt động.
Đoạn mã cấu hình src/main.ts của Claude Code sau yêu cầu:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  
  // Kích hoạt xác thực dữ liệu tự động cho mọi request
  app.useGlobalPipes(new ValidationPipe());
  
  await app.listen(3000);
}
bootstrap();
6

Bước 6: Viết logic xử lý trong Service

Yêu cầu Claude Code hiện thực hóa các chức năng nghiệp vụ truy vấn cơ sở dữ liệu trong Service.Prompt yêu cầu Claude Code:
Hãy hoàn thiện mã nguồn 'src/products/products.service.ts':
- Inject Product Repository thông qua Constructor.
- Hàm 'create': Tạo mới và lưu sản phẩm.
- Hàm 'findAll': Lấy toàn bộ danh sách sản phẩm.
- Hàm 'findOne': Tìm kiếm sản phẩm theo ID. Nếu không tìm thấy, hãy ném ra 'NotFoundException' kèm thông báo lỗi phù hợp.
- Hàm 'update': Tìm sản phẩm theo ID, sử dụng hàm 'merge' của TypeORM để cập nhật các trường được thay đổi, sau đó lưu lại.
- Hàm 'remove': Tìm sản phẩm theo ID và xóa khỏi CSDL.
Đoạn mã logic nghiệp vụ trong src/products/products.service.ts do Claude Code xây dựng:
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Product } from './entities/product.entity';
import { CreateProductDto } from './dto/create-product.dto';
import { UpdateProductDto } from './dto/update-product.dto';

@Injectable()
export class ProductsService {
  constructor(
    @InjectRepository(Product)
    private readonly productRepository: Repository<Product>,
  ) {}

  async create(createProductDto: CreateProductDto): Promise<Product> {
    const newProduct = this.productRepository.create(createProductDto);
    return await this.productRepository.save(newProduct);
  }

  async findAll(): Promise<Product[]> {
    return await this.productRepository.find();
  }

  async findOne(id: number): Promise<Product> {
    const product = await this.productRepository.findOneBy({ id });
    if (!product) {
      throw new NotFoundException(`Không tìm thấy sản phẩm có ID #${id}`);
    }
    return product;
  }

  async update(id: number, updateProductDto: UpdateProductDto): Promise<Product> {
    const product = await this.findOne(id);
    const updatedProduct = this.productRepository.merge(product, updateProductDto);
    return await this.productRepository.save(updatedProduct);
  }

  async remove(id: number): Promise<void> {
    const product = await this.findOne(id);
    await this.productRepository.remove(product);
  }
}
7

Bước 7: Định nghĩa các đầu Endpoint trong Controller

Yêu cầu Claude Code thiết lập bộ điều hướng Router HTTP ánh xạ nghiệp vụ.Prompt yêu cầu Claude Code:
Hãy hoàn thiện file 'src/products/products.controller.ts' để định nghĩa các endpoint HTTP kết nối tới ProductsService:
- POST /products: Tạo sản phẩm mới (Body là CreateProductDto).
- GET /products: Lấy danh sách sản phẩm.
- GET /products/:id: Lấy chi tiết sản phẩm (sử dụng ParseIntPipe cho tham số id).
- PATCH /products/:id: Cập nhật sản phẩm (sử dụng ParseIntPipe cho id, Body là UpdateProductDto).
- DELETE /products/:id: Xóa sản phẩm (sử dụng ParseIntPipe cho id).
Bộ điều phối Endpoint tại src/products/products.controller.ts của Claude Code:
import { Controller, Get, Post, Body, Patch, Param, Delete, ParseIntPipe } 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) {}

  @Post()
  create(@Body() createProductDto: CreateProductDto) {
    return this.productsService.create(createProductDto);
  }

  @Get()
  findAll() {
    return this.productsService.findAll();
  }

  @Get(':id')
  findOne(@Param('id', ParseIntPipe) id: number) {
    return this.productsService.findOne(id);
  }

  @Patch(':id')
  update(@Param('id', ParseIntPipe) id: number, @Body() updateProductDto: UpdateProductDto) {
    return this.productsService.update(id, updateProductDto);
  }

  @Delete(':id')
  remove(@Param('id', ParseIntPipe) id: number) {
    return this.productsService.remove(id);
  }
}

Kiểm thử các API Endpoint bằng Claude Code

Bạn có thể yêu cầu Claude Code khởi chạy máy chủ phát triển và thực hiện kiểm thử tự động trực tiếp trên terminal: Prompt yêu cầu kiểm thử hệ thống:
Hãy chạy thử máy chủ phát triển của ứng dụng, sau đó sử dụng công cụ cURL kiểm thử tuần tự các API sau:
1. Tạo một sản phẩm mới (POST /products).
2. Lấy toàn bộ danh sách sản phẩm (GET /products).
3. Thử tạo một sản phẩm bị lỗi validate (tên dưới 3 ký tự) để kiểm tra ValidationPipe.
4. Cập nhật thông tin sản phẩm vừa tạo (PATCH /products/:id).
5. Xóa sản phẩm đó (DELETE /products/:id).
Claude Code sẽ tự khởi động môi trường npm run start:dev và thực hiện các lệnh gọi cURL dưới nền để báo cáo kết quả chi tiết cho bạn!

Kiểm thử các API Endpoint bằng Postman (Giao diện trực quan)

Nếu muốn tự mình kiểm tra và quan sát trực quan dữ liệu phản hồi, bạn hãy sử dụng phần mềm Postman theo các bước hướng dẫn chi tiết dưới đây.
1

Bước 1: Khởi tạo Collection trong Postman

  1. Mở Postman, chọn thẻ Collections ở thanh menu bên trái.
  2. Nhấp chuột vào biểu tượng dấu cộng + để tạo mới một Collection và đặt tên là My First NestJS CRUD.
2

Bước 2: Tạo Request thêm mới sản phẩm (POST)

  1. Nhấp chuột phải vào Collection vừa tạo, chọn Add Request.
  2. Đổi tên request thành Create Product.
  3. Chọn phương thức là POST và điền URL: http://localhost:3000/products.
  4. Nhấp chọn thẻ Body -> Chọn kiểu dữ liệu là raw -> Định dạng là JSON và dán nội dung:
    {
      "name": "Bàn phím cơ không dây",
      "price": 1500000,
      "description": "Bàn phím cơ sử dụng Red Switch êm ái"
    }
    
  5. Nhấn nút Send. Bạn sẽ nhận lại mã trạng thái 201 Created đi kèm dữ liệu sản phẩm vừa tạo (có thêm trường idcreatedAt tự sinh).
3

Bước 3: Tạo Request lấy danh sách sản phẩm (GET)

  1. Chọn Add Request trong Collection của bạn, đặt tên là Get All Products.
  2. Chọn phương thức là GET và điền URL: http://localhost:3000/products.
  3. Nhấn Send và xem danh sách toàn bộ sản phẩm trả về dưới dạng một mảng JSON dưới phần Response.
4

Bước 4: Tạo Request cập nhật sản phẩm (PATCH)

  1. Chọn Add Request, đặt tên là Update Product.
  2. Chọn phương thức là PATCH và điền URL với ID của sản phẩm cần sửa (ví dụ sản phẩm số 1): http://localhost:3000/products/1.
  3. Chọn thẻ Body -> raw -> JSON và điền nội dung thay đổi:
    {
      "price": 1400000,
      "isAvailable": false
    }
    
  4. Nhấn Send và kiểm tra xem thông tin sản phẩm đã được cập nhật thành công với mã phản hồi 200 OK.
5

Bước 5: Tạo Request xóa sản phẩm (DELETE)

  1. Chọn Add Request, đặt tên là Delete Product.
  2. Chọn phương thức là DELETE và điền URL của sản phẩm cần xóa: http://localhost:3000/products/1.
  3. Nhấn Send để thực hiện lệnh xóa. Bạn sẽ thấy CSDL cập nhật và sản phẩm đó không còn xuất hiện khi chạy lại request lấy danh sách.