Kiến trúc Serverless với Node.js và TypeScript trên Vercel
Trong bối cảnh phát triển web hiện đại, việc tối ưu hóa quy trình triển khai (deployment) và mở rộng hệ thống (scaling) là yếu tố then chốt quyết định thành công của một dự án. Một trong những xu hướng đang thống trị lĩnh vực này là kiến trúc Serverless. Bài viết này sẽ đi sâu vào việc xây dựng một API Backend hiệu quả, an toàn và dễ bảo trì bằng sự kết hợp giữa TypeScript, Node.js và nền tảng hosting Vercel, đồng thời hướng dẫn cách tích hợp mượt mà với các frontend React hoặc Vue.
Khác với các mô hình VPS truyền thống yêu cầu quản lý server 24/7, mô hình Serverless cho phép chúng ta chỉ viết logic nghiệp vụ dưới dạng các hàm (Functions). Nhà cung cấp dịch vụ sẽ tự động chịu trách nhiệm quản lý cơ sở hạ tầng, phân bổ tài nguyên và mở rộng quy mô theo lượng truy cập thực tế. TypeScript đóng vai trò như lớp bảo vệ đầu tiên, giúp phát hiện lỗi ngay từ khi viết code (compile time) thay vì phải đợi đến khi chạy trên server, điều này cực kỳ quan trọng trong môi trường serverless nơi việc debug có thể phức tạp hơn.
Cấu trúc dự án và môi trường phát triển
Để bắt đầu, chúng ta cần thiết lập một dự án TypeScript chuẩn với Node.js. Thay vì tạo nhiều thư mục rời rạc, chúng ta sẽ sử dụng một cấu trúc đơn giản nhưng mô-đun hóa rõ ràng. Trước tiên, hãy khởi tạo một thư mục mới và cài đặt các gói cần thiết. Bạn không cần cài đặt framework server nặng nề như Express hay Koa cho Serverless, vì các nền tảng như Vercel cung cấp runtime sẵn có.
Bước đầu tiên là cài đặt các phụ thuộc cốt lõi. Chúng ta cần typescript để biên dịch, @types/node cho các định nghĩa kiểu của Node.js, và tsx để chạy code TypeScript trực tiếp mà không cần biên dịch thủ công trong quá trình phát triển.
npm init -y
npm install typescript tsx @types/node
npx tsc --init
Sau khi khởi tạo, chúng ta cần cấu hình file tsconfig.json phù hợp với môi trường serverless. Điểm mấu chốt ở đây là target nên đặt ở ESNext hoặc ES2022 để tận dụng các tính năng mới của JavaScript, và module nên là ESNext hoặc NodeNext tùy thuộc vào cách nhà cung cấp dịch vụ đóng gói. Tuy nhiên, với Vercel, chúng ta có thể để mặc định hoặc sử dụng CommonJS nếu cần tương thích ngược, nhưng ESM là xu hướng hiện đại hơn. Trong file tsconfig.json, hãy chú ý đến phần outDir để biên dịch ra thư mục dist.
Để đảm bảo code của chúng ta tuân thủ các quy chuẩn nghiêm ngặt, hãy thêm ESLint với các plugin TypeScript. Điều này giúp chuẩn hóa code ngay từ đầu, tránh các lỗi nhỏ như thiếu return hay kiểu dữ liệu không xác định, vốn là nguyên nhân gây lỗi runtime khó tìm trong môi trường serverless.
Viết API Serverless với Node.js và TypeScript
Trong kiến trúc Serverless trên Vercel, mỗi endpoint API tương ứng với một hàm xử lý (Handler). Điểm khác biệt lớn so với server truyền thống là hàm này sẽ được kích hoạt bởi một event từ HTTP request và trả về một response, sau đó môi trường sẽ bị đóng lại để tiết kiệm tài nguyên. Vì vậy, code bên trong hàm phải thực thi nhanh, không giữ kết nối dài hạn và không chứa trạng thái (stateless).
Chúng ta sẽ tạo một file api/hello.ts để xử lý endpoint /api/hello. Hàm này nhận đối tượng Request và trả về đối tượng Response. Để làm việc với JSON trong TypeScript một cách an toàn, chúng ta nên định nghĩa rõ ràng interface cho request body và query parameters.
Đoạn code dưới đây minh họa cách định nghĩa một handler chuẩn, bao gồm việc xác thực request và xử lý lỗi.
import { Handler } from "aws-lambda";
import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda";
export type HelloRequest = {
name: string;
version?: string;
};
export type HelloResponse = {
message: string;
timestamp: number;
};
export const handler: Handler = async (
event: APIGatewayProxyEvent
): Promise => {
// Xử lý CORS để frontend có thể gọi API
const headers = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "Content-Type",
};
if (event.httpMethod === "OPTIONS") {
return {
statusCode: 200,
headers,
body: "",
};
}
try {
const body = JSON.parse(event.body || "{}") as HelloRequest;
const name = body.name || event.queryStringParameters?.name || "Anonymous";
const response: HelloResponse = {
message: `Hello, ${name}!`,
timestamp: Date.now(),
};
return {
statusCode: 200,
headers,
body: JSON.stringify(response),
};
} catch (error) {
console.error("API Error:", error);
return {
statusCode: 500,
headers,
body: JSON.stringify({ error: "Internal Server Error" }),
};
}
};
Trong ví dụ trên, chúng ta sử dụng các interface APIGatewayProxyEvent và APIGatewayProxyResult vì Vercel sử dụng runtime tương thích với AWS Lambda. Việc này giúp TypeScript hiểu rõ cấu trúc của dữ liệu đầu vào và đầu ra, từ đó gợi ý (autocomplete) chính xác. Lưu ý rằng trong môi trường Vercel, chúng ta cũng có thể sử dụng các handler đơn giản hơn dựa trên Next.js API Routes nếu đang dùng Next.js, nhưng với dự án Node.js thuần, cấu hình qua vercel.json sẽ linh hoạt hơn.
Để cấu hình Vercel nhận diện các hàm này, chúng ta cần tạo file vercel.json ở root của dự án. File này ánh xạ đường dẫn URL sang file chứa code handler.
{
"version": 2,
"builds": [
{
"src": "api/*.ts",
"use": "@vercel/node"
}
],
"routes": [
{
"src": "/api/(.*)",
"dest": "/api/$1"
}
]
}
Cấu hình trên thông báo cho Vercel rằng tất cả các file TypeScript trong thư mục api sẽ được xử lý bởi runtime @vercel/node, và bất kỳ request nào bắt đầu bằng /api/ đều sẽ được chuyển hướng đến thư mục đó.
Tối ưu hiệu năng và tích hợp Frontend
Khi tích hợp với React hoặc Vue, yếu tố quan trọng nhất là quản lý trạng thái (state management) và xử lý các request không đồng bộ. Vì serverless có thời gian khởi động (cold start), việc thiết kế API phải đảm bảo phản hồi nhanh trong vòng vài trăm mili giây. TypeScript giúp đảm bảo tính an toàn kiểu dữ liệu giữa frontend và backend. Chúng ta nên trích xuất các loại dữ liệu (types) từ file backend và chia sẻ chung sang frontend thông qua một thư viện gói gói (package) nội bộ hoặc file shared-types.ts.
Trong React, khi gọi API này, bạn có thể sử dụng fetch hoặc axios. Tuy nhiên, để tận dụng tối đa TypeScript, hãy đảm bảo rằng các biến response được gán kiểu chính xác dựa trên interface HelloResponse đã định nghĩa ở backend. Điều này ngăn chặn việc frontend cố gắng truy cập các trường dữ liệu không tồn tại, một lỗi phổ biến gây ra crash ứng dụng.
Về hiệu năng, hãy nhớ rằng các hàm Serverless có giới hạn về thời gian thực thi (thường là 10-15 phút tùy gói). Do đó, các tác vụ nặng như xử lý video, render PDF hoặc tính toán phức tạp nên được chuyển sang các worker queue (như BullMQ với Redis) hoặc các dịch vụ chuyên biệt, thay vì làm trực tiếp trong handler HTTP. Trong handler, bạn chỉ nên xử lý logic nghiệp vụ nhẹ, xác thực user và gọi các dịch vụ khác, sau đó trả về kết quả hoặc một token để frontend follow sau.
Để triển khai dự án lên Vercel, bạn chỉ cần cài đặt CLI tool của họ và thực thi lệnh deploy. Quá trình này sẽ tự động nhận diện file vercel.json, biên dịch TypeScript và upload các hàm lên cloud.
npm i -g vercel
vercel
vercel --prod
Trong quá trình deploy, Vercel sẽ tự động tạo các build artifacts và map routes như đã cấu hình. Bạn không cần can thiệp vào server hay thiết lập database (trừ khi bạn tự kết nối). Sau khi deploy thành công, bạn sẽ nhận được một URL sản phẩm để gọi API.
Tóm lại, việc kết hợp TypeScript, Node.js và Vercel tạo nên một mô hình phát triển hiện đại, nơi sự an toàn kiểu dữ liệu được đảm bảo tuyệt đối, chi phí vận hành được tối ưu theo nhu cầu thực tế và quy trình triển khai trở nên đơn giản hóa đến mức tối đa. Đây là nền tảng lý tưởng cho các dự án cần mở rộng nhanh chóng mà không phải lo lắng về cơ sở hạ tầng phần cứng, đồng thời duy trì được độ tin cậy cao của mã nguồn nhờ vào sự chặt chẽ của TypeScript.