Chúng ta sẽ cùng tìm hiểu các cách để viết các REST API với nodejs làm sao cho hiệu quả, bao gồm các chủ đề như đặt tên các route, authentication, black-box testing và sử dụng cache header đúng cách các tài nguyên. Hy vọng 10 cách được liệt kê dưới đây có thể giúp ích cho các bạn:
Giả sử bạn đang xây dựng một RESTful API Node.js để tạo, cập nhật, lấy thông tin hay xóa người dùng. Với các tính năng đó HTTP đã cung cấp một bộ đầy đủ các phương thức: POST, PUT, GET, PATCH hoặc DELETE.
Cách tối ưu nhất là các API route của bạn chỉ nên sử dụng danh từ cho việc xác định tài nguyên. Các route khi đó sẽ trông như thế này:
POST /user
dùng để tạo userGET /user
lấy danh sách usersGET /user/:id
lấy một user cụ thể bằng idPUT /user:/id
thay đổi/cập nhật thông tin user cụ thể thông qua idDELETE /user/:id
để xóa một user.Nếu có gì xảy ra khi server đang xử lý một request, bạn cần tạo http status code trong response trả về:
2xx
, nếu không có lỗi (thường là 200).3xx
, nếu resourse đã chuyển4xx
, nếu request không được thực hiện do lỗi client.5xx
, nếu có lỗi ở phía API server (một exception nào đó xảy ra,...).Nếu bạn sử dụng Express, thiết lập mã status khá dễ dàng: res.status(500).send({error: 'Internal server error happened'})
. Thường thì ta nên tạo một helper để khai báo các định dạng và status code trả về để tiện dùng sau này trong app.
Để đính kèm các metadata vào payload bạn sắp gửi, sử dụng HTTP header. Các header sẽ bao gồm các thông tin:
Nếu bạn cần thiết lập bất cứ metadata custom nào trong header, hãy thêm tiền tố X vào phía trước. Ví dụ, nếu bạn đang sử dụng CSRF token, cách thông thường là X-Csrf-Token. Tuy nhiên, theo RFC 6648 thì sẽ gây khó hiểu. Với các API mới không nên sử dụng các tên header dễ gây conflict với các ứng dụng khác. Ví dụ, OpenStack sẽ tự động thêm tiền tố vào header với OpenStack:
OpenStack-Identity-Account-ID
OpenStack-Networking-Host-Name
OpenStack-Object-Storage-Policy
Chú ý rằng các chuẩn HTTP không định nghĩa bất cứ giới hạn size nào trong header. Tuy nhiên Node.js đã buộc object header nhận một giới hạn kích thước là 80kB cho lý do thực tế từ Nodejs HTTP parser.
Don’t allow the total size of the HTTP headers (including the status line) to exceed HTTP_MAX_HEADER_SIZE. This check is here to protect embedders against denial-of-service attacks where the attacker feeds us a never-ending header that the embedder keeps buffering
Việc chọn đúng framework phù hợp với yêu cầu công việc của bạn là quan trọng. Một số framework phổ biến như: Express, Koa hay Hapi Express, Koa hay Hapi có thể được sử dụng để tạo ra các web application, chúng hỗ trợ templating và rendering. Mình thì bắt đầu với Express Restify Restify tập trung hoàn toàn vào việc giúp bạn xây dựng các REST services. Restify tồn giúp bạn xây dựng các "strict" API services đáng kể, dễ bảo trì. Restify cũng đi kèm với công cụ hỗ trợ tự động DTrace. Cụ thể thì Restify đang được sử dụng trong rất nhiều các ứng dụng như npm hay Netflix.
Một trong những cách hay để test các REST API là xem chúng như các black-box. Black-box test là phương pháp kiểm thử chức năng của ứng dụng mà không cần quan tâm đến cấu trúc bên trong của nó hay cách nó hoạt động. Do đó, sẽ không cần mock dependency nào, hệ thống sẽ được test một thể. Chúng ta sẽ sử dụng supertest để test các REST API theo phương pháp black-box này. Sau đây là một test case đơn giản. Nó kiểm tra xem thông tin người dùng đã được trả về hay chưa, sử dụng test runner mocha:
const request = require('supertest')
describe('GET /user/:id', function() {
it('returns a user', function() {
// newer mocha versions accepts promises as well
return request(app)
.get('/user')
.set('Accept', 'application/json')
.expect(200, {
id: '1',
name: 'John Math'
}, done)
})
})
Có lẽ bạn sẽ thắc mắc: làm thế nào để tạo dữ liệu vào trong database phục vụ cho các REST API? Thông thường, test thường được viết làm sao để chúng tạo ra càng ít các giả định cho trạng thái hệ thống càng tốt. Tuy vậy, trong một vài bối cảnh bạn cần biết chính xác trạng thái của hệ thống, bạn có thể tạo các assertion và đạt được test coverage cao hơn. Tùy thuộc vào nhu cầu của bạn, bạn có thể populate cơ sở dữ liệu với các dữ liệu test theo một trong các cách sau:
Dĩ nhiên, black-box test không có nghĩa là bạn không cần viết unit test. Trong hầu hết các trường hợp, bạn vẫn cần viết unit test cho các API. Cụ thể các bạn có thể tham khảo bài viết trước của mình: Testing API end point in Nodejs
Các API phải là phi trạng thái, và authentication cũng tương tự. JWT (Json Web Token) chính là ý tưởng đó. JWT gồm 3 phần:
Thêm xác thực loại JWT vào ứng dụng khá đơn giản và nhanh chóng:
import * as jwt from 'jsonwebtoken';
const expiresIn = parseInt('86400');
const token = jwt.sign(user.toObject(), process.env.APP_KEY, {
expiresIn // in seconds
});
Sau đó, API endpoint đã được bảo vệ với JWT. Để truy cập vào các endpoint được bảo vệ, bạn cần phải cung cấp token trong trường header Authorization
:
curl --header "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ" my-website.com
Hãy chú ý một điều rằng module JWT trên không phụ thuộc bất kì database nào, do tất cả token JWT có cơ chế tự xác thực, và chúng còn bao gồm cả thời gian tồn tại. Ngoài ra, bạn phải chắc chắn rằng tất cả các API endpoint trong ứng dụng sẽ chỉ được truy cập thông qua kết nối bảo mật sử dụng HTTPS.
Các request có điều kiện là các request HTTP được thực thi theo các cách khác nhau, phụ thuộc vào các header HTTP cụ thể. bạn có thể xem các header này như là điều kiện tiên quyết: nếu chúng gặp nhau, các request sẽ được thực thi theo các cách khác nhau. Các header này sẽ cố gắng kiểm tra liệu version của resource được lưu trữ trên server có trùng với version của cùng resource hay không. Do vậy, các header có thể là:
Các header này là:
Last-Modified
( chỉ ra lần gần nhất dữ liệu được chỉnh sửa ).Etag
( biểu thị tag ).If-Modified-Since
(dùng với header Last-Modified
).If-None-Match
(dùng với header Etag
).Hãy xem ví dụ sau: Ở hình dưới đây, client đã không có bất cứ phiên bản cũ nào của tài nguyên doc. Do đó cả 2 header If-Modified-Since và If-None-Match đều không được sử dụng khi tài nguyên được gửi. Sau đó, server phản hồi với các header Etag and Last-Modified được thiết lập.
Client có thể thiết lập header If-Modified-Since và If-None-Match một lần khi cố gắng gửi request tới cùng resource. Nếu response trả về là giống nhau, server đơn giản là response lại với mã status 304 - Not Modified
và không gửi lại resource nữa.
Rate limiting được sử dụng để kiểm soát việc một consumer có thể gửi đến API bao nhiêu request. Để cho API biết có bao nhiêu request đã được gửi, trong header hãy thiết lập như sau:
X-Rate-Limit-Limit
, số lượng các request được cho phép trong một khoảng thời gian cho trước.X-Rate-Limit-Remaining
, số lượng các request còn lại trong cùng khoảng thời gian.X-Rate-Limit-Rese
: thời gian mà rate limit được thiết lập lại.Phần lớn các framework HTTP hỗ trợ bằng các công cụ bên ngoài (hoặc với plugin). Ví dụ, nếu bạn sử dụng Koa, hãy thử package koa-ratelimit. Chú ý rằng thời gian có thể thay đổi tùy vào các nhà cung cấp API - ví dụ đối với Github là 1 giờ, trong khi Twitter là 15 phút.
Các API được viết ra để mọi người có thể sử dụng chúng. Do đó việc cung cấp các document đi kèm với các REST API là điều cần thiết. Dưới đây là các open-source giúp các bạn trong việc tạo api doc:
Trong vài năm vừa qua, hai ngôn ngữ truy vấn lớn cho các API đã nổi lên: GraphQL của Facebook và Falcor của Netflix. Tại sao ta lại cần chúng đến thế?
Tưởng tượng một request tài nguyên RESTful như sau:
/org/1/space/2/docs/1/collaborators?
include=email&page=1&limit=10
Request này có thể trở nên vượt qua ngoài tầm kiểm soát của chúng ta khá dễ dàng - trường hợp bạn muốn lấy cùng định dạng response cho tất cả model vào mọi thời điểm. Lúc này thì GraphQL và Falcor sẽ giúp bạn. GraphQL là một ngôn ngữ truy vấn cho APIs và một runtime để hoàn thành những truy vấn đó. Nó cung cấp mô tả đầy đủ và dễ hiểu về dữ liệu trong API của bạn, đồng thời cho phép clients quyền yêu cầu chính xác những gì họ cần. Falcor là một nền tảng sáng tạo giúp tối ưu hóa sức mạnh cho Netflix UI. Nó cho phép bạn model mọi dữ liệu ở backend như một JSON object ảo trên Node server của bạn. Ở phía client, bạn làm việc với JSON object thông qua một số toán tử JavaScript như get, set và call.
Nếu bạn định bắt đầu phát triển một REST API Node.js hoặc tạo một phiên bản mới của API cũ, chúng tôi đã thu thập 4 ví dụ thực tế để giúp bạn:
Nguồn: viblo.asia