Tiếng Việt

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

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ố:

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:

Đâ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_URIMONGODB_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:

Đâ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ư:

Đâ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

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.