Tiếng Việt

Tìm hiểu cách tạo các API endpoint mạnh mẽ bằng Next.js Route Handlers. 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, với các ví dụ thực tế và phương pháp hay nhất.

Next.js Route Handlers: Hướng dẫn Toàn diện về Cách Tạo API Endpoint

Next.js đã cách mạng hóa cách chúng ta xây dựng các ứng dụng web với các tính năng mạnh mẽ như kết xuất phía máy chủ (server-side rendering), tạo trang web tĩnh (static site generation), và bây giờ là Route Handlers. Route Handlers cung cấp một cách linh hoạt và hiệu quả để tạo các điểm cuối API trực tiếp trong ứng dụng Next.js của bạn. Hướng dẫn này khám phá khái niệm về Route Handlers, lợi ích của chúng, và cách sử dụng chúng một cách hiệu quả để xây dựng các API mạnh mẽ.

Next.js Route Handlers là gì?

Route Handlers là các hàm được định nghĩa trong thư mục app của một dự án Next.js để xử lý các yêu cầu HTTP đến. Không giống như phương pháp pages/api cũ hơn (sử dụng API Routes), Route Handlers cung cấp một cách tinh gọn và linh hoạt hơn để định nghĩa các điểm cuối API ngay bên cạnh các thành phần React của bạn. Về cơ bản, chúng là các hàm không máy chủ (serverless functions) được thực thi trên edge hoặc môi trường máy chủ bạn đã chọn.

Hãy coi Route Handlers như là logic backend của ứng dụng Next.js của bạn, chịu trách nhiệm xử lý các yêu cầu, tương tác với cơ sở dữ liệu và trả về các phản hồi.

Lợi ích của việc sử dụng Route Handlers

Thiết lập Dự án Next.js của bạn

Trước khi đi sâu vào Route Handlers, hãy đảm bảo bạn đã thiết lập một dự án Next.js với thư mục app. Nếu bạn đang bắt đầu một dự án mới, hãy sử dụng lệnh sau:

npx create-next-app@latest my-nextjs-app

Chọn thư mục app trong quá trình thiết lập để kích hoạt hệ thống định tuyến mới.

Tạo Route Handler đầu tiên của bạn

Hãy tạo một điểm cuối API đơn giản trả về một phản hồi JSON. Tạo một thư mục mới trong thư mục app, ví dụ: /app/api/hello. Bên trong thư mục này, tạo một tệp có tên route.ts (hoặc route.js nếu bạn không sử dụng TypeScript).

Đây là mã cho Route Handler đầu tiên của bạn:

// app/api/hello/route.ts
import { NextResponse } from 'next/server';

export async function GET(request: Request) {
 return NextResponse.json({ message: 'Xin chào từ Next.js Route Handlers!' });
}

Giải thích:

Bây giờ, bạn có thể truy cập điểm cuối này bằng cách điều hướng đến /api/hello trong trình duyệt của bạn hoặc sử dụng một công cụ như curl hoặc Postman.

Xử lý các Phương thức HTTP khác nhau

Route Handlers hỗ trợ các phương thức HTTP khác nhau như GET, POST, PUT, DELETE, PATCH, và OPTIONS. Bạn có thể định nghĩa các hàm riêng biệt cho mỗi phương thức trong cùng một tệp route.ts.

// app/api/users/route.ts
import { NextResponse } from 'next/server';

export async function GET(request: Request) {
 // Logic để lấy tất cả người dùng từ cơ sở dữ liệu
 const users = [{ id: 1, name: 'John Doe' }, { id: 2, name: 'Jane Smith' }]; // Dữ liệu ví dụ
 return NextResponse.json(users);
}

export async function POST(request: Request) {
 const data = await request.json(); // Phân tích nội dung request dưới dạng JSON
 // Logic để tạo người dùng mới trong cơ sở dữ liệu bằng 'data'
 const newUser = { id: 3, name: data.name, email: data.email }; // Ví dụ
 return NextResponse.json(newUser, { status: 201 }); // Trả về người dùng mới với mã trạng thái 201 Created
}

Giải thích:

Truy cập Dữ liệu Request

Đối tượng request cung cấp quyền truy cập vào các thông tin khác nhau về yêu cầu đến, bao gồm headers, tham số truy vấn và nội dung yêu cầu.

Headers

Bạn có thể truy cập các request header bằng thuộc tính request.headers:

export async function GET(request: Request) {
 const userAgent = request.headers.get('user-agent');
 console.log('User Agent:', userAgent);
 return NextResponse.json({ userAgent });
}

Tham số Truy vấn (Query Parameters)

Để truy cập các tham số truy vấn, bạn có thể sử dụng hàm khởi tạo URL:

export async function GET(request: Request) {
 const url = new URL(request.url);
 const searchParams = new URLSearchParams(url.search);
 const id = searchParams.get('id');
 console.log('ID:', id);
 return NextResponse.json({ id });
}

Nội dung Request (Request Body)

Đối với các yêu cầu POST, PUT, và PATCH, bạn có thể truy cập nội dung yêu cầu bằng các phương thức request.json() hoặc request.text(), tùy thuộc vào loại nội dung.

export async function POST(request: Request) {
 const data = await request.json();
 console.log('Dữ liệu:', data);
 return NextResponse.json({ receivedData: data });
}

Trả về Phản hồi (Response)

Đối tượng NextResponse được sử dụng để xây dựng các phản hồi API. Nó cung cấp một số phương thức để thiết lập headers, mã trạng thái và nội dung phản hồi.

Phản hồi JSON

Sử dụng phương thức NextResponse.json() để trả về các phản hồi JSON:

return NextResponse.json({ message: 'Thành công!', data: { name: 'John Doe' } }, { status: 200 });

Phản hồi dạng Văn bản (Text)

Sử dụng hàm khởi tạo new Response() để trả về các phản hồi văn bản thuần túy:

return new Response('Xin chào, thế giới!', { status: 200, headers: { 'Content-Type': 'text/plain' } });

Chuyển hướng (Redirects)

Sử dụng NextResponse.redirect() để chuyển hướng người dùng đến một URL khác:

import { redirect } from 'next/navigation';
import { NextResponse } from 'next/server';

export async function GET(request: Request) {
 return NextResponse.redirect(new URL('/new-location', request.url));
}

Thiết lập Headers

Bạn có thể thiết lập các header tùy chỉnh bằng tùy chọn headers trong NextResponse.json() hoặc new Response():

return NextResponse.json({ message: 'Thành công!' }, { status: 200, headers: { 'Cache-Control': 'no-cache' } });

Tích hợp Middleware

Middleware cho phép bạn chạy mã trước khi một yêu cầu được xử lý bởi Route Handler của bạn. Điều này hữu ích cho việc xác thực, phân quyền, ghi nhật ký và các mối quan tâm xuyên suốt khác.

Để tạo middleware, hãy tạo một tệp có tên middleware.ts (hoặc middleware.js) trong thư mục app hoặc bất kỳ thư mục con nào. Middleware sẽ áp dụng cho tất cả các route trong thư mục đó và các thư mục con của nó.

// app/middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
 const token = request.cookies.get('auth-token');

 if (!token) {
 return NextResponse.redirect(new URL('/login', request.url));
 }

 return NextResponse.next();
}

export const config = {
 matcher: ['/protected/:path*'], // Áp dụng middleware này cho các đường dẫn bắt đầu bằng /protected/
};

Giải thích:

Xử lý Lỗi

Xử lý lỗi đúng cách là rất quan trọng để xây dựng các API mạnh mẽ. Bạn có thể sử dụng các khối try...catch để xử lý các ngoại lệ và trả về các phản hồi lỗi phù hợp.

export async function GET(request: Request) {
 try {
 // Mô phỏng một lỗi
 throw new Error('Đã xảy ra lỗi!');
 } catch (error: any) {
 console.error('Lỗi:', error);
 return NextResponse.json({ error: error.message }, { status: 500 });
 }
}

Giải thích:

Phản hồi Truyền phát (Streaming)

Route Handlers hỗ trợ các phản hồi truyền phát, cho phép bạn gửi dữ liệu tăng dần đến máy khách. Điều này đặc biệt hữu ích cho các tập dữ liệu lớn hoặc các quy trình chạy dài.

import { Readable } from 'stream';
import { NextResponse } from 'next/server';

async function* generateData() {
 for (let i = 0; i < 10; i++) {
 await new Promise(resolve => setTimeout(resolve, 500)); // Mô phỏng độ trễ
 yield `Mảnh dữ liệu ${i}\n`;
 }
}

export async function GET(request: Request) {
 const readableStream = Readable.from(generateData());

 return new Response(readableStream, {
 headers: { 'Content-Type': 'text/plain; charset=utf-8' },
 });
}

Giải thích:

Xác thực và Phân quyền

Bảo mật các điểm cuối API của bạn là rất quan trọng. Bạn có thể triển khai xác thực và phân quyền bằng middleware hoặc trực tiếp trong Route Handlers của mình.

Xác thực (Authentication)

Xác thực xác minh danh tính của người dùng thực hiện yêu cầu. Các phương pháp xác thực phổ biến bao gồm:

Đây là một ví dụ về xác thực JWT bằng middleware:

// app/middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import jwt from 'jsonwebtoken';

const secret = process.env.JWT_SECRET || 'your-secret-key'; // Thay thế bằng một chuỗi bí mật mạnh, được tạo ngẫu nhiên

export function middleware(request: NextRequest) {
 const token = request.cookies.get('auth-token')?.value;

 if (!token) {
 return NextResponse.json({ message: 'Yêu cầu xác thực' }, { status: 401 });
 }

 try {
 jwt.verify(token, secret);
 return NextResponse.next();
 } catch (error) {
 return NextResponse.json({ message: 'Token không hợp lệ' }, { status: 401 });
 }
}

export const config = {
 matcher: ['/api/protected/:path*'],
};

Phân quyền (Authorization)

Phân quyền xác định những tài nguyên mà một người dùng được phép truy cập. Điều này thường dựa trên vai trò hoặc quyền hạn.

Bạn có thể triển khai phân quyền trong Route Handlers của mình bằng cách kiểm tra vai trò hoặc quyền hạn của người dùng và trả về lỗi nếu họ không có quyền truy cập.

// app/api/admin/route.ts
import { NextResponse } from 'next/server';

export async function GET(request: Request) {
 // Giả sử bạn có một hàm để lấy vai trò của người dùng từ token hoặc session
 const userRole = await getUserRole(request);

 if (userRole !== 'admin') {
 return NextResponse.json({ message: 'Không được phép' }, { status: 403 });
 }

 // Logic để lấy dữ liệu quản trị
 const adminData = { message: 'Dữ liệu quản trị' };
 return NextResponse.json(adminData);
}

async function getUserRole(request: Request): Promise {
 // Thay thế bằng logic thực tế của bạn để trích xuất vai trò của người dùng từ request
 // Điều này có thể bao gồm việc xác minh token JWT hoặc kiểm tra session
 return 'admin'; // Ví dụ: vai trò được gán cứng để minh họa
}

Triển khai Route Handlers

Route Handlers được triển khai dưới dạng các hàm không máy chủ (serverless functions) trên nhà cung cấp dịch vụ lưu trữ bạn đã chọn. Next.js hỗ trợ các nền tảng triển khai khác nhau, bao gồm Vercel, Netlify, AWS, và nhiều hơn nữa.

Đối với Vercel, việc triển khai đơn giản chỉ là kết nối kho lưu trữ Git của bạn với Vercel và đẩy mã nguồn của bạn lên. Vercel tự động phát hiện dự án Next.js của bạn và triển khai Route Handlers của bạn dưới dạng các hàm không máy chủ.

Các Kỹ thuật Nâng cao

Edge Functions

Route Handlers có thể được triển khai dưới dạng Edge Functions, được thực thi trên biên của một CDN, gần hơn với người dùng của bạn. Điều này có thể giảm đáng kể độ trễ và cải thiện hiệu suất.

Để triển khai một Route Handler dưới dạng Edge Function, hãy thêm runtime edge vào tệp route.ts của bạn:

export const runtime = 'edge';

import { NextResponse } from 'next/server';

export async function GET(request: Request) {
 return NextResponse.json({ message: 'Xin chào từ Edge!' });
}

Server Actions

Server Actions cho phép bạn thực thi mã phía máy chủ trực tiếp từ các thành phần React của bạn. Route Handlers và Server Actions hoạt động liền mạch với nhau, cho phép bạn xây dựng các ứng dụng phức tạp một cách dễ dàng.

Đây là một ví dụ về việc sử dụng Server Action để gọi một Route Handler:

// app/components/MyComponent.tsx
'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';

async function handleSubmit(data: FormData) {
 'use server';

 const name = data.get('name');
 const email = data.get('email');

 const response = await fetch('/api/users', {
 method: 'POST',
 body: JSON.stringify({ name, email }),
 });

 if (response.ok) {
 router.refresh(); // Tải lại trang để phản ánh các thay đổi
 }
}

export default function MyComponent() {
 const router = useRouter();

 return (
 




); }

Caching (Lưu vào bộ nhớ đệm)

Caching có thể cải thiện đáng kể hiệu suất của các điểm cuối API của bạn. Bạn có thể sử dụng header Cache-Control để kiểm soát cách các phản hồi của bạn được lưu vào bộ nhớ đệm bởi các trình duyệt và CDN.

return NextResponse.json({ message: 'Thành công!' }, { status: 200, headers: { 'Cache-Control': 'public, max-age=3600' } });

Ví dụ này đặt header Cache-Control thành public, max-age=3600, điều này cho các trình duyệt và CDN biết để lưu phản hồi vào bộ nhớ đệm trong một giờ.

Các Phương pháp Tốt nhất (Best Practices)

Ví dụ trong Thực tế

Dưới đây là một vài ví dụ thực tế về cách có thể sử dụng Route Handlers:

Ví dụ Thương mại điện tử quốc tế: Một Route Handler được sử dụng để lấy giá sản phẩm dựa trên quốc gia của người dùng. Điểm cuối có thể sử dụng vị trí địa lý của yêu cầu (lấy từ địa chỉ IP) để xác định vị trí của người dùng và trả về giá bằng đơn vị tiền tệ phù hợp. Điều này góp phần tạo ra trải nghiệm mua sắm được bản địa hóa.

Ví dụ Xác thực toàn cầu: Một Route Handler triển khai xác thực đa yếu tố (MFA) cho người dùng trên toàn thế giới. Điều này có thể bao gồm việc gửi mã SMS hoặc sử dụng các ứng dụng xác thực, đồng thời tôn trọng các quy định về quyền riêng tư và cơ sở hạ tầng viễn thông của các khu vực khác nhau.

Phân phối nội dung đa ngôn ngữ: Một Route Handler cung cấp nội dung bằng ngôn ngữ ưa thích của người dùng. Điều này có thể được xác định từ header `Accept-Language` trong yêu cầu. Ví dụ này nhấn mạnh sự cần thiết của việc mã hóa UTF-8 đúng cách và hỗ trợ ngôn ngữ từ phải sang trái khi thích hợp.

Kết luận

Next.js Route Handlers cung cấp một cách mạnh mẽ và linh hoạt để tạo các điểm cuối API trực tiếp trong ứng dụng Next.js của bạn. Bằng cách tận dụng Route Handlers, bạn có thể xây dựng các API mạnh mẽ một cách dễ dàng, đồng vị trí logic backend với các thành phần React của bạn, và tận dụng các tính năng như middleware, streaming, và Edge Functions.

Hướng dẫn toàn diệ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. Bằng cách tuân theo các phương pháp tốt nhất được nêu trong hướng dẫn này, bạn có thể xây dựng các API chất lượng cao, an toàn, hiệu quả và có thể bảo trì.

Next.js Route Handlers: Hướng dẫn Toàn diện về Cách Tạo API Endpoint | MLOG