Tìm hiểu cách tận dụng Next.js API Routes để xây dựng backend serverless ngay trong ứng dụng Next.js của bạn. Hướng dẫn này bao gồm mọi thứ từ thiết lập cơ bản đến các kỹ thuật nâng cao để xử lý xác thực, lưu trữ dữ liệu, v.v.
Next.js API Routes: Xây Dựng Backend Một Cách Dễ Dàng
Next.js đã cách mạng hóa việc phát triển front-end với các tính năng mạnh mẽ và cấu trúc trực quan. Nhưng bạn có biết rằng nó cũng có thể đơn giản hóa đáng kể việc phát triển backend không? Next.js API Routes cho phép bạn tạo các điểm cuối API (API endpoints) serverless ngay trong ứng dụng Next.js của mình, loại bỏ nhu cầu về một máy chủ backend riêng biệt trong nhiều trường hợp. Hướng dẫn toàn diện này sẽ chỉ cho bạn qua quy trình xây dựng một backend mạnh mẽ và có khả năng mở rộng bằng cách sử dụng Next.js API Routes.
Next.js API Routes là gì?
API Routes là các hàm serverless mà bạn tạo trong thư mục /pages/api
của dự án Next.js. Các hàm này xử lý các yêu cầu HTTP đến và trả về phản hồi, giống như một API backend truyền thống. Sự khác biệt chính là chúng được triển khai dưới dạng các hàm serverless, có nghĩa là bạn không cần phải quản lý máy chủ hay cơ sở hạ tầng.
Hãy coi chúng như những hàm backend nhẹ, theo yêu cầu được tích hợp liền mạch với front-end Next.js của bạn.
Lợi ích của việc sử dụng Next.js API Routes
- Đơn giản hóa việc phát triển: Viết cả mã front-end và backend trong cùng một dự án, sử dụng JavaScript hoặc TypeScript. Không còn phải chuyển đổi ngữ cảnh giữa các dự án và công nghệ khác nhau.
- Kiến trúc Serverless: Hưởng lợi từ khả năng mở rộng, độ tin cậy và hiệu quả chi phí của điện toán serverless. Chỉ trả tiền cho các tài nguyên bạn tiêu thụ.
- Triển khai dễ dàng: Triển khai toàn bộ ứng dụng của bạn (front-end và backend) bằng một lệnh duy nhất sử dụng các nền tảng như Vercel hoặc Netlify.
- Bảo mật tích hợp: Next.js và các nền tảng serverless cung cấp các tính năng bảo mật tích hợp để bảo vệ các điểm cuối API của bạn.
- Cải thiện hiệu suất: API Routes có thể được triển khai gần hơn với người dùng của bạn, giảm độ trễ và cải thiện hiệu suất, đặc biệt có lợi cho người dùng trên toàn cầu.
- Tái sử dụng mã nguồn: Chia sẻ mã nguồn giữa front-end và backend của bạn, giảm sự trùng lặp mã và cải thiện khả năng bảo trì.
Bắt đầu với Next.js API Routes
Hãy tạo một API route đơn giản trả về một phản hồi JSON. Đầu tiên, hãy chắc chắn rằng bạn đã thiết lập một dự án Next.js. Nếu chưa, hãy tạo một dự án bằng cách sử dụng:
npx create-next-app my-app
cd my-app
Bây giờ, hãy tạo một tệp có tên hello.js
bên trong thư mục /pages/api
:
// pages/api/hello.js
export default function handler(req, res) {
res.status(200).json({ name: 'John Doe' })
}
Đoạn mã này định nghĩa một API route đơn giản trả về một đối tượng JSON chứa tên "John Doe". Để truy cập API route này, hãy khởi động máy chủ phát triển Next.js của bạn:
npm run dev
Sau đó, mở trình duyệt của bạn và điều hướng đến http://localhost:3000/api/hello
. Bạn sẽ thấy phản hồi JSON sau:
{"name": "John Doe"}
Tìm hiểu về Trình xử lý API Route (Handler)
Hàm handler
trong API route của bạn nhận hai đối số:
req
: Một instance củahttp.IncomingMessage
, chứa thông tin về yêu cầu đến, chẳng hạn như phương thức yêu cầu, headers và body.res
: Một instance củahttp.ServerResponse
, cho phép bạn gửi phản hồi trở lại cho client.
Bạn có thể sử dụng các đối tượng này để xử lý các loại yêu cầu khác nhau, đọc dữ liệu từ request body, thiết lập response headers và gửi các loại phản hồi khác nhau.
Xử lý các phương thức HTTP khác nhau
Bạn có thể sử dụng thuộc tính req.method
để xác định phương thức HTTP của yêu cầu đến và xử lý các phương thức khác nhau cho phù hợp. Ví dụ:
// pages/api/method.js
export default function handler(req, res) {
if (req.method === 'GET') {
// Xử lý yêu cầu GET
res.status(200).json({ message: 'Đây là một yêu cầu GET' })
} else if (req.method === 'POST') {
// Xử lý yêu cầu POST
res.status(200).json({ message: 'Đây là một yêu cầu POST' })
} else {
// Xử lý các phương thức khác
res.status(405).json({ message: 'Phương thức không được phép' })
}
}
Trong ví dụ này, API route xử lý cả yêu cầu GET và POST. Nếu phương thức yêu cầu là GET, nó sẽ trả về một đối tượng JSON chứa thông điệp "Đây là một yêu cầu GET". Nếu phương thức yêu cầu là POST, nó sẽ trả về một đối tượng JSON chứa thông điệp "Đây là một yêu cầu POST". Nếu phương thức yêu cầu là bất kỳ thứ gì khác, nó sẽ trả về lỗi 405 Method Not Allowed.
Đọc dữ liệu từ Request Body
Đối với các yêu cầu POST, PUT, và PATCH, bạn thường cần đọc dữ liệu từ request body. Next.js cung cấp hỗ trợ tích hợp để phân tích cú pháp các request body dạng JSON và URL-encoded. Để phân tích một request body JSON, bạn có thể sử dụng thuộc tính req.body
. Ví dụ:
// pages/api/post.js
export default async function handler(req, res) {
if (req.method === 'POST') {
const { name, email } = req.body
// Xử lý dữ liệu
console.log('Tên:', name)
console.log('Email:', email)
res.status(200).json({ message: 'Dữ liệu đã được nhận thành công' })
} else {
res.status(405).json({ message: 'Phương thức không được phép' })
}
}
Để kiểm tra API route này, bạn có thể sử dụng một công cụ như Postman hoặc curl để gửi yêu cầu POST với nội dung JSON:
curl -X POST -H "Content-Type: application/json" -d '{"name": "Jane Doe", "email": "jane.doe@example.com"}' http://localhost:3000/api/post
Thiết lập Response Headers
Bạn có thể sử dụng phương thức res.setHeader()
để thiết lập response headers. Điều này hữu ích để thiết lập loại nội dung, kiểm soát bộ đệm (cache control), và các thông tin quan trọng khác. Ví dụ:
// pages/api/headers.js
export default function handler(req, res) {
res.setHeader('Content-Type', 'application/json')
res.setHeader('Cache-Control', 's-maxage=3600')
res.status(200).json({ message: 'Xin chào, thế giới!' })
}
Trong ví dụ này, API route thiết lập header Content-Type
thành application/json
, cho biết rằng phản hồi là một đối tượng JSON. Nó cũng thiết lập header Cache-Control
thành s-maxage=3600
, yêu cầu trình duyệt và CDN lưu vào bộ đệm phản hồi trong tối đa 1 giờ.
Xử lý lỗi
Việc xử lý lỗi một cách mượt mà trong các API route của bạn là rất quan trọng. Bạn có thể sử dụng các khối try-catch để bắt các ngoại lệ và gửi các phản hồi lỗi thích hợp cho client. Ví dụ:
// pages/api/error.js
export default async function handler(req, res) {
try {
// Mô phỏng một lỗi
throw new Error('Đã xảy ra sự cố')
} catch (error) {
console.error(error)
res.status(500).json({ message: 'Lỗi máy chủ nội bộ' })
}
}
Trong ví dụ này, API route mô phỏng một lỗi bằng cách ném một đối tượng Error
mới. Khối catch bắt lỗi, ghi nó vào console, và gửi một phản hồi 500 Internal Server Error cho client. Hãy cân nhắc sử dụng một hệ thống ghi log mạnh mẽ như Sentry hoặc Datadog cho môi trường production.
Kết nối đến Cơ sở dữ liệu
Một trong những trường hợp sử dụng phổ biến nhất cho API routes là kết nối đến cơ sở dữ liệu. Next.js API Routes tích hợp liền mạch với nhiều loại cơ sở dữ liệu khác nhau, bao gồm:
- MongoDB: Một cơ sở dữ liệu NoSQL phổ biến, rất phù hợp cho dữ liệu linh hoạt và không có cấu trúc.
- PostgreSQL: Một cơ sở dữ liệu quan hệ mã nguồn mở mạnh mẽ, nổi tiếng về độ tin cậy và tính toàn vẹn của dữ liệu.
- MySQL: Một cơ sở dữ liệu quan hệ mã nguồn mở phổ biến khác được sử dụng rộng rãi cho các ứng dụng web.
- Firebase: Một nền tảng dựa trên đám mây cung cấp cơ sở dữ liệu thời gian thực và các dịch vụ khác.
- FaunaDB: Một cơ sở dữ liệu serverless được thiết kế cho các ứng dụng toàn cầu.
Đây là một ví dụ về cách kết nối đến cơ sở dữ liệu MongoDB trong một Next.js API route:
// pages/api/mongodb.js
import { MongoClient } from 'mongodb'
const uri = process.env.MONGODB_URI
const options = {}
let client
let clientPromise
if (!process.env.MONGODB_URI) {
throw new Error('Vui lòng thêm Mongo URI của bạn vào .env.local')
}
if (process.env.NODE_ENV === 'development') {
// Ở chế độ development, sử dụng một biến toàn cục để giá trị
// được bảo toàn qua các lần tải lại module do HMR (Hot Module Replacement) gây ra.
if (!global._mongoClientPromise) {
client = new MongoClient(uri, options)
global._mongoClientPromise = client.connect()
}
clientPromise = global._mongoClientPromise
} else {
// Ở chế độ production, tốt nhất là không sử dụng biến toàn cục.
client = new MongoClient(uri, options)
clientPromise = client.connect()
}
// Xuất một promise MongoClient có phạm vi module. Bằng cách thực hiện điều này trong một
// module riêng biệt, client có thể được tái sử dụng an toàn trên nhiều hàm.
// Xem: https://github.com/vercel/next.js/blob/canary/examples/with-mongodb/lib/mongodb.js
export default async function handler(req, res) {
try {
const client = await clientPromise
const db = client.db(process.env.MONGODB_DB)
const collection = db.collection('users')
const users = await collection.find({}).toArray()
res.status(200).json({ users })
} catch (e) {
console.error(e)
res.status(500).json({ message: 'Không thể lấy dữ liệu người dùng' })
}
}
Trước khi chạy mã này, hãy chắc chắn rằng bạn đã cài đặt gói mongodb
:
npm install mongodb
Bạn cũng cần thiết lập các biến môi trường MONGODB_URI
và MONGODB_DB
. Các biến này nên được định nghĩa trong tệp .env.local
của bạn (hoặc cài đặt biến môi trường của nhà cung cấp hosting cho môi trường production). MONGODB_URI
chứa chuỗi kết nối đến cơ sở dữ liệu MongoDB của bạn, và MONGODB_DB
chỉ định tên cơ sở dữ liệu.
Xác thực và Phân quyền
Bảo vệ các API route của bạn là rất quan trọng đối với an ninh. Next.js API Routes có thể được bảo mật bằng nhiều kỹ thuật xác thực và phân quyền khác nhau, bao gồm:
- JSON Web Tokens (JWT): Một tiêu chuẩn để truyền thông tin an toàn giữa các bên dưới dạng một đối tượng JSON.
- API Keys: Một cách đơn giản để hạn chế quyền truy cập vào các điểm cuối API của bạn.
- OAuth: Một giao thức ủy quyền cho phép người dùng cấp cho các ứng dụng của bên thứ ba quyền truy cập vào tài nguyên của họ mà không cần chia sẻ thông tin đăng nhập.
- NextAuth.js: Một giải pháp xác thực mã nguồn mở hoàn chỉnh cho các ứng dụng Next.js.
Đây là một ví dụ về cách bảo vệ một API route bằng xác thực JWT:
// pages/api/protected.js
import jwt from 'jsonwebtoken'
const secret = process.env.JWT_SECRET
export default function handler(req, res) {
const token = req.headers.authorization?.split(' ')[1]
if (!token) {
return res.status(401).json({ message: 'Chưa được xác thực' })
}
try {
const decoded = jwt.verify(token, secret)
// Đối tượng "decoded" chứa thông tin người dùng được nhúng trong token
// Ví dụ: const userId = decoded.userId;
// Tiếp tục xử lý yêu cầu
res.status(200).json({ message: 'Truy cập tài nguyên được bảo vệ thành công' })
} catch (error) {
return res.status(401).json({ message: 'Token không hợp lệ' })
}
}
Trước khi chạy mã này, hãy chắc chắn rằng bạn đã cài đặt gói jsonwebtoken
:
npm install jsonwebtoken
Bạn cũng cần thiết lập biến môi trường JWT_SECRET
. Đây phải là một khóa bí mật mạnh, được tạo ngẫu nhiên, được sử dụng để ký và xác minh JWT. Lưu trữ khóa này một cách an toàn và không bao giờ để lộ nó trong mã phía client của bạn.
Middleware
Mặc dù Next.js không cung cấp middleware truyền thống cho các API route theo cách của Express.js, bạn có thể đạt được chức năng tương tự bằng cách bọc các trình xử lý API route của mình bằng các hàm có thể tái sử dụng. Điều này cho phép bạn thực hiện các tác vụ như:
- Xác thực: Xác minh thông tin đăng nhập của người dùng trước khi cho phép truy cập vào các điểm cuối API.
- Phân quyền: Kiểm tra xem người dùng có quyền cần thiết để thực hiện một hành động cụ thể hay không.
- Ghi log: Ghi lại các yêu cầu đến và phản hồi đi để kiểm toán và gỡ lỗi.
- Xác thực dữ liệu (Validation): Xác thực dữ liệu yêu cầu để đảm bảo nó đáp ứng các tiêu chí cụ thể.
- Giới hạn tỷ lệ (Rate Limiting): Bảo vệ API của bạn khỏi bị lạm dụng bằng cách giới hạn số lượng yêu cầu mà một người dùng có thể thực hiện trong một khoảng thời gian nhất định.
Đây là một ví dụ về cách tạo một middleware ghi log đơn giản:
// utils/middleware.js
export function withLogging(handler) {
return async function(req, res) {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`)
return handler(req, res)
}
}
Để sử dụng middleware này, chỉ cần bọc trình xử lý API route của bạn bằng hàm withLogging
:
// pages/api/logged.js
import { withLogging } from '../../utils/middleware'
async function handler(req, res) {
res.status(200).json({ message: 'Yêu cầu này đã được ghi log' })
}
export default withLogging(handler)
Các phương pháp hay nhất (Best Practices) khi xây dựng Next.js API Routes
- Giữ cho các API route của bạn nhỏ và tập trung. Mỗi API route nên xử lý một tác vụ hoặc tài nguyên cụ thể.
- Sử dụng biến môi trường cho dữ liệu nhạy cảm. Không bao giờ mã hóa cứng các khóa bí mật hoặc API keys trong mã của bạn.
- Xác thực dữ liệu yêu cầu để ngăn ngừa các lỗ hổng bảo mật. Sử dụng một thư viện như Joi hoặc Yup để xác thực request bodies.
- Xử lý lỗi một cách mượt mà và cung cấp thông báo lỗi đầy đủ thông tin. Sử dụng các khối try-catch và ghi log lỗi vào một vị trí trung tâm.
- Sử dụng bộ đệm (caching) để cải thiện hiệu suất. Lưu vào bộ đệm dữ liệu được truy cập thường xuyên để giảm tải cho cơ sở dữ liệu.
- Theo dõi các API route của bạn về hiệu suất và lỗi. Sử dụng một công cụ giám sát như Sentry hoặc Datadog để theo dõi tình trạng của API.
- Tài liệu hóa các API route của bạn bằng một công cụ như Swagger hoặc OpenAPI. Điều này giúp các nhà phát triển khác dễ dàng sử dụng API của bạn hơn.
- Cân nhắc sử dụng TypeScript để đảm bảo an toàn kiểu. TypeScript có thể giúp bạn phát hiện lỗi sớm và cải thiện khả năng bảo trì của mã.
- Nghĩ về quốc tế hóa (i18n) ngay từ đầu. Nếu ứng dụng của bạn sẽ được sử dụng bởi người dùng từ các quốc gia khác nhau, hãy thiết kế các API route để hỗ trợ nhiều ngôn ngữ và tiền tệ. Ví dụ, các điểm cuối API cho thương mại điện tử có thể cần xử lý các mức thuế và chi phí vận chuyển khác nhau dựa trên vị trí của người dùng.
- Triển khai cấu hình CORS (Cross-Origin Resource Sharing) phù hợp. Điều này rất quan trọng khi API của bạn được truy cập từ một tên miền khác với ứng dụng Next.js của bạn. Cấu hình CORS cẩn thận để chỉ cho phép các nguồn gốc được ủy quyền truy cập vào tài nguyên API của bạn.
Các kỹ thuật nâng cao
Tác vụ nền (Background Jobs)
Đối với các tác vụ chạy lâu mà không nên chặn phản hồi API, hãy cân nhắc sử dụng các tác vụ nền. Bạn có thể sử dụng các thư viện như BullMQ hoặc Bree để quản lý các tác vụ nền của mình và xử lý chúng một cách không đồng bộ.
WebSockets
Đối với các ứng dụng thời gian thực, bạn có thể sử dụng WebSockets trong các Next.js API route của mình. Các thư viện như Socket.IO và ws giúp dễ dàng thiết lập các kết nối bền vững giữa client và máy chủ.
GraphQL
Nếu bạn cần một cách linh hoạt và hiệu quả hơn để lấy dữ liệu, hãy cân nhắc sử dụng GraphQL. Bạn có thể sử dụng các thư viện như Apollo Server hoặc Yoga để tạo một điểm cuối API GraphQL trong ứng dụng Next.js của mình.
Kết luận
Next.js API Routes cung cấp một cách mạnh mẽ và thuận tiện để xây dựng các backend serverless ngay trong ứng dụng Next.js của bạn. Bằng cách tận dụng lợi ích của kiến trúc serverless, bạn có thể đơn giản hóa việc phát triển, cải thiện hiệu suất và giảm chi phí. Cho dù bạn đang xây dựng một biểu mẫu liên hệ đơn giản hay một nền tảng thương mại điện tử phức tạp, Next.js API Routes có thể giúp bạn tạo ra một backend mạnh mẽ và có khả năng mở rộng một cách dễ dàng. Với sự hiểu biết vững chắc về các nguyên tắc cơ bản và việc áp dụng các phương pháp hay nhất, bạn có thể tận dụng công cụ mạnh mẽ này để tạo ra các ứng dụng hiệu quả, an toàn và có thể truy cập toàn cầu.