Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

RESTful API

Overview Questions

  • REST là gì và tại sao nó quan trọng?
  • Các HTTP methods nào được sử dụng và khi nào dùng?
  • Status codes nào nên dùng cho từng trường hợp?
  • URL naming conventions như thế nào cho đúng chuẩn?
  • Paging, filtering, sorting được implement ra sao?

Thiết kế API đúng chuẩn REST

REST Principles

  1. Client-Server - Tách biệt client và server
  2. Stateless - Mỗi request chứa đủ thông tin
  3. Cacheable - Response có thể được cache
  4. Uniform Interface - Sử dụng HTTP methods và status codes đúng cách
  5. Layered System - Có thể có nhiều layers

HTTP Methods

MethodPurposeIdempotent
GETLấy resource✅ Yes
POSTTạo resource mới❌ No
PUTThay thế toàn bộ resource✅ Yes
PATCHCập nhật một phần resource❌ No
DELETEXóa resource✅ Yes

Status Codes

CodeMeaningUsage
200OKGET, PUT, PATCH thành công
201CreatedPOST tạo mới thành công
204No ContentDELETE thành công
400Bad RequestValidation failed
401UnauthorizedChưa authenticate
403ForbiddenKhông có permission
404Not FoundResource không tồn tại
500Internal Server ErrorServer error

URL Naming Conventions

// ✅ Tốt
GET    /api/products              // Lấy danh sách products
GET    /api/products/1          // Lấy product có id = 1
POST   /api/products             // Tạo product mới
PUT    /api/products/1          // Cập nhật product 1
DELETE /api/products/1          // Xóa product 1

// ❌ Tránh
GET    /api/getProducts
GET    /api/ProductController/GetAll
POST   /api/createProduct

Model Binding & Validation

Note: Model Binding và Model Validation đã được chuyển sang các file riêng để dễ học hơn:

Quick Reference

[ApiController]
public class ProductsController : ControllerBase
{
    // FromRoute - Từ URL path
    [HttpGet("{id}")]
    public IActionResult GetById([FromRoute] int id)
    {
        return Ok(id);
    }
    
    // FromQuery - Từ query string
    [HttpGet]
    public IActionResult Search([FromQuery] string name, [FromQuery] int page = 1)
    {
        return Ok(new { name, page });
    }
    
    // FromBody - Từ request body (JSON)
    [HttpPost]
    public IActionResult Create([FromBody] Product product)
    {
        // [ApiController] tự động validate ModelState
        return CreatedAtAction(nameof(GetById), new { id = product.Id }, product);
    }
}

Paging, Filtering, Sorting

[HttpGet]
public IActionResult GetProducts(
    [FromQuery] PagingParameters paging,
    [FromQuery] ProductFilter filter,
    [FromQuery] string sortBy = "Name")
{
    var query = _context.Products.AsQueryable();
    
    // Filtering
    if (!string.IsNullOrEmpty(filter.Category))
        query = query.Where(p => p.Category == filter.Category);
    
    if (filter.MinPrice.HasValue)
        query = query.Where(p => p.Price >= filter.MinPrice.Value);
    
    // Sorting
    query = sortBy?.ToLower() switch
    {
        "price" => query.OrderBy(p => p.Price),
        "pricedesc" => query.OrderByDescending(p => p.Price),
        _ => query.OrderBy(p => p.Name)
    };
    
    // Paging
    var total = query.Count();
    var items = query
        .Skip((paging.Page - 1) * paging.PageSize)
        .Take(paging.PageSize)
        .ToList();
    
    return Ok(new PagedResult(items, total, paging.Page, paging.PageSize));
}

public class PagingParameters
{
    [FromQuery(Name = "page")]
    public int Page { get; set; } = 1;
    
    [FromQuery(Name = "pageSize")]
    public int PageSize { get; set; } = 10;
}

public class ProductFilter
{
    [FromQuery(Name = "category")]
    public string Category { get; set; }
    
    [FromQuery(Name = "minPrice")]
    public decimal? MinPrice { get; set; }
    
    [FromQuery(Name = "maxPrice")]
    public decimal? MaxPrice { get; set; }
}