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?
Client-Server - Tách biệt client và server
Stateless - Mỗi request chứa đủ thông tin
Cacheable - Response có thể được cache
Uniform Interface - Sử dụng HTTP methods và status codes đúng cách
Layered System - Có thể có nhiều layers
Method Purpose Idempotent
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
Code Meaning Usage
200OK GET, PUT, PATCH thành công
201Created POST tạo mới thành công
204No Content DELETE thành công
400Bad Request Validation failed
401Unauthorized Chưa authenticate
403Forbidden Không có permission
404Not Found Resource không tồn tại
500Internal Server Error Server error
// ✅ 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
Note: Model Binding và Model Validation đã được chuyển sang các file riêng để dễ học hơn:
[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);
}
}
[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; }
}