1. Tổng quan về REST API và HTTP
Trong quá trình phát triển một ứng dụng, chẳng hạn như ứng dụng bán hàng bằng Flutter, phần giao diện người dùng không thể tự biết toàn bộ thông tin sản phẩm, tài khoản, đơn hàng hay số lượng hàng tồn kho. Những dữ liệu này thường được lưu trữ và xử lý ở phía máy chủ (Backend).
Ví dụ, khi người dùng mở ứng dụng bán sữa, ứng dụng cần lấy danh sách sản phẩm từ máy chủ. Khi người dùng đăng nhập, ứng dụng phải gửi thông tin tài khoản để máy chủ kiểm tra. Khi người dùng đặt hàng, ứng dụng phải gửi thông tin sản phẩm, số lượng và địa chỉ giao hàng để Backend xử lý.
Để hai phía có thể trao đổi dữ liệu, chúng cần có một cách giao tiếp thống nhất. Trong các ứng dụng web và ứng dụng di động hiện nay, HTTP thường được sử dụng làm giao thức truyền thông, còn REST là một phong cách kiến trúc phổ biến để thiết kế API.
Hai khái niệm này thường xuất hiện cùng nhau dưới tên REST API & HTTP, nhưng chúng không phải là một. HTTP quy định cách các bên gửi và nhận thông điệp, còn REST cung cấp các nguyên tắc để tổ chức API và cách Client tương tác với tài nguyên trên Server.
Để hiểu đúng hai khái niệm này, trước tiên cần hiểu cách một ứng dụng được tổ chức và dữ liệu được truyền đi như thế nào.
2. Client, Server, Frontend và Backend là gì?
2.1. Client là gì?
Client là thành phần khởi tạo yêu cầu đến một dịch vụ để lấy dữ liệu hoặc yêu cầu thực hiện một công việc.
Trong một hệ thống phần mềm, Client có thể là trình duyệt web, ứng dụng Flutter trên điện thoại, ứng dụng desktop hoặc một chương trình khác cần sử dụng dữ liệu từ máy chủ.
Ví dụ, trong ứng dụng bán sữa:
- Người dùng mở màn hình danh sách sản phẩm.
- Ứng dụng Flutter gửi yêu cầu lấy danh sách sản phẩm.
- Ứng dụng nhận dữ liệu do máy chủ trả về.
- Flutter sử dụng dữ liệu đó để hiển thị tên, hình ảnh và giá sản phẩm lên giao diện.
Như vậy, Flutter đóng vai trò Client khi giao tiếp với Backend.
2.2. Server là gì?
Server là thành phần tiếp nhận yêu cầu từ Client, xử lý yêu cầu và trả về kết quả phù hợp.
Server có thể thực hiện nhiều công việc như xác thực tài khoản, kiểm tra quyền truy cập, truy vấn cơ sở dữ liệu, tính toán giá trị đơn hàng, kiểm tra tồn kho và lưu thông tin mới.
Ví dụ, khi khách hàng đặt mua hai hộp sữa, Server cần kiểm tra sản phẩm có tồn tại hay không, số lượng tồn kho có đủ hay không, giá bán có hợp lệ hay không và khách hàng có được phép đặt hàng hay không. Nếu dữ liệu hợp lệ, Server mới tiến hành xử lý đơn hàng theo quy tắc của hệ thống.
Server không nhất thiết phải là một máy tính vật lý riêng biệt. Trong thực tế, nó có thể là một máy chủ trên Internet, một máy chủ đám mây hoặc một dịch vụ chạy trên máy tính dùng để phát triển phần mềm.
2.3. Frontend và Backend phối hợp với nhau như thế nào?
Frontend là phần người dùng trực tiếp tương tác, chẳng hạn như nút bấm, biểu mẫu, danh sách sản phẩm và màn hình đơn hàng. Backend chịu trách nhiệm xử lý nghiệp vụ, quản lý dữ liệu và cung cấp các chức năng cho Frontend thông qua API.
Một hệ thống bán hàng có thể được tổ chức như sau:
Flutter (Frontend) → HTTP Request → REST API (Backend) → Database.
Khi có kết quả, dữ liệu đi theo chiều ngược lại:
Database → Backend xử lý → HTTP Response → Flutter hiển thị kết quả.
Cần lưu ý rằng REST API không phải là toàn bộ Backend. REST API là giao diện giao tiếp mà Backend cung cấp để Client có thể sử dụng các chức năng của hệ thống.
Ngoài ra, API không nhất thiết phải kết nối trực tiếp với cơ sở dữ liệu. Trong một ứng dụng được thiết kế tốt, yêu cầu thường đi qua các lớp xử lý, chẳng hạn như định tuyến, xác thực, kiểm tra dữ liệu, xử lý nghiệp vụ và lớp truy cập dữ liệu, trước khi đến cơ sở dữ liệu.
3. HTTP là gì?
HTTP là viết tắt của Hypertext Transfer Protocol, nghĩa là Giao thức truyền tải siêu văn bản. Đây là một giao thức ở tầng ứng dụng, quy định cách các bên trao đổi thông điệp để truy cập tài nguyên hoặc thực hiện các hoạt động trên hệ thống.
HTTP ban đầu được phát triển để phục vụ việc trao đổi tài liệu trên Web. Ngày nay, nó còn được sử dụng rộng rãi để truyền dữ liệu JSON, hình ảnh, tệp tin và nhiều loại dữ liệu khác giữa Client và Server.
HTTP không phải là ngôn ngữ lập trình, cũng không phải cơ sở dữ liệu hay một framework. Nó là một tập hợp các quy tắc giao tiếp mà những hệ thống tương thích có thể tuân theo.
Ví dụ, Flutter có thể gửi một yêu cầu HTTP đến Backend được xây dựng bằng Laravel, Node.js, Django hoặc một công nghệ khác. Nếu cả hai phía tuân thủ các quy tắc HTTP và thống nhất cách trao đổi dữ liệu, chúng vẫn có thể giao tiếp với nhau dù được viết bằng các ngôn ngữ lập trình khác nhau.
Đây là một trong những lợi ích quan trọng của HTTP: các hệ thống không cần sử dụng cùng một ngôn ngữ lập trình để trao đổi dữ liệu.
3.1. Vì sao cần HTTP?
Hãy tưởng tượng Flutter và Backend là hai chương trình độc lập. Flutter cần yêu cầu Backend cung cấp danh sách sản phẩm, nhưng nếu mỗi chương trình tự quy định cách gửi dữ liệu mà không có quy tắc chung thì việc giao tiếp sẽ trở nên phức tạp.
Hai bên phải thống nhất hàng loạt vấn đề: yêu cầu được gửi đi theo định dạng nào, dữ liệu nằm ở đâu, làm sao biết yêu cầu muốn đọc hay tạo dữ liệu, làm sao Server trả về kết quả và làm sao thông báo lỗi.
HTTP giải quyết phần lớn những vấn đề thuộc về giao tiếp này bằng một hệ thống quy tắc chung. Nhờ vậy, Client có thể gửi yêu cầu và Server có thể trả lời theo định dạng mà hai bên hiểu được.
Tuy nhiên, HTTP không tự quyết định toàn bộ nghiệp vụ. Chẳng hạn, HTTP có phương thức POST nhưng không quy định riêng rằng POST phải có nghĩa là tạo đơn hàng. Ý nghĩa cụ thể phụ thuộc vào API và cách Backend được thiết kế.
3.2. HTTP hoạt động như thế nào?
Trong mô hình HTTP thông thường, Client gửi một HTTP Request và Server trả về một HTTP Response.
Quá trình cơ bản gồm các bước:
- Client xác định địa chỉ cần truy cập, bao gồm máy chủ và đường dẫn tài nguyên.
- Client thiết lập kết nối với Server. Với HTTP thông thường, việc truyền dữ liệu có thể diễn ra qua TCP; với HTTP/3, giao thức truyền tải được sử dụng là QUIC.
- Client gửi yêu cầu, bao gồm phương thức HTTP, đường dẫn, các header và có thể có phần thân dữ liệu (body).
- Server tiếp nhận yêu cầu, xác định cách xử lý và thực hiện các bước cần thiết.
- Server trả về phản hồi gồm mã trạng thái, các header và có thể có body chứa dữ liệu hoặc thông báo.
- Client đọc phản hồi, kiểm tra kết quả và cập nhật giao diện hoặc thông báo lỗi cho người dùng.
Đây là mô hình khái quát. Trong thực tế, một ứng dụng có thể sử dụng kết nối được giữ lại để gửi nhiều yêu cầu, đi qua proxy hoặc API Gateway, và nhận dữ liệu theo những cách khác nhau tùy thiết kế.
3.3. HTTP và HTTPS khác nhau như thế nào?
HTTP là giao thức trao đổi thông điệp. HTTPS là HTTP được bảo vệ bằng TLS, giúp mã hóa dữ liệu truyền trên đường truyền, kiểm tra tính toàn vẹn dữ liệu và xác thực danh tính máy chủ theo cơ chế chứng chỉ.
Nếu ứng dụng gửi mật khẩu hoặc thông tin cá nhân qua kết nối HTTP không được mã hóa, dữ liệu có nguy cơ bị người khác đọc hoặc sửa đổi khi truyền qua mạng. Vì vậy, các ứng dụng thực tế nên sử dụng HTTPS, đặc biệt khi truyền thông tin đăng nhập, thông tin khách hàng và dữ liệu đơn hàng.
Tuy nhiên, HTTPS không có nghĩa là hệ thống tự động an toàn hoàn toàn. Backend vẫn cần xác thực người dùng, kiểm tra quyền truy cập, xác thực dữ liệu đầu vào và bảo vệ thông tin nhạy cảm.
4. HTTP Request là gì?
HTTP Request, hay yêu cầu HTTP, là thông điệp do Client gửi đến Server để yêu cầu truy cập một tài nguyên hoặc thực hiện một hành động được API hỗ trợ.
Một HTTP Request thường có các thành phần sau:
- Method: Cho biết loại hành động mà Client muốn thực hiện.
- URL: Xác định địa chỉ của tài nguyên hoặc điểm cuối API.
- Headers: Chứa thông tin mô tả yêu cầu, chẳng hạn loại dữ liệu, định dạng mong muốn hoặc thông tin xác thực.
- Body: Chứa dữ liệu mà Client gửi lên Server, nếu yêu cầu cần có dữ liệu.
Ví dụ, khi người dùng đăng nhập vào ứng dụng bán hàng, Flutter có thể gửi một yêu cầu như sau:
POST /api/login HTTP/1.1
Host: api.milkstore.example
Content-Type: application/json
Accept: application/json
{
"email": "[email protected]",
"password": "example-password"
}
Đây là ví dụ minh họa, sử dụng tên miền dành cho ví dụ chứ không phải máy chủ thật.
Phân tích yêu cầu trên:
POSTlà phương thức HTTP được sử dụng để gửi yêu cầu đăng nhập./api/loginlà đường dẫn mà Backend dùng để tiếp nhận yêu cầu.Hostxác định máy chủ trong ví dụ HTTP/1.1.Content-Type: application/jsoncho biết phần body được gửi theo định dạng JSON.Accept: application/jsoncho biết Client mong muốn nhận phản hồi dạng JSON.- Phần JSON chứa email và mật khẩu mà Client gửi để Backend xác thực.
Một điểm cần hiểu rõ là URL không phải toàn bộ yêu cầu HTTP. URL chỉ giúp xác định địa chỉ đích và tài nguyên; phương thức, header và body còn cung cấp các thông tin khác cần thiết để Server xử lý.
Trong thực tế, thư viện HTTP sẽ hỗ trợ tạo thông điệp và gửi qua mạng. Lập trình viên thường không phải tự viết toàn bộ thông điệp HTTP bằng tay.
5. HTTP Response là gì?
HTTP Response, hay phản hồi HTTP, là thông điệp Server gửi lại cho Client sau khi tiếp nhận yêu cầu.
Một phản hồi HTTP thường gồm:
- Status code: Mã trạng thái cho biết kết quả xử lý ở mức HTTP.
- Headers: Chứa thông tin về phản hồi, chẳng hạn định dạng nội dung, độ dài dữ liệu hoặc thông tin lưu bộ nhớ đệm.
- Body: Chứa dữ liệu trả về, thông báo lỗi hoặc nội dung khác nếu có.
Ví dụ, sau khi đăng nhập thành công, Server có thể trả về:
HTTP/1.1 200 OK
Content-Type: application/json
{
"message": "Đăng nhập thành công",
"data": {
"id": 12,
"name": "Nguyen Van A"
}
}
Trong ví dụ này, 200 OK cho biết yêu cầu đã được xử lý thành công ở mức HTTP. Header cho biết nội dung phản hồi là JSON. Body chứa thông báo và thông tin người dùng mà ứng dụng có thể sử dụng để hiển thị.
Đây chỉ là một định dạng phản hồi minh họa. API thực tế có thể trả về cấu trúc khác, chẳng hạn thêm trường success, meta hoặc errors.
Điều quan trọng là Frontend và Backend phải thống nhất cấu trúc dữ liệu. Nếu Backend trả về trường name nhưng Flutter lại cố đọc trường full_name, ứng dụng có thể không lấy được tên người dùng dù yêu cầu đã thành công.
6. HTTP Method là gì? Những phương thức quan trọng
HTTP Method là phương thức được sử dụng trong yêu cầu HTTP để thể hiện ý định của Client đối với tài nguyên. Khi thiết kế REST API, việc lựa chọn phương thức phù hợp giúp API dễ hiểu và nhất quán hơn.
6.1. GET – Lấy dữ liệu
GET được dùng để yêu cầu lấy thông tin từ Server. Trong thiết kế REST API, GET thường được sử dụng để đọc danh sách hoặc chi tiết tài nguyên.
Ví dụ:
GET /api/products
Yêu cầu này có thể được dùng để lấy danh sách sản phẩm.
Muốn lấy thông tin sản phẩm có ID bằng 15, Client có thể gửi:
GET /api/products/15
Server tiếp nhận yêu cầu, kiểm tra điều kiện truy cập nếu cần, truy vấn dữ liệu và trả về kết quả.
Theo ngữ nghĩa HTTP, GET là phương thức an toàn (safe), nghĩa là Client không yêu cầu thực hiện thay đổi trạng thái nghiệp vụ của tài nguyên. GET cũng có tính idempotent, nghĩa là việc gửi lặp lại cùng một yêu cầu không làm thay đổi kết quả nghiệp vụ theo ý định của phương thức.
Điều này không có nghĩa Server tuyệt đối không được ghi log hoặc cập nhật dữ liệu kỹ thuật khi xử lý GET. Ý nghĩa của safe là hành động được yêu cầu không nhằm thay đổi trạng thái nghiệp vụ.
6.2. POST – Gửi dữ liệu hoặc yêu cầu xử lý
POST được dùng để yêu cầu Server xử lý phần dữ liệu gửi lên theo chức năng của điểm cuối API. Một cách sử dụng phổ biến là tạo tài nguyên mới.
Ví dụ tạo đơn hàng:
POST /api/orders
Content-Type: application/json
{
"items": [
{
"product_id": 15,
"quantity": 2
}
],
"payment_method": "cod"
}
Backend sẽ tiếp nhận dữ liệu, kiểm tra sản phẩm, số lượng, giá bán và các quy tắc nghiệp vụ trước khi tạo đơn hàng.
POST cũng có thể được dùng cho những hành động khác như đăng nhập, gửi biểu mẫu hoặc thực hiện thao tác nghiệp vụ. Vì vậy, không nên hiểu rằng POST chỉ có một ý nghĩa duy nhất là tạo dữ liệu.
POST thường không có tính idempotent mặc định. Nếu Client gửi yêu cầu tạo đơn hàng nhiều lần, Server có thể tạo nhiều đơn hàng. Với các thao tác quan trọng như thanh toán hoặc đặt hàng, hệ thống có thể cần cơ chế chống xử lý trùng, chẳng hạn idempotency key hoặc mã yêu cầu duy nhất.
6.3. PUT – Thay thế tài nguyên
PUT thường được dùng để tạo hoặc thay thế trạng thái của tài nguyên tại một URI xác định, tùy quy tắc của API.
Ví dụ:
PUT /api/products/15
Content-Type: application/json
{
"name": "Sữa dinh dưỡng A",
"price": 450000,
"stock": 20
}
Trong ví dụ này, Client yêu cầu cập nhật tài nguyên sản phẩm có ID 15 theo nội dung được gửi lên. Cách xử lý các trường bị bỏ qua phụ thuộc vào hợp đồng API, nhưng PUT thường mang ý nghĩa thay thế biểu diễn của tài nguyên hơn là chỉ cập nhật một vài trường.
PUT có tính idempotent theo ngữ nghĩa HTTP: gửi cùng một yêu cầu PUT nhiều lần sẽ có cùng hiệu quả dự kiến đối với trạng thái tài nguyên như gửi một lần, dù phản hồi của mỗi lần không nhất thiết giống nhau.
6.4. PATCH – Cập nhật một phần tài nguyên
PATCH được dùng khi Client muốn thay đổi một phần tài nguyên thay vì gửi toàn bộ biểu diễn của nó.
Ví dụ, chỉ cập nhật giá sản phẩm:
PATCH /api/products/15
Content-Type: application/json
{
"price": 450000
}
Nếu API hỗ trợ cách cập nhật này, Server chỉ cần xử lý trường price và giữ nguyên những trường khác.
PATCH không mặc định có tính idempotent. Tính chất này phụ thuộc vào cách thiết kế thao tác và dữ liệu gửi lên.
PUT và PATCH không hoàn toàn giống nhau. PUT thường biểu thị việc thay thế trạng thái tài nguyên, trong khi PATCH biểu thị việc áp dụng một thay đổi lên tài nguyên.
6.5. DELETE – Xóa tài nguyên
DELETE được dùng để yêu cầu xóa liên kết giữa URI và tài nguyên hoặc thực hiện thao tác xóa theo thiết kế của hệ thống.
Ví dụ:
DELETE /api/products/15
Backend có thể kiểm tra quyền quản trị, kiểm tra các ràng buộc liên quan và sau đó xóa sản phẩm hoặc đánh dấu sản phẩm là đã xóa.
Trong nhiều hệ thống bán hàng, sản phẩm đã phát sinh đơn hàng có thể không được xóa vật lý vì cần giữ lịch sử giao dịch. Thay vào đó, Backend có thể sử dụng cơ chế xóa mềm (soft delete), chẳng hạn đánh dấu sản phẩm là không còn hoạt động.
DELETE có tính idempotent theo ngữ nghĩa HTTP, nhưng điều đó không có nghĩa mọi lần gửi đều trả về cùng một mã trạng thái. Lần đầu có thể trả về 204 No Content, còn lần sau có thể trả về 404 Not Found nếu tài nguyên không còn tồn tại.
6.6. Bảng tổng hợp HTTP Method
| Phương thức | Mục đích thường gặp | Ví dụ |
|---|---|---|
| GET | Đọc dữ liệu | Lấy danh sách sản phẩm |
| POST | Tạo tài nguyên hoặc yêu cầu xử lý | Tạo đơn hàng |
| PUT | Thay thế trạng thái tài nguyên | Cập nhật toàn bộ thông tin sản phẩm |
| PATCH | Cập nhật một phần tài nguyên | Chỉ đổi giá sản phẩm |
| DELETE | Xóa tài nguyên | Xóa hoặc vô hiệu hóa sản phẩm |
Ngoài các phương thức trên, HTTP còn có những phương thức như HEAD, OPTIONS và các phương thức khác. Trong đó, OPTIONS thường hữu ích khi làm việc với CORS trên trình duyệt.
7. HTTP Status Code là gì?
HTTP Status Code là mã trạng thái do Server gửi trong phản hồi để cho biết kết quả xử lý yêu cầu ở mức giao thức HTTP.
Các mã này được chia thành năm nhóm chính:
- Nhóm 1xx: Thông tin.
- Nhóm 2xx: Thành công.
- Nhóm 3xx: Chuyển hướng.
- Nhóm 4xx: Lỗi liên quan đến yêu cầu từ Client.
- Nhóm 5xx: Lỗi xảy ra ở phía Server.
7.1. Các mã trạng thái thường gặp
200 OK: Yêu cầu được xử lý thành công. Ví dụ, API trả về danh sách sản phẩm.
201 Created: Một tài nguyên mới đã được tạo thành công. Ví dụ, tạo đơn hàng mới và Server trả về thông tin đơn hàng vừa tạo.
204 No Content: Yêu cầu được xử lý thành công nhưng phản hồi không có body. Mã này thường được sử dụng cho thao tác xóa hoặc cập nhật không cần trả dữ liệu.
400 Bad Request: Server không thể hoặc không muốn xử lý yêu cầu do lỗi trong yêu cầu. Ví dụ, dữ liệu gửi lên sai cấu trúc hoặc không đáp ứng điều kiện mà API quy định.
401 Unauthorized: Yêu cầu thiếu thông tin xác thực hợp lệ hoặc thông tin xác thực không được chấp nhận. Ví dụ, token đăng nhập đã hết hạn.
403 Forbidden: Server hiểu yêu cầu nhưng từ chối cho phép thực hiện. Ví dụ, tài khoản khách hàng cố truy cập API chỉ dành cho quản trị viên.
404 Not Found: Không tìm thấy tài nguyên được yêu cầu hoặc Server không cung cấp tài nguyên đó tại địa chỉ tương ứng.
409 Conflict: Yêu cầu xung đột với trạng thái hiện tại của tài nguyên. Ví dụ, mã sản phẩm bị trùng hoặc đơn hàng không thể chuyển sang trạng thái được yêu cầu.
422 Unprocessable Content: Server hiểu loại nội dung gửi lên nhưng không thể xử lý dữ liệu do vi phạm các quy tắc kiểm tra. Một số Backend sử dụng mã này để trả lỗi validation.
429 Too Many Requests: Client gửi quá nhiều yêu cầu trong một khoảng thời gian và bị giới hạn tốc độ.
500 Internal Server Error: Server gặp lỗi không mong muốn khi xử lý yêu cầu.
502 Bad Gateway: Một máy chủ đóng vai trò trung gian nhận phản hồi không hợp lệ từ máy chủ phía sau.
503 Service Unavailable: Dịch vụ tạm thời không sẵn sàng, chẳng hạn do bảo trì hoặc quá tải.
504 Gateway Timeout: Máy chủ trung gian không nhận được phản hồi kịp thời từ máy chủ phía sau.
7.2. Vì sao Frontend cần kiểm tra Status Code?
Giả sử người dùng nhấn nút đăng nhập. Nếu Backend trả về 200 OK, Flutter có thể tiếp tục xử lý dữ liệu người dùng theo hợp đồng API. Nếu Backend trả về 401 Unauthorized, ứng dụng nên thông báo thông tin đăng nhập không hợp lệ hoặc yêu cầu đăng nhập lại tùy trường hợp.
Nếu Server trả về 500 Internal Server Error, ứng dụng không nên thông báo rằng người dùng nhập sai mật khẩu vì lỗi này cho biết đã xảy ra vấn đề ở phía Server.
Việc phân biệt mã trạng thái giúp Frontend đưa ra phản hồi phù hợp, đồng thời hỗ trợ lập trình viên tìm nguyên nhân khi xảy ra lỗi.
Cũng cần lưu ý rằng một phản hồi HTTP thành công không có nghĩa mọi quy tắc nghiệp vụ đều đã đạt yêu cầu. Ví dụ, một API có thể trả 200 OK nhưng body chứa thông tin rằng một tác vụ chưa hoàn tất. Vì vậy, Client cần tuân thủ hợp đồng API và không chỉ dựa vào một mã trạng thái duy nhất.
8. HTTP Headers là gì?
HTTP Headers là các trường thông tin đi kèm yêu cầu hoặc phản hồi. Chúng cung cấp thông tin về nội dung, xác thực, bộ nhớ đệm, kết nối và nhiều đặc tính khác của quá trình giao tiếp.
Một số header quan trọng gồm:
Content-Type: Cho biết kiểu nội dung trong body. Ví dụ, application/json cho biết dữ liệu được biểu diễn dưới dạng JSON.
Accept: Cho biết những định dạng phản hồi mà Client mong muốn nhận. Ví dụ, Accept: application/json.
Authorization: Thường chứa thông tin xác thực để Client chứng minh danh tính hoặc quyền truy cập. Một ví dụ phổ biến là Bearer token.
Authorization: Bearer eyJhbGciOi...
Token ở trên chỉ là ví dụ minh họa. Trong thực tế, token phải được bảo vệ và không được công khai trong mã nguồn, nhật ký hoặc tài liệu gửi cho người khác.
Cache-Control: Điều khiển hoặc hướng dẫn cách lưu và sử dụng dữ liệu trong bộ nhớ đệm HTTP.
Accept-Language: Thể hiện ngôn ngữ mà Client ưu tiên nhận, nếu Server hỗ trợ nội dung theo ngôn ngữ.
Headers có thể được dùng để mô tả dữ liệu và cách giao tiếp, nhưng không thay thế cho việc kiểm tra bảo mật ở Backend. Chẳng hạn, gửi header cho biết người dùng là quản trị viên không chứng minh rằng người đó thực sự có quyền quản trị. Backend phải xác thực thông tin và kiểm tra quyền dựa trên dữ liệu đáng tin cậy.
9. HTTP Body và JSON có vai trò gì?
HTTP Body là phần nội dung của thông điệp, được sử dụng khi cần truyền dữ liệu. Body có thể chứa JSON, văn bản, dữ liệu biểu mẫu, hình ảnh hoặc nội dung nhị phân, tùy loại yêu cầu và phản hồi.
JSON là viết tắt của JavaScript Object Notation. Đây là một định dạng biểu diễn dữ liệu có cấu trúc, thường được dùng để trao đổi dữ liệu giữa Frontend và Backend.
Ví dụ:
{
"id": 15,
"name": "Sữa dinh dưỡng",
"price": 450000,
"in_stock": true
}
Đối tượng JSON trên chứa bốn trường: id, name, price và in_stock. Những trường này có giá trị thuộc các kiểu dữ liệu khác nhau.
JSON không phải là HTTP. HTTP là giao thức truyền thông, còn JSON là định dạng dữ liệu có thể được truyền trong HTTP Body.
Sự phân biệt này rất quan trọng. Một yêu cầu HTTP có thể truyền JSON, nhưng cũng có thể truyền định dạng khác. Ngược lại, JSON có thể được lưu trong tệp hoặc truyền qua những cơ chế khác chứ không chỉ HTTP.
Trong ứng dụng Flutter, dữ liệu JSON thường được giải mã thành các kiểu dữ liệu Dart để ứng dụng có thể sử dụng. Quá trình này liên quan trực tiếp đến khái niệm JSON Serialization và Deserialization.
Ví dụ, Backend trả về giá sản phẩm dưới dạng số nhưng Flutter lại giả định đó là chuỗi. Nếu không xử lý kiểu dữ liệu phù hợp, ứng dụng có thể xảy ra lỗi khi đọc hoặc hiển thị dữ liệu.
Ngoài ra, Client không nên mặc định rằng JSON luôn hợp lệ hoặc luôn có đủ trường. Dữ liệu nhận được cần được kiểm tra và chuyển đổi theo cấu trúc mà ứng dụng mong đợi.
10. REST là gì?
REST là viết tắt của Representational State Transfer. Đây là một phong cách kiến trúc được Roy Fielding mô tả trong luận án tiến sĩ năm 2000, liên quan đến thiết kế các hệ thống phân tán và kiến trúc Web.
REST không phải là một giao thức truyền thông riêng, cũng không phải một ngôn ngữ lập trình hoặc thư viện. Nó đưa ra những ràng buộc kiến trúc để hệ thống có thể được thiết kế theo hướng rõ ràng, linh hoạt và có khả năng mở rộng.
Một API được gọi là REST API khi nó được thiết kế theo các nguyên tắc REST. Trong giao tiếp Web, REST API thường sử dụng HTTP, nhưng chỉ sử dụng HTTP và trả JSON không tự động làm cho một API trở thành RESTful hoàn chỉnh.
Để hiểu REST, cần hiểu cách nó nhìn nhận tài nguyên và cách Client tương tác với tài nguyên đó.
10.1. Resource – Tài nguyên là gì?
Trong REST, tài nguyên là một khái niệm cốt lõi. Tài nguyên có thể là một sản phẩm, một khách hàng, một đơn hàng hoặc một tập hợp các tài nguyên.
Ví dụ, trong hệ thống bán sữa, các tài nguyên có thể gồm:
- Danh sách sản phẩm.
- Một sản phẩm cụ thể.
- Danh sách đơn hàng của khách hàng.
- Một đơn hàng cụ thể.
- Thông tin tài khoản khách hàng.
- Danh sách danh mục sản phẩm.
Mỗi tài nguyên thường được xác định bằng một URI, chẳng hạn /api/products/15 để chỉ đến sản phẩm có ID 15 theo quy ước của API.
Tài nguyên không nhất thiết là một hàng trong cơ sở dữ liệu. Nó là khái niệm ở cấp giao diện API, được Backend biểu diễn và quản lý theo thiết kế của hệ thống.
10.2. URI và Endpoint là gì?
URI là viết tắt của Uniform Resource Identifier, dùng để định danh một tài nguyên. URL là một dạng URI cung cấp thông tin để xác định vị trí và cách truy cập tài nguyên.
Ví dụ:
https://api.example.com/api/products/15
Trong ví dụ này:
httpslà scheme, cho biết giao thức được sử dụng.api.example.comlà tên máy chủ./api/products/15là đường dẫn đến tài nguyên theo quy ước của API.
Endpoint thường được dùng trong phát triển phần mềm để chỉ một điểm truy cập API cụ thể, thường được xác định bởi phương thức HTTP và đường dẫn.
Ví dụ, GET /api/products/15 và DELETE /api/products/15 sử dụng cùng một đường dẫn nhưng có mục đích khác nhau. Do đó, khi mô tả endpoint, cần nêu cả phương thức và đường dẫn thay vì chỉ ghi mỗi URL.
10.3. Vì sao REST API thường sử dụng danh từ cho đường dẫn?
REST hướng đến việc biểu diễn các tài nguyên, vì vậy đường dẫn thường được đặt theo danh từ thay vì mô tả hành động bằng động từ.
Ví dụ:
GET /api/products
GET /api/products/15
POST /api/products
PATCH /api/products/15
DELETE /api/products/15
Cách thiết kế này giúp các phương thức HTTP thể hiện ý định thao tác, còn đường dẫn xác định tài nguyên được thao tác.
Một thiết kế ít nhất quán hơn có thể tạo ra các đường dẫn như:
/api/getProducts
/api/createProduct
/api/updateProduct
/api/deleteProduct
Cách đặt tên này vẫn có thể hoạt động, nhưng thường lặp lại ý nghĩa của phương thức HTTP trong URL và làm API kém đồng nhất hơn.
Tuy nhiên, không phải mọi endpoint đều phải là danh từ. Các hoạt động không dễ biểu diễn thành CRUD trên tài nguyên, chẳng hạn đăng nhập hoặc gửi yêu cầu hủy đơn hàng, có thể được thiết kế bằng những endpoint chuyên biệt. Điều quan trọng là API cần có quy tắc nhất quán và dễ hiểu.
11. Các nguyên tắc cốt lõi của REST
REST được mô tả bằng một tập hợp các ràng buộc kiến trúc. Một API thực tế không nên chỉ được đánh giá dựa trên cách đặt URL mà cần xem xét cả cách hệ thống tổ chức và giao tiếp.
11.1. Client–Server
Client và Server có trách nhiệm riêng biệt.
Client chịu trách nhiệm về trải nghiệm người dùng, hiển thị dữ liệu và gửi yêu cầu. Server chịu trách nhiệm xử lý nghiệp vụ, quản lý dữ liệu và thực thi các quy tắc của hệ thống.
Sự phân tách này giúp Frontend và Backend có thể được phát triển, kiểm thử hoặc thay đổi tương đối độc lập, miễn là giao diện API vẫn được duy trì tương thích.
Ví dụ, một Backend Laravel có thể phục vụ đồng thời ứng dụng Flutter và website React. Hai giao diện có thể khác nhau nhưng vẫn sử dụng chung các chức năng Backend thông qua API.
11.2. Stateless – Không lưu trạng thái phiên Client trên Server giữa các yêu cầu
Stateless là một nguyên tắc quan trọng của REST. Mỗi yêu cầu từ Client cần chứa đủ thông tin để Server hiểu và xử lý yêu cầu đó mà không phải dựa vào ngữ cảnh phiên giao tiếp được lưu từ yêu cầu trước.
Ví dụ, ứng dụng sử dụng Bearer token có thể gửi token trong header Authorization ở những yêu cầu cần xác thực. Server xác minh token và xác định người dùng tương ứng.
Nguyên tắc stateless không có nghĩa Server không được lưu dữ liệu. Server vẫn có thể lưu tài khoản, đơn hàng, giỏ hàng, phiên bản dữ liệu hoặc token. Điều mà nguyên tắc này nhấn mạnh là trạng thái của phiên giao tiếp giữa Client và Server không được dựa vào một ngữ cảnh tạm thời cần thiết phải được giữ lại giữa các yêu cầu.
Cần phân biệt trạng thái phiên giao tiếp với trạng thái nghiệp vụ. Đơn hàng đang xử lý là trạng thái nghiệp vụ và vẫn được lưu trong cơ sở dữ liệu. Điều đó không làm cho API vi phạm nguyên tắc stateless.
11.3. Cacheable – Có khả năng lưu bộ nhớ đệm
Một số phản hồi có thể được lưu tạm để giảm số lần gọi Server và cải thiện hiệu năng. HTTP cung cấp các cơ chế cache thông qua header như Cache-Control, ETag và Last-Modified.
Ví dụ, danh sách danh mục sản phẩm ít thay đổi có thể được lưu trong bộ nhớ đệm trong một khoảng thời gian phù hợp. Khi cần kiểm tra dữ liệu mới, Client có thể sử dụng cơ chế xác thực lại cache nếu API hỗ trợ.
Tuy nhiên, không phải dữ liệu nào cũng nên được cache giống nhau. Dữ liệu cá nhân, thông tin nhạy cảm hoặc dữ liệu đơn hàng cần được xử lý theo chính sách phù hợp để tránh hiển thị dữ liệu cũ hoặc rò rỉ thông tin.
11.4. Uniform Interface – Giao diện thống nhất
REST nhấn mạnh một giao diện tương tác nhất quán giữa Client và Server. Tài nguyên được định danh bằng URI, các thao tác được thể hiện qua giao diện thống nhất và dữ liệu được truyền dưới dạng biểu diễn của tài nguyên.
Nguyên tắc này giúp Client không cần biết toàn bộ cách Backend lưu dữ liệu bên trong. Client chỉ cần tuân thủ hợp đồng API để tương tác với tài nguyên.
Ví dụ, ứng dụng Flutter chỉ cần biết cách gọi GET /api/products/15 và cấu trúc dữ liệu trả về. Nó không cần biết Backend đang lưu sản phẩm trong MySQL, PostgreSQL hay một hệ thống lưu trữ khác.
Trong mô tả REST đầy đủ, giao diện thống nhất còn bao gồm những khía cạnh như thông điệp tự mô tả và liên kết điều hướng thông qua siêu phương tiện (hypermedia).
11.5. Layered System – Hệ thống phân lớp
Client không nhất thiết phải giao tiếp trực tiếp với máy chủ xử lý nghiệp vụ cuối cùng. Yêu cầu có thể đi qua proxy, API Gateway, bộ cân bằng tải hoặc các lớp trung gian khác.
Ví dụ:
Flutter → API Gateway → Backend → Database.
Các lớp trung gian có thể hỗ trợ cân bằng tải, kiểm soát truy cập, giới hạn tốc độ hoặc ghi nhận hoạt động.
Client không cần biết toàn bộ cấu trúc nội bộ của hệ thống. Nó chỉ cần sử dụng API theo giao diện được cung cấp.
11.6. Code on Demand – Mã theo yêu cầu (tùy chọn)
REST có một ràng buộc tùy chọn cho phép Server cung cấp mã thực thi để Client sử dụng. Ví dụ điển hình trong môi trường Web là việc tải JavaScript về trình duyệt.
Đây không phải yêu cầu bắt buộc đối với mọi REST API. Nhiều API chỉ cung cấp dữ liệu và không sử dụng nguyên tắc này.
Tóm lại, REST không chỉ là cách đặt URL hoặc dùng JSON. Đó là một phong cách kiến trúc với nhiều ràng buộc, trong đó Client–Server, Stateless, Cacheable, Uniform Interface và Layered System là những nguyên tắc quan trọng cần hiểu.
12. REST API hoạt động như thế nào trong thực tế?
Xét một ứng dụng bán sữa được xây dựng bằng Flutter, Backend Laravel và cơ sở dữ liệu MySQL.
Khi khách hàng mở màn hình danh sách sản phẩm, Flutter cần lấy dữ liệu từ Backend. Quá trình này có thể diễn ra như sau:
Bước 1: Flutter tạo yêu cầu
Flutter gọi endpoint GET /api/products, có thể kèm tham số phân trang hoặc lọc dữ liệu.
Ví dụ:
GET /api/products?page=1&per_page=10
Accept: application/json
Bước 2: Backend tiếp nhận yêu cầu
Laravel định tuyến yêu cầu đến phần xử lý phù hợp. Tùy thiết kế, Backend có thể kiểm tra tham số, xác định điều kiện truy vấn và gọi lớp xử lý nghiệp vụ.
Bước 3: Backend truy vấn dữ liệu
Backend truy vấn MySQL để lấy các sản phẩm phù hợp. Nếu có yêu cầu lọc theo danh mục hoặc trạng thái kinh doanh, điều kiện đó được áp dụng trong quá trình truy vấn.
Bước 4: Backend tạo phản hồi
Backend chuyển kết quả thành dữ liệu theo cấu trúc API đã quy định, thường là JSON, rồi trả về cùng mã trạng thái phù hợp.
Bước 5: Flutter đọc phản hồi
Ứng dụng nhận được dữ liệu, giải mã JSON, chuyển dữ liệu thành các đối tượng Dart hoặc cấu trúc dữ liệu phù hợp, sau đó cập nhật giao diện.
Bước 6: Giao diện hiển thị kết quả
Flutter hiển thị danh sách sản phẩm. Nếu yêu cầu thất bại, ứng dụng có thể hiển thị thông báo lỗi, nút thử lại hoặc trạng thái không có dữ liệu.
Luồng tổng quát:
Flutter → HTTP Request → REST API của Laravel → Xử lý nghiệp vụ → MySQL → HTTP Response → Flutter cập nhật giao diện.
Cần nhớ rằng HTTP không trực tiếp truy vấn cơ sở dữ liệu. HTTP chỉ là một phần của quá trình giao tiếp giữa Client và Server. Backend mới là thành phần thực hiện truy vấn và xử lý dữ liệu.
13. Phân biệt REST API, HTTP, JSON và Backend
Các khái niệm này thường xuất hiện cùng nhau nên người mới học dễ nhầm lẫn.
HTTP là giao thức quy định cách các bên trao đổi thông điệp.
REST là phong cách kiến trúc quy định những nguyên tắc tổ chức giao diện giữa các thành phần trong hệ thống.
REST API là giao diện API được thiết kế theo các nguyên tắc REST. Trong ứng dụng web, nó thường sử dụng HTTP để giao tiếp.
JSON là một định dạng dữ liệu thường được sử dụng trong body của yêu cầu hoặc phản hồi.
Backend là phần hệ thống thực hiện xử lý nghiệp vụ, xác thực, kiểm tra quyền và quản lý dữ liệu. Backend có thể cung cấp REST API nhưng không đồng nghĩa với REST API.
Database là nơi lưu trữ và tổ chức dữ liệu theo công nghệ mà hệ thống sử dụng.
Có thể hình dung mối quan hệ như sau:
Flutter (Client) → HTTP (giao thức giao tiếp) → REST API (giao diện được thiết kế theo REST) → Backend xử lý nghiệp vụ → Database.
Dữ liệu trao đổi có thể được biểu diễn bằng JSON, nhưng JSON không phải thành phần bắt buộc của mọi API REST.
Một điểm khác cũng cần lưu ý: không phải mọi API dùng HTTP đều là REST API. Chẳng hạn, một API có thể sử dụng HTTP nhưng thiết kế theo RPC, hoặc sử dụng GraphQL trên HTTP. Vì vậy, HTTP và REST có mối liên hệ chặt chẽ trong thực tế nhưng không phải hai khái niệm đồng nghĩa.
14. Ví dụ thiết kế REST API cho hệ thống bán sữa
Để hiểu rõ hơn cách vận dụng lý thuyết, hãy xây dựng một bộ endpoint minh họa cho hệ thống bán sữa.
14.1. Nhóm API sản phẩm
| Phương thức | Endpoint | Chức năng |
|---|---|---|
| GET | /api/products |
Lấy danh sách sản phẩm |
| GET | /api/products/15 |
Lấy chi tiết sản phẩm ID 15 |
| POST | /api/products |
Tạo sản phẩm mới |
| PATCH | /api/products/15 |
Cập nhật một phần thông tin sản phẩm |
| DELETE | /api/products/15 |
Xóa hoặc vô hiệu hóa sản phẩm theo quy tắc hệ thống |
Nhóm API này cho thấy cách kết hợp HTTP Method và tài nguyên để xây dựng giao diện nhất quán.
Trong hệ thống thực tế, các API tạo, cập nhật và xóa sản phẩm thường yêu cầu quyền quản trị viên. Backend phải kiểm tra quyền trước khi thực hiện thao tác, chứ không chỉ dựa vào việc Frontend có ẩn nút thao tác hay không.
14.2. Nhóm API đơn hàng
| Phương thức | Endpoint | Chức năng |
|---|---|---|
| POST | /api/orders |
Tạo đơn hàng |
| GET | /api/orders |
Lấy danh sách đơn hàng mà người dùng được phép xem |
| GET | /api/orders/1001 |
Xem chi tiết đơn hàng ID 1001 |
| PATCH | /api/orders/1001 |
Cập nhật những trường được phép theo quy tắc API |
| POST | /api/orders/1001/cancel-requests |
Gửi yêu cầu hủy đơn hàng |
Ví dụ, khi khách hàng muốn hủy đơn hàng, không nhất thiết nên cho phép Client cập nhật trực tiếp trạng thái đơn hàng thành “Đã hủy”. Hệ thống có thể yêu cầu khách hàng gửi yêu cầu hủy, sau đó quản trị viên xem xét và chấp thuận hoặc từ chối.
Điều này minh họa rằng REST API không chỉ là cách gửi dữ liệu. Thiết kế API còn phải phản ánh chính xác quy tắc nghiệp vụ và quyền hạn của từng loại người dùng.
14.3. Nhóm API tài khoản
| Phương thức | Endpoint | Chức năng |
|---|---|---|
| POST | /api/register |
Đăng ký tài khoản |
| POST | /api/login |
Đăng nhập |
| GET | /api/me |
Lấy thông tin tài khoản hiện tại |
| POST | /api/logout |
Đăng xuất theo cơ chế xác thực mà hệ thống sử dụng |
Các đường dẫn trên là ví dụ thiết kế, không phải quy ước bắt buộc của REST. API thực tế có thể sử dụng đường dẫn khác, miễn là nhất quán và có hợp đồng giao tiếp rõ ràng.
15. Query Parameters, Path Parameters và Request Body
Khi làm việc với REST API, ngoài phương thức và đường dẫn, cần hiểu cách truyền dữ liệu qua URL hoặc body.
15.1. Path Parameters
Path parameter là giá trị nằm trong đường dẫn, thường được sử dụng để xác định một tài nguyên cụ thể.
Ví dụ:
GET /api/products/15
Trong đó, 15 là mã định danh sản phẩm theo quy ước của API.
Backend sử dụng giá trị này để xác định sản phẩm cần lấy. Nếu sản phẩm không tồn tại hoặc người dùng không được phép truy cập, Server cần trả kết quả phù hợp.
15.2. Query Parameters
Query parameters là các tham số xuất hiện sau dấu ? trong URL. Chúng thường được sử dụng để lọc, tìm kiếm, sắp xếp hoặc phân trang.
Ví dụ:
GET /api/products?category_id=3&page=1&per_page=10
Trong ví dụ này:
category_id=3: Lọc sản phẩm theo danh mục ID 3.page=1: Lấy trang đầu tiên.per_page=10: Yêu cầu tối đa 10 sản phẩm mỗi trang theo quy ước của API.
Query parameters phù hợp khi Client muốn lựa chọn tập dữ liệu cần nhận mà không cần tạo ra một đường dẫn riêng cho từng tổ hợp điều kiện.
Tuy nhiên, Backend cần kiểm tra và giới hạn các tham số này. Ví dụ, không nên cho phép người dùng yêu cầu hàng triệu bản ghi trong một trang chỉ bằng cách thay đổi per_page.
15.3. Request Body
Body thường được sử dụng khi Client gửi dữ liệu để tạo hoặc cập nhật tài nguyên, hoặc yêu cầu Server xử lý một tác vụ.
Ví dụ:
{
"product_id": 15,
"quantity": 2,
"payment_method": "cod"
}
Đây có thể là một phần dữ liệu gửi khi tạo đơn hàng. Backend không nên tin tưởng trực tiếp vào mọi giá trị từ Client. Ví dụ, giá sản phẩm cần được tính hoặc xác minh từ dữ liệu đáng tin cậy phía Server thay vì lấy nguyên giá mà Client gửi lên.
16. Authentication và Authorization trong REST API
Hai khái niệm này rất quan trọng khi xây dựng API có tài khoản người dùng.
Authentication (xác thực) là quá trình kiểm tra người dùng hoặc hệ thống đang truy cập là ai. Ví dụ, Backend xác minh thông tin đăng nhập hoặc kiểm tra token hợp lệ.
Authorization (phân quyền) là quá trình xác định người đã được xác thực có được phép thực hiện hành động cụ thể hay không.
Ví dụ, khách hàng đã đăng nhập có thể xem đơn hàng của chính mình nhưng không được xem toàn bộ đơn hàng của khách hàng khác. Quản trị viên có thể có quyền quản lý sản phẩm và cập nhật trạng thái đơn hàng.
Trong nhiều API, Client gửi token trong header Authorization:
GET /api/me
Authorization: Bearer example-token
Accept: application/json
Backend kiểm tra token theo cơ chế xác thực được triển khai. Nếu token hợp lệ, hệ thống xác định người dùng tương ứng và tiếp tục kiểm tra quyền truy cập.
Một sai lầm nguy hiểm là chỉ kiểm tra Frontend. Việc ẩn nút “Xóa sản phẩm” khỏi khách hàng không ngăn được người dùng tự tạo yêu cầu HTTP đến endpoint xóa sản phẩm. Backend phải kiểm tra quyền cho từng thao tác nhạy cảm.
Một sai lầm khác là cho phép người dùng truy cập dữ liệu bằng cách thay đổi ID trong URL mà không kiểm tra quyền sở hữu. Ví dụ, khách hàng có ID tài khoản 12 không được tự động xem đơn hàng của tài khoản 13 chỉ vì họ đổi đường dẫn thành /api/orders/1002.
Ngoài ra, HTTPS cần được sử dụng để bảo vệ token trong quá trình truyền tải, còn việc lưu token ở phía Client phải phù hợp với nền tảng và mô hình bảo mật của ứng dụng.
17. REST API có phải lúc nào cũng trả về JSON không?
Không. JSON là định dạng phổ biến vì dễ đọc, dễ xử lý và được hỗ trợ rộng rãi bởi nhiều ngôn ngữ lập trình. Tuy nhiên, REST không bắt buộc mọi tài nguyên phải được biểu diễn bằng JSON.
Một API có thể trả về:
- JSON cho dữ liệu sản phẩm hoặc đơn hàng.
- Hình ảnh khi yêu cầu một tài nguyên hình ảnh.
- Tệp PDF khi tải hóa đơn.
- Nội dung văn bản hoặc định dạng khác theo nhu cầu hệ thống.
HTTP sử dụng các header như Content-Type để mô tả kiểu nội dung của phản hồi. Client cần xử lý nội dung theo đúng định dạng đó.
Ví dụ, nếu API trả về tệp PDF nhưng Flutter cố giải mã body như JSON, ứng dụng sẽ gặp lỗi vì dữ liệu thực tế không phải JSON.
Tương tự, không nên giả định mọi phản hồi đều có body. Một số phản hồi thành công như 204 No Content không chứa nội dung, vì vậy Client cần xử lý theo mã trạng thái và hợp đồng API thay vì luôn cố đọc JSON.
18. Những lỗi thường gặp khi làm việc với HTTP và REST API
18.1. Sai URL hoặc endpoint
Nếu Client gọi nhầm đường dẫn hoặc sử dụng sai phương thức HTTP, Server có thể trả về 404 Not Found, 405 Method Not Allowed hoặc một kết quả khác tùy cách cấu hình.
Cách khắc phục là kiểm tra địa chỉ máy chủ, đường dẫn, phương thức và tài liệu API.
18.2. Sai định dạng dữ liệu
Backend có thể yêu cầu trường quantity là số nguyên nhưng Client gửi chuỗi không hợp lệ. Trong trường hợp đó, yêu cầu có thể bị từ chối khi kiểm tra dữ liệu.
Frontend cần gửi đúng cấu trúc và kiểu dữ liệu theo hợp đồng API. Backend vẫn phải tự kiểm tra dữ liệu vì Client không phải nguồn dữ liệu đáng tin cậy.
18.3. Thiếu thông tin xác thực
Nếu endpoint yêu cầu đăng nhập nhưng Client không gửi token hợp lệ, Server có thể trả 401 Unauthorized.
Lập trình viên cần kiểm tra token, thời hạn, cách truyền header và cơ chế xác thực mà Backend sử dụng.
18.4. Không có quyền truy cập
Một yêu cầu có thể xác thực thành công nhưng vẫn không được phép thực hiện. Ví dụ, khách hàng cố gắng cập nhật thông tin kho hàng chỉ dành cho quản trị viên.
Trường hợp này thường liên quan đến 403 Forbidden, nhưng hệ thống có thể sử dụng cách phản hồi khác tùy yêu cầu bảo mật và thiết kế API.
18.5. Mất kết nối hoặc hết thời gian chờ
HTTP Request có thể thất bại do mạng không ổn định, máy chủ không phản hồi hoặc kết nối bị gián đoạn.
Frontend cần phân biệt lỗi mạng với phản hồi lỗi HTTP. Trong một số trường hợp, người dùng có thể thử lại. Tuy nhiên, với thao tác tạo đơn hàng hoặc thanh toán, không nên tự động gửi lại một cách thiếu kiểm soát vì có nguy cơ tạo giao dịch trùng lặp.
18.6. Server trả về lỗi nhưng Frontend vẫn xử lý như thành công
Nếu Frontend chỉ cố đọc body mà không kiểm tra trạng thái hoặc quy tắc xử lý lỗi của thư viện, ứng dụng có thể hiển thị thông báo không chính xác.
Cần kiểm tra mã trạng thái, cấu trúc lỗi và ngoại lệ mà thư viện HTTP có thể ném ra. Với một số thư viện, phản hồi có mã lỗi HTTP vẫn được trả về dưới dạng đối tượng response; với những cấu hình khác, thư viện có thể ném ngoại lệ.
18.7. Không xử lý phân trang và giới hạn dữ liệu
Khi danh sách sản phẩm có hàng nghìn hoặc hàng chục nghìn bản ghi, việc tải toàn bộ dữ liệu trong một yêu cầu sẽ tốn thời gian và tài nguyên.
API nên hỗ trợ phân trang, lọc hoặc tìm kiếm phù hợp. Frontend chỉ tải phần dữ liệu cần thiết, còn Backend cần giới hạn kích thước trang để bảo vệ hệ thống.
18.8. Tin tưởng dữ liệu gửi từ Client
Giả sử Client gửi giá một hộp sữa là 1.000 đồng dù giá thực tế là 450.000 đồng. Nếu Backend sử dụng trực tiếp giá Client gửi, người dùng có thể lợi dụng để đặt hàng với giá sai.
Vì vậy, Backend phải tự xác minh giá, tồn kho, quyền truy cập và những quy tắc nghiệp vụ quan trọng. Client chủ yếu chịu trách nhiệm cung cấp dữ liệu đầu vào và hiển thị kết quả.
19. REST API trong Flutter: Vai trò của thư viện HTTP
Trong Flutter, lập trình viên thường không tự xây dựng toàn bộ quá trình giao tiếp HTTP từ đầu mà sử dụng thư viện hỗ trợ như package:http hoặc Dio.
Các thư viện này giúp gửi yêu cầu, truyền header và body, nhận phản hồi, cấu hình timeout và xử lý các tình huống mạng.
Ví dụ minh họa dưới đây sử dụng thư viện http để lấy danh sách sản phẩm. Đoạn code chỉ thể hiện cách gọi API cơ bản; tên miền, cấu trúc JSON và mô hình dữ liệu cần được điều chỉnh theo Backend thực tế.
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<void> fetchProducts() async {
final url = Uri.parse(
'https://api.example.com/api/products',
);
try {
final response = await http.get(
url,
headers: {
'Accept': 'application/json',
},
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
print('Lấy dữ liệu thành công: $data');
} else {
print(
'API trả về mã lỗi: ${response.statusCode}',
);
}
} catch (e) {
print('Không thể kết nối hoặc xử lý phản hồi: $e');
}
}
Phân tích đoạn code:
Uri.parse(...): Chuyển chuỗi URL thành đối tượng URI để thư viện sử dụng.
http.get(...): Gửi HTTP Request bằng phương thức GET đến endpoint lấy sản phẩm.
headers: Gửi header Accept để thể hiện định dạng phản hồi mà Client mong muốn.
await: Chờ thao tác bất đồng bộ trả về kết quả mà không phải chặn toàn bộ luồng giao diện trong lúc chờ mạng.
response.statusCode: Đọc mã trạng thái HTTP từ phản hồi.
jsonDecode(response.body): Giải mã chuỗi JSON nhận được thành kiểu dữ liệu Dart phù hợp, thường là Map hoặc List tùy cấu trúc dữ liệu.
try...catch: Bắt các ngoại lệ có thể phát sinh, chẳng hạn lỗi mạng hoặc lỗi khi xử lý phản hồi. Trong ứng dụng thực tế, cần xử lý riêng những loại lỗi có ý nghĩa khác nhau thay vì chỉ in thông báo chung.
Đoạn code trên chưa phải cách tổ chức hoàn chỉnh cho một ứng dụng lớn. Khi phát triển thực tế, nên tách phần giao tiếp API khỏi giao diện, sử dụng model để biểu diễn dữ liệu, quản lý trạng thái tải và lỗi, đồng thời cấu hình timeout và các cơ chế xác thực cần thiết.
Đặc biệt, không nên xem việc nhận được phản hồi HTTP là đồng nghĩa với việc dữ liệu đã được giải mã thành công hoặc ứng dụng đã hiển thị đúng. Những bước đó cần được xử lý riêng.
20. REST API và HTTP liên quan như thế nào đến Dio?
Dio là một thư viện HTTP dành cho Dart, được sử dụng phổ biến trong các dự án Flutter. Nó hỗ trợ gửi yêu cầu HTTP, cấu hình header, query parameters, timeout, interceptor, tải tệp và nhiều chức năng khác.
REST API là giao diện mà Client gọi đến; HTTP là giao thức truyền thông; Dio là công cụ mà lập trình viên có thể lựa chọn để thực hiện việc giao tiếp đó.
Có thể hình dung:
Flutter → Dio → HTTP Request → REST API của Backend → HTTP Response → Dio → Flutter.
Ví dụ, khi Flutter cần lấy danh sách sản phẩm, Dio gửi yêu cầu đến endpoint tương ứng. Backend xử lý yêu cầu và trả phản hồi. Dio cung cấp dữ liệu phản hồi cho mã Dart để ứng dụng kiểm tra, giải mã và cập nhật giao diện.
Nếu đổi Dio sang thư viện http, API phía Backend không nhất thiết phải thay đổi. Hai thư viện là những công cụ khác nhau nhưng đều có thể sử dụng HTTP để gọi cùng một endpoint.
Điều này cho thấy cần phân biệt kiến thức nền tảng với công cụ triển khai. Hiểu HTTP và REST API giúp lập trình viên làm việc với nhiều thư viện khác nhau, thay vì chỉ biết sử dụng một thư viện theo mẫu có sẵn.
21. Một số vấn đề cần quan tâm khi thiết kế REST API thực tế
Ngoài những nguyên tắc cơ bản, API được dùng trong ứng dụng thật còn cần quan tâm đến tính nhất quán, hiệu năng, bảo mật và khả năng bảo trì.
21.1. Validation – Kiểm tra dữ liệu
Backend cần kiểm tra dữ liệu trước khi xử lý. Ví dụ, số lượng sản phẩm đặt mua phải là số nguyên dương, email phải đáp ứng quy tắc hợp lệ và sản phẩm phải tồn tại.
Việc kiểm tra ở Frontend giúp người dùng phát hiện lỗi sớm, nhưng không thể thay thế validation ở Backend.
21.2. Error Response – Cấu trúc phản hồi lỗi
API nên có cách trả lỗi nhất quán để Frontend biết phải xử lý như thế nào.
Ví dụ:
{
"message": "Dữ liệu không hợp lệ",
"errors": {
"quantity": [
"Số lượng phải lớn hơn 0"
]
}
}
Đây là cấu trúc minh họa. API có thể dùng cấu trúc khác, nhưng cần quy định rõ trường nào chứa thông báo, trường nào chứa lỗi theo từng thuộc tính và cách Frontend đọc các trường đó.
21.3. Pagination – Phân trang
Phân trang giúp tránh tải quá nhiều dữ liệu trong một lần. API có thể trả về danh sách bản ghi cùng thông tin như trang hiện tại, tổng số bản ghi hoặc đường dẫn đến trang tiếp theo.
Cấu trúc cụ thể phụ thuộc vào thiết kế Backend. Điều quan trọng là Frontend và Backend phải thống nhất cách yêu cầu trang và cách nhận kết quả.
21.4. API Versioning – Quản lý phiên bản
Khi hệ thống phát triển, cấu trúc API có thể thay đổi. Nếu Backend đổi tên trường hoặc thay đổi cách trả dữ liệu mà không duy trì tính tương thích, ứng dụng đang sử dụng API cũ có thể bị lỗi.
Một cách phổ biến là đặt phiên bản trong đường dẫn, chẳng hạn /api/v1/products. Tuy nhiên, versioning có nhiều cách triển khai khác nhau. Mục tiêu là quản lý thay đổi sao cho Client có đủ thời gian thích nghi và những thay đổi phá vỡ tương thích được kiểm soát.
21.5. Rate Limiting – Giới hạn tốc độ yêu cầu
Nếu một Client gửi quá nhiều yêu cầu trong thời gian ngắn, hệ thống có thể bị quá tải hoặc bị lạm dụng. Backend có thể giới hạn tốc độ truy cập và trả về 429 Too Many Requests khi vượt giới hạn.
21.6. Logging và Monitoring – Ghi nhận và giám sát
Khi API gặp lỗi, log và hệ thống giám sát giúp xác định thời điểm xảy ra lỗi, endpoint liên quan và nguyên nhân kỹ thuật.
Tuy nhiên, log không nên ghi trực tiếp mật khẩu, token hoặc những thông tin nhạy cảm không cần thiết. Việc theo dõi phải cân bằng giữa khả năng tìm lỗi và bảo vệ dữ liệu.
21.7. CORS – Chia sẻ tài nguyên khác nguồn
CORS (Cross-Origin Resource Sharing) là cơ chế do trình duyệt thực thi, cho phép máy chủ quy định những nguồn nào được phép đọc phản hồi từ một số yêu cầu khác nguồn.
CORS thường xuất hiện khi phát triển Frontend chạy trong trình duyệt và Backend ở một origin khác. Nó không phải là cơ chế xác thực người dùng, cũng không thay thế cho kiểm tra quyền ở Backend.
Ứng dụng Flutter chạy trên thiết bị di động không chịu cùng cơ chế CORS của trình duyệt như một ứng dụng web chạy trong trình duyệt. Vì vậy, khi API hoạt động trên điện thoại nhưng bị trình duyệt chặn, cần xem xét cấu hình CORS thay vì mặc định cho rằng API bị hỏng.
22. REST API có những ưu điểm và hạn chế gì?
Ưu điểm
Dễ tích hợp: REST API thường sử dụng HTTP và các định dạng phổ biến như JSON, nên nhiều ngôn ngữ và nền tảng có thể sử dụng.
Phân tách Frontend và Backend: Giao diện và xử lý nghiệp vụ có thể được phát triển tương đối độc lập khi hợp đồng API được duy trì.
Dễ tổ chức tài nguyên: Các endpoint được thiết kế nhất quán giúp việc đọc, tạo, cập nhật và xóa tài nguyên trở nên dễ hiểu.
Tận dụng hệ sinh thái Web: REST API có thể sử dụng các cơ chế HTTP như cache, mã trạng thái, header và HTTPS.
Phù hợp với nhiều ứng dụng: REST API thường phù hợp với website, ứng dụng Flutter, hệ thống quản trị và các hệ thống tích hợp dữ liệu.
Hạn chế
Có thể lấy dư hoặc thiếu dữ liệu: Một endpoint trả về quá nhiều trường có thể khiến Client nhận dữ liệu không cần thiết. Ngược lại, nếu cần thông tin từ nhiều tài nguyên khác nhau, Client có thể phải gửi nhiều yêu cầu.
Cần thiết kế hợp đồng API rõ ràng: Nếu cấu trúc phản hồi hoặc cách xử lý lỗi thiếu nhất quán, Frontend sẽ khó tích hợp và bảo trì.
Không tự giải quyết mọi vấn đề bảo mật: REST API vẫn cần xác thực, phân quyền, validation, giới hạn tốc độ và các biện pháp bảo vệ khác.
Không phải lựa chọn tối ưu cho mọi bài toán: Những hệ thống có nhu cầu truy vấn dữ liệu rất linh hoạt, giao tiếp thời gian thực hoặc truyền dữ liệu theo luồng có thể cần kết hợp thêm những công nghệ và mô hình giao tiếp khác.
Do đó, REST API là một lựa chọn phổ biến và hữu ích, nhưng thiết kế API cần dựa trên yêu cầu thực tế thay vì áp dụng máy móc một khuôn mẫu.
23. Kết luận
REST API và HTTP là hai khái niệm nền tảng trong quá trình xây dựng ứng dụng hiện đại. HTTP cung cấp quy tắc để Client và Server trao đổi thông điệp, còn REST cung cấp những nguyên tắc kiến trúc giúp tổ chức API theo hướng nhất quán, dựa trên tài nguyên và có khả năng mở rộng.
Khi một ứng dụng Flutter cần lấy danh sách sản phẩm, ứng dụng gửi HTTP Request đến endpoint của Backend. Backend tiếp nhận yêu cầu, xác thực và kiểm tra dữ liệu nếu cần, thực hiện xử lý nghiệp vụ, truy vấn cơ sở dữ liệu và gửi HTTP Response trở lại. Flutter sau đó đọc phản hồi, giải mã dữ liệu và hiển thị kết quả lên giao diện.
Để làm việc với REST API hiệu quả, lập trình viên cần hiểu phương thức HTTP, URL và endpoint, header, body, JSON, mã trạng thái, xác thực, phân quyền và cách xử lý lỗi. Đồng thời, cần hiểu rằng API không phải là cơ sở dữ liệu và Client không được phép quyết định những quy tắc nghiệp vụ quan trọng thay cho Backend.
Trong quá trình phát triển ứng dụng Flutter, kiến thức REST API & HTTP là nền tảng để tiếp tục tìm hiểu những nội dung như Dio & HTTP Client, JSON Serialization/Deserialization, API Error Handling và quản lý trạng thái. Khi nắm được bản chất của việc giao tiếp giữa Client và Server, lập trình viên sẽ dễ dàng hiểu cách ứng dụng lấy dữ liệu, gửi dữ liệu, xử lý phản hồi và phối hợp với Backend trong một dự án thực tế hơn.
Tài liệu tham khảo đề xuất để tìm hiểu sâu hơn
- MDN Web Docs – HTTP: https://developer.mozilla.org/en-US/docs/Web/HTTP
- MDN Web Docs – HTTP Request Methods: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Methods
- MDN Web Docs – HTTP Response Status Codes: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status
- Roy Fielding – Architectural Styles and the Design of Network-based Software Architectures, luận án tiến sĩ mô tả REST: https://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm
- Flutter Documentation – Networking: https://docs.flutter.dev/cookbook/networking
- Dart Packages – HTTP: https://pub.dev/packages/http
- Dio – Thư viện HTTP cho Dart: https://pub.dev/packages/dio








Khoá học lập trình game con rắn cho trẻ em




