8 phương pháp hay nhất để thiết kế REST API | Học trực tuyến CNTT, học lập trình từ cơ bản đến nâng cao

8 phương pháp hay nhất để thiết kế REST API

Chia sẻ kiến thức 02/01/2022

FUNiX sẽ giới thiệu đến các bạn Các phương pháp hay nhất để thiết kế REST API sao cho bất kỳ ai sử dụng cũng cảm thấy dễ hiểu, dùng được lâu dài, an toàn và nhanh chóng vì dữ liệu cung cấp cho khách hàng được bảo mật.

>> Lập trình Web năm 2021 cần học những gì để trở thành “cao thủ”?

>> Phát triển ứng dụng web – công việc lập trình phổ biến và quan trọng nhất

Rest – Representational State Transfer – là một dạng chuyển đổi cấu trúc, thường sử dụng phương thức HTTP để tạo ra giao tiếp giữa các máy.

API – Application Programming Interface – tập hợp bao gồm những cơ chế và quy tắc tạo ra sự tương tác giữa một ứng dụng hoặc một thành phần nào đó với một ứng dụng hoặc một số thành phần khác. 

REST API – là một trong những loại dịch vụ web phổ biến hiện nay, cho phép các ứng dụng khác nhau, bao gồm các ứng dụng trình duyệt, có thể giao tiếp với máy chủ thông qua giao thức API REST. Do đó điều rất quan trọng là phải thiết kế các API REST đúng cách để khi sử dụng không gặp phải sự cố liên quan đến tính bảo mật, hiệu suất và tính dễ sử dụng cho người tiêu dùng API. 

1. REST API là gì?

8 phương pháp hay nhất để thiết kế REST API
REST API

REST API là một giao diện lập trình ứng dụng mà tuân theo các ràng buộc kiến ​​trúc cụ thể, như giao tiếp không trạng thái và dữ liệu có thể lưu vào bộ nhớ cache. Nó không phải là một giao thức hoặc tiêu chuẩn. Mặc dù các API có thể được truy cập thông qua một số giao thức truyền thông, nhưng chúng thường sử dụng nhất là HTTPS, vì vậy các nguyên tắc bên dưới áp dụng cho các điểm cuối REST API sẽ được truy cập qua internet.

2. Chấp nhận và phản hồi với JSON

JSON – JavaScript Object Notation – là một tiêu chuẩn mở giúp trao đổi dữ liệu trên website, đây là một kiểu định dạng dữ liệu tuân theo một quy tắc nhất định mà hầu hết ngôn ngữ lập trình hiện nay đều đọc được. 

Để phản hồi các yêu cầu, REST API phải chấp nhận và gửi phản hồi bằng định dạng JSON. Nó là tiêu chuẩn để truyền dữ liệu. Hầu hết mọi công nghệ kết nối mạng đều có thể sử dụng nó: JavaScript có các phương thức tích hợp để mã hóa và giải mã JSON thông qua Fetch API (một API đơn giản giúp việc gửi và nhận request dễ dàng và đơn giản hơn) hoặc một ứng dụng khách hàng HTTP/HTTPS khác. Các công nghệ phía máy chủ có các thư viện có thể giải mã JSON mà không cần làm nhiều việc.

Có nhiều cách khác để chuyển dữ liệu khác nhau. Nếu XML (Extensible Markup Language – ngôn ngữ đánh dấu mở rộng, có chức năng truyền và mô tả nhiều loại dữ liệu khác nhau) không tự chuyển đổi dữ liệu thành thứ có thể sử dụng được như JSON thì sẽ không được các framework hỗ trợ rộng rãi, lúc đó sẽ không thể thao tác dữ liệu này dễ dàng ở phía máy khách hàng, đặc biệt là trong trình duyệt và cuối cùng phải làm rất nhiều công việc bổ sung chỉ để thực hiện truyền dữ liệu bình thường.

Dữ liệu biểu mẫu rất tốt cho việc gửi dữ liệu, đặc biệt nếu chúng ta muốn gửi file. Nhưng đối với văn bản và số, không cần dữ liệu biểu mẫu để chuyển những dữ liệu đó vì với hầu hết các framework, chúng ta có thể chuyển JSON bằng cách đơn giản nhất là lấy dữ liệu trực tiếp ở phía máy khách hàng. 

Để đảm bảo rằng khách hàng sẽ hiểu được khi ứng dụng REST API phản hồi với JSON, chúng ta nên đặt Content-Type trong tiêu đề phản hồi application/json. Nhiều framework ứng dụng phía máy chủ đặt tiêu đề phản hồi tự động. Một số máy khách HTTP nhìn vào Content-Type trong tiêu đề phản hồi và phân tích cú pháp dữ liệu theo định dạng đó.

Chỉ có ngoại lệ duy nhất là khi chúng ta cố gắng gửi và nhận file giữa máy khách và máy chủ, sau đó phải xử lý các phản hồi file và gửi dữ liệu biểu mẫu từ máy khách đến máy chủ. Điều này sẽ được làm rõ trong một bài khác.

Nên đảm bảo rằng các điểm cuối trả về JSON dưới dạng phản hồi. Nhiều framework phía máy chủ có tính năng này như được tích hợp sẵn.

REST API chấp nhận và phản hồi với JSON
REST API chấp nhận và phản hồi với JSON.

Cho ví dụ về một API chấp nhận tải trọng JSON, sẽ sử dụng Express – là một back end framework cho Node.js (một nền tảng được xây dựng trên Javascript runtime, giúp xây dựng được các ứng dụng mạng một cách nhanh chóng và dễ dàng mở rộng). Chúng ta có thể sử dụng phần mềm trung gian body-parser để phân tích cú pháp cho phần thân được yêu cầu với JSON và sau đó chúng ta có thể dùng phương thức res.json với đối tượng mà chúng ta muốn trả về dưới dạng phản hồi JSON như sau:

const express = require(‘express’);

const bodyParser = require(‘body-parser’);

const app = express();

app.use(bodyParser.json());

app.post(‘/’, (req, res) => {

  res.json(req.body);

});

app.listen(3000, () => console.log(‘server started’));

Lệnh bodyParser.json() phân tích cú pháp chuỗi nội dung yêu cầu JSON thành một đối tượng JavaScript và sau đó gán nó cho đối tượng req.body.

Đặt tiêu đề Content-Type trong phản hồi thành application/json; charset=utf-8 mà không có bất kỳ thay đổi nào. Phương pháp trên áp dụng cho hầu hết các back end framework khác.

>>> Xem thêm: Hướng dẫn đầy đủ về Bảo mật API cho người trong ngành

3. Sử dụng danh từ thay vì động từ trong đường dẫn điểm cuối

Không nên sử dụng động từ trong đường dẫn điểm cuối mà thay vào đó nên sử dụng các danh từ đại diện cho thực thể của điểm cuối chúng ta đang truy xuất hoặc thao tác như tên đường dẫn.

Vì phương thức yêu cầu HTTP đã có động từ nên khi có thêm trong đường dẫn điểm cuối REST API vừa không hữu ích vừa làm đường dẫn dài ra mà không truyền tải thêm bất kỳ thông tin mới nào. Các động từ được chọn có thể thay đổi tùy theo ý thích của lập trình viên. 

Hành động sẽ được chỉ ra bởi phương thức yêu cầu HTTP. Các phương pháp phổ biến nhất bao gồm GET, POST, PUT và DELETE.

  • GET truy xuất tài nguyên.
  • POST gửi dữ liệu mới đến máy chủ.
  • PUT cập nhật dữ liệu hiện có.
  • DELETE xóa dữ liệu.

Với hai nguyên tắc đã thảo luận ở trên, chúng ta nên tạo các tuyến như GET /articles/ để lấy các bài báo. Tương tự như vậy, POST /articles/ để thêm một bài viết mới, PUT /articles/:id là để cập nhật bài viết với ID đã cho. DELETE /articles/:id là để xóa một bài viết hiện có với ID đã cho.

/articles đại diện cho một tài nguyên REST API. Ví dụ: chúng ta có thể sử dụng Express để thêm các điểm cuối cho thao tác các bài viết như sau:

const express = require(‘express’);

const bodyParser = require(‘body-parser’);

const app = express();

app.use(bodyParser.json());

app.get(‘/articles’, (req, res) => {

  const articles = [];

  // code to retrieve an article…

  res.json(articles);

});

app.post(‘/articles’, (req, res) => {

  // code to add a new article…

  res.json(req.body);

});

app.put(‘/articles/:id’, (req, res) => {

  const { id } = req.params;

  // code to update an article…

  res.json(req.body);

});

app.delete(‘/articles/:id’, (req, res) => {

  const { id } = req.params;

  // code to delete an article…

  res.json({ deleted: id });

});

app.listen(3000, () => console.log(‘server started’));

Đoạn mã trên đã xác định các điểm cuối để thao tác các bài báo. Tên đường dẫn không có bất kỳ động từ nào mà chỉ có danh từ. Các động từ nằm trong HTTP.

Các điểm cuối POST, PUT và DELETE đều lấy JSON như phần thân yêu cầu và đều trả về JSON dưới dạng phản hồi, bao gồm cả điểm cuối GET.

4. Lồng ghép logic trên các điểm cuối

Khi thiết kế các điểm cuối REST API, phải nhóm các điểm chứa thông tin liên quan. Nghĩa là nếu một đối tượng có thể chứa một đối tượng khác thì nên thiết kế điểm cuối để phản ánh điều đó. Đây là một phương pháp hay, kể cả dữ liệu có được cấu trúc như thế này trong cơ sở dữ liệu hay không. Không nên sao chép cấu trúc cơ sở dữ liệu trong các điểm cuối để tránh cung cấp thông tin không cần thiết cho những kẻ tấn công.

Ví dụ: nếu chúng ta muốn một điểm cuối để nhận các comment cho một bài báo, chúng ta nên nối /comments vào cuối đường dẫn /articles. Có thể làm điều đó với code sau trong Express:

const express = require(‘express’);

const bodyParser = require(‘body-parser’);

const app = express();

app.use(bodyParser.json());

app.get(‘/articles/:articleId/comments’, (req, res) => {

  const { articleId } = req.params;

  const comments = [];

  // code to get comments by articleId

  res.json(comments);

});

app.listen(3000, () => console.log(‘server started’));

Đoạn mã trên sử dụng phương thức GET trên đường dẫn ‘/articles/:articleId/comments’. Chúng ta sẽ nhận được comments trên bài báo được xác định bởi articleId, và sau đó trả lại nó trong phản hồi. Thêm ‘comments’ vào sau phân đoạn đường dẫn ‘/articles/:articleId’ để chỉ ra rằng đó là tài nguyên con của /articles.

Nguyên tắc tương tự cũng áp dụng cho các điểm cuối POST, PUT và DELETE. Tất cả đều có thể sử dụng cùng một loại cấu trúc lồng nhau cho các tên đường dẫn.

Tuy nhiên sau khoảng cấp độ thứ hai hoặc thứ ba, các điểm cuối lồng nhau có thể khó sử dụng. Vậy nếu dữ liệu đó không nhất thiết phải được chứa trong đối tượng cấp cao nhất, thì hãy xem xét để trả lại URL cho các resources đó.

Ví dụ: giả sử khi muốn trả lại tác giả của các comment cụ thể. Lúc này có thể sử dụng /articles/:articleId/comments/:commentId/author nhưng thay vào đó hãy trả lại URL cho người dùng cụ thể trong phản hồi JSON:

“author”: “/users/:userId”

>>> Xem thêm: Xác thực API là gì? Xác thực API hoạt động như thế nào?

5. Xử lý lỗi khéo léo và trả về những code lỗi tiêu chuẩn

Để loại bỏ sự nhầm lẫn cho người dùng REST API khi xảy ra lỗi, cần xử lý lỗi một cách khéo léo và trả về mã phản hồi HTTP cho biết đã xảy ra loại lỗi nào, giúp cung cấp đủ thông tin về vấn đề đã xảy ra với API để tiến hành bảo trì. 

Các lỗi HTTP status code phổ biến gồm:

  • 400 Bad Request – Đầu vào phía máy khách không xác thực được.
  • 401 Unauthorized – Người dùng không được phép truy cập resource. Lỗi này thường trả về khi người dùng không được xác thực.
  • 403 Forbidden – Người dùng đã được xác thực, nhưng không được phép truy cập resource.
  • 404 Not Found – Một resource không được tìm thấy.
  • 500 Internal server error – Đây là một lỗi chung của máy chủ. 
  • 502 Bad Gateway – Phản hồi không hợp lệ từ một máy chủ ngược dòng.
  • 503 Service Unavailable – Điều gì đó không mong muốn đã xảy ra ở phía máy chủ (Có thể là quá tải máy chủ, một số bộ phận của hệ thống bị lỗi, …).

Nên đưa ra các lỗi tương ứng với sự cố mà ứng dụng đã gặp phải. Ví dụ: muốn từ chối dữ liệu từ tải trọng yêu cầu, thì ta phải trả lại 400 phản hồi như sau trong REST API Express:

const express = require(‘express’);

const bodyParser = require(‘body-parser’);

const app = express();

// existing users

const users = [

  { email: ‘abc@foo.com’ }

]

app.use(bodyParser.json());

app.post(‘/users’, (req, res) => {

  const { email } = req.body;

  const userExists = users.find(u => u.email === email);

  if (userExists) {

    return res.status(400).json({ error: ‘User already exists’ })

  }

  res.json(req.body);

});

app.listen(3000, () => console.log(‘server started’));

Đoạn mã trên thể hiện danh sách những người dùng đang tồn tại trong mảng users với email đã cho.

Nếu cố gắng gửi đi tải trọng với giá trị email đã tồn tại trong users, sẽ nhận được 400 status code phản hồi kèm theo thông báo ‘User already exists’ cho biết rằng người dùng đã tồn tại. 

Lỗi code cần phải có thông báo đi kèm để cung cấp thông tin giúp người bảo trì khắc phục sự cố, nhưng không để những kẻ tấn công có cơ hội sử dụng nội dung lỗi đó để đánh cắp thông tin hoặc phá hủy hệ thống.

6. Cho phép lọc, sắp xếp và phân trang dữ liệu

Cơ sở dữ liệu đằng sau REST API có thể rất lớn. Nên khi có quá nhiều dữ liệu trả lại cùng một lúc sẽ làm chậm hoặc làm hỏng hệ thống. Do đó cần có cách để lọc.

Cần các cách phân trang dữ liệu để chỉ trả về một vài kết quả tại một thời điểm. 

Lọc và phân trang đều tăng hiệu suất bằng cách giảm việc sử dụng resources máy chủ. Khi càng nhiều dữ liệu được tích lũy trong cơ sở dữ liệu, thì những tính năng này càng trở nên quan trọng hơn.

Dưới đây là một ví dụ trong đó REST API có thể chấp nhận một chuỗi truy vấn với các tham số truy vấn khác nhau để cho phép lọc theo các trường: 

const express = require(‘express’);

const bodyParser = require(‘body-parser’);

const app = express();

// employees data in a database

const employees = [

  { firstName: ‘Jane’, lastName: ‘Smith’, age: 20 },

  //…

  { firstName: ‘John’, lastName: ‘Smith’, age: 30 },

  { firstName: ‘Mary’, lastName: ‘Green’, age: 50 },

]

app.use(bodyParser.json());

app.get(‘/employees’, (req, res) => {

  const { firstName, lastName, age } = req.query;

  let results = […employees];

  if (firstName) {

    results = results.filter(r => r.firstName === firstName);

  }

  if (lastName) {

    results = results.filter(r => r.lastName === lastName);

  }

  if (age) {

    results = results.filter(r => +r.age === +age);

  }

  res.json(results);

});

app.listen(3000, () => console.log(‘server started’));

Trong đoạn mã trên có biến req.query biến để nhận các tham số truy vấn. Để trích xuất các giá trị thuộc tính chúng ta đã hủy cấu trúc các tham số truy vấn riêng lẻ thành các biến bằng cách sử dụng cú pháp hủy cấu trúc JavaScript. Tiếp tục chạy filter với từng giá trị tham số truy vấn để xác định vị trí các mục muốn trả về.

Khi đó sẽ trả về results như một phản hồi. Do đó khi thực hiện yêu cầu GET đến đường dẫn sau với chuỗi truy vấn:

/employees?lastName=Smith&age=30

Chúng ta nhận được phản hồi đã lọc theo lastNameage

[

    {

        “firstName”: “John”,

        “lastName”: “Smith”,

        “age”: 30

    }

]

Tương tự chúng ta có thể chấp nhận tham số truy vấn page và trả về một nhóm các mục nhập ở vị trí từ (page – 1) * 20 đến page * 20

Chúng ta cũng có thể chỉ định các trường để sắp xếp trong chuỗi truy vấn. Ví dụ: có thể lấy tham số từ một chuỗi truy vấn với các trường muốn sắp xếp dữ liệu. Sau đó có thể sắp xếp chúng theo các trường riêng lẻ đó.

Ví dụ: muốn trích xuất chuỗi truy vấn từ một URL như:

http://example.com/articles?sort=+author,-datepublished

Trong đó + có nghĩa là tăng dần và có nghĩa là giảm dần. Vì vậy tên tác giả sẽ sắp xếp theo thứ tự bảng chữ cái và datepublished từ gần đây nhất đến xa hơn.

>>> Xem thêm: Bảo mật API là gì? Bảo mật API hoạt động như thế nào?

7. Sử dụng và duy trì các phương pháp bảo mật tốt

Duy trì phương pháp bảo mật tốt
Duy trì phương pháp bảo mật tốt.

Hầu hết giao tiếp giữa máy khách và máy chủ phải ở chế độ riêng tư vì thường gửi và nhận thông tin cá nhân. Do đó bắt buộc sử dụng SSL (Secure Sockets Layer – Lớp socket bảo mật) hoặc TLS (Transport Layer Security – Bảo mật lớp truyền tải) để bảo mật.

Chứng chỉ SSL được tải lên máy chủ miễn phí hoặc chi phí rất thấp, nên không có lý do gì để không làm cho các REST API giao tiếp qua các kênh an toàn.

Người dùng sẽ không thể truy cập thêm thông tin không cần thiết. Ví dụ: một người dùng bình thường sẽ không thể truy cập thông tin của người dùng khác. Họ cũng không thể truy cập dữ liệu của quản trị viên.

Để thực thi nguyên tắc ít đặc quyền nhất, cần thêm chức năng kiểm tra người dùng với một vai trò duy nhất hoặc có các vai trò chi tiết hơn cho từng người.

Nếu chọn nhóm người dùng vào một vài vai trò, thì các vai trò đó bao gồm tất cả các quyền họ cần và không còn gì hơn nữa. Nếu có các quyền chi tiết hơn cho từng tính năng mà người dùng có quyền truy cập, thì phải đảm bảo rằng quản trị viên có thể thêm và xóa các tính năng đó khỏi từng người dùng một cách phù hợp. Ngoài ra chúng ta cần thêm một số vai trò cài đặt trước mà có thể áp dụng được cho một nhóm người dùng, để không phải làm điều đó cho từng người dùng theo cách thủ công.

8. Lưu trữ dữ liệu vào bộ nhớ đệm để cải thiện hiệu suất

Có thể thêm bộ nhớ đệm để trả về dữ liệu nhanh hơn từ bộ nhớ đệm cục bộ thay vì truy vấn cơ sở dữ liệu để lấy dữ liệu mỗi khi người dùng yêu cầu. Tuy nhiên, dữ liệu người dùng nhận được có thể đã bị lỗi thời. 

Có nhiều giải pháp để lưu vào bộ nhớ đệm như Redis, bộ nhớ đệm trong bộ nhớ, … Chúng ta có thể thay đổi cách dữ liệu được lưu vào bộ nhớ đệm khi nhu cầu thay đổi.

Ví dụ: REST API Express có phần mềm trung gian apicache để thêm bộ nhớ đệm vào ứng dụng mà không cần cấu hình nhiều. Chúng ta có thể thêm một bộ đệm ẩn trong bộ nhớ đơn giản vào máy chủ của như sau:

const express = require(‘express’);

const bodyParser = require(‘body-parser’);

const apicache = require(‘apicache’);

const app = express();

let cache = apicache.middleware;

app.use(cache(‘5 minutes’));

// employees data in a database

const employees = [

  { firstName: ‘Jane’, lastName: ‘Smith’, age: 20 },

  //…

  { firstName: ‘John’, lastName: ‘Smith’, age: 30 },

  { firstName: ‘Mary’, lastName: ‘Green’, age: 50 },

]

app.use(bodyParser.json());

app.get(‘/employees’, (req, res) => {

  res.json(employees);

});

app.listen(3000, () => console.log(‘server started’));

Đoạn mã trên chỉ tham chiếu đến phần mềm trung gian apicache với apicache.middleware và sau đó chúng ta có:

app.use(cache(‘5 minutes’))

để áp dụng bộ nhớ đệm cho toàn bộ ứng dụng. Chúng ta lưu kết quả vào bộ nhớ đệm trong năm phút và có thể điều chỉnh theo nhu cầu của chúng ta.

Nếu bạn đang sử dụng bộ nhớ đệm thì cũng nên đưa thông tin Cache-Control vào tiêu đề. Điều này sẽ giúp người dùng sử dụng hiệu quả hệ thống bộ nhớ đệm của bạn.

Lưu trữ dữ liệu vào bộ nhớ đệm
Lưu trữ dữ liệu vào bộ nhớ đệm.

>>> Xem thêm: Cách tích hợp dữ liệu thời tiết trên trang chủ với API Weatherstack

9. Tạo phiên bản cho các REST API 

Chúng ta nên có các phiên bản REST API khác nhau vì khi thực hiện bất kỳ thay đổi nào với chúng đều có thể làm hỏng ứng dụng khách hàng. Việc tạo phiên bản có thể được thực hiện theo phiên bản ngữ nghĩa giống với hầu hết các ứng dụng hiện nay (ví dụ: 2.0.6 để chỉ ra phiên bản chính 2 và bản vá thứ 6).

Cách này có thể loại bỏ dần các điểm cuối cũ thay vì buộc mọi người chuyển sang API mới. Điểm cuối v1 có thể duy trì hoạt động cho những người không muốn thay đổi, trong khi đó v2 với các tính năng mới có thể phục vụ những người sẵn sàng nâng cấp. Nếu API công khai thì điều này rất quan trọng. Chúng ta nên tạo phiên bản để không phá vỡ các ứng dụng của bên thứ ba sử dụng API.

Việc tạo phiên bản thường được thực hiện với /v1/, /v2/,… được thêm vào đầu đường dẫn REST API.

Ví dụ, chúng ta có thể làm với Express như sau:

const express = require(‘express’);

const bodyParser = require(‘body-parser’);

const app = express();

app.use(bodyParser.json());

app.get(‘/v1/employees’, (req, res) => {

  const employees = [];

  // code to get employees

  res.json(employees);

});

app.get(‘/v2/employees’, (req, res) => {

  const employees = [];

  // different code to get employees

  res.json(employees);

});

app.listen(3000, () => console.log(‘server started’));

FUNiX vừa cung cấp cho các bạn 8 phương pháp hay nhất để thiết kế REST API. Hy vọng giúp các bạn tạo ra được REST API tốt nhất và hiệu quả nhất.

Bài gốc: https://stackoverflow.blog/2020/03/02/best-practices-for-rest-api-design/

>>> Bài viết liên quan:

API là gì? API mở đang thay đổi Internet như thế nào?

8 phương pháp hay nhất để thiết kế REST API

7 phương pháp hay nhất để bảo mật REST API: Xác thực và ủy quyền

Phạm Thị Thanh Ngọc (theo Stack Overflow)

ĐĂNG KÝ TƯ VẤN HỌC LẬP TRÌNH TẠI FUNiX

Bình luận (
0
)

Bài liên quan

  • Tầng 0, tòa nhà FPT, 17 Duy Tân, Q. Cầu Giấy, Hà Nội
  • info@funix.edu.vn
  • 0782313602 (Zalo, Viber)        
Chat Button
Chat với FUNiX GPT ×

yêu cầu gọi lại