- vừa được xem lúc

Có thể bạn chưa biết cách viết API như thế này với Express

0 0 3

Người đăng: Vũ Tuấn

Theo Viblo Asia

Khi tôi lần đầu "đào sâu" vào mã nguồn của n8n trong quá trình làm việc với LiveAPI, tôi đã kỳ vọng một cấu trúc Express quen thuộc:

  • Một file routes chính import các module khác,
  • Mỗi route sẽ gọi đến một controller,
  • Controller sẽ đi qua một trình xác thực (validator),
  • Và cuối cùng đến service để xử lý logic.

Đây là cách thiết lập tiêu chuẩn trong hầu hết các backend dùng Node.js – như trong các dự án FlowiseAI hay NodeBB.

Nhưng còn n8n? Nó hoàn toàn đảo ngược kỳ vọng của tôi.

Thay vì luồng route → controller → service thông thường, n8n sử dụng cách tiếp cận đậm chất TypeScript, dựa vào decorator – khiến nó trông giống NestJS hơn là Express truyền thống.

Đây thực sự là một bất ngờ thú vị và tôi đã nghĩ ngay: “Phải chia sẻ điều này thôi.”

File Server.ts: Một thiết lập Express tùy chỉnh

Trái tim của backend trong n8n nằm ở class Server, mở rộng từ AbstractServer, thiết lập một API dựa trên Express.

Nhưng không chỉ là một app Express đơn giản với vài route:

  • Tải controller động: Dựa vào cấu hình môi trường, các controller cho xác thực (LDAP, SAML), quản lý mã nguồn, xác thực đa yếu tố (MFA), và nhiều hơn nữa được nạp khi runtime.
  • Xử lý webhook & sự kiện thời gian thực: WebSocket, giám sát bằng Prometheus, và bộ nhớ đệm được tích hợp.
  • Sử dụng middleware thông minh: Helmet, CSP, phân tích cookie, và giới hạn tốc độ đều được cấu hình động.
  • Phục vụ cả SPA và tài nguyên frontend: Route API và frontend cùng tồn tại.

Decorator – Cách định nghĩa API mới mẻ

n8n loại bỏ thư mục routes truyền thống và thay bằng các decorator TypeScript để định nghĩa endpoint API.

Đây chính là lúc bạn bắt đầu cảm thấy nó rất giống với NestJS.

Hãy xem ví dụ trong route.ts:

Không còn route file truyền thống – Chỉ dùng Decorator

Các decorator định nghĩa phương thức API một cách động:

import type { RequestHandler } from 'express'; import { getRouteMetadata } from './controller.registry';
import type { Controller, Method, RateLimit } from './types'; const RouteFactory = (method: Method) => (path: `/${string}`, options: RouteOptions = {}): MethodDecorator => (target, handlerName) => { const routeMetadata = getRouteMetadata(target.constructor as Controller, String(handlerName)); routeMetadata.method = method; routeMetadata.path = path; routeMetadata.middlewares = options.middlewares ?? []; }; export const Get = RouteFactory('get');
export const Post = RouteFactory('post');
export const Put = RouteFactory('put');
export const Patch = RouteFactory('patch');
export const Delete = RouteFactory('delete');

Cách tiếp cận này giúp:

  • Không cần khai báo route thủ công,
  • Mỗi method trong controller chỉ cần gắn @Get(), @Post(), v.v.,
  • Và n8n sẽ xử lý routing phía sau hậu trường.

Phép màu của @RestController

Thay vì phải tự định nghĩa router trong Express theo cách thủ công, decorator @RestController sẽ tự động đăng ký toàn bộ controller:

import { Service } from '@n8n/di'; import { getControllerMetadata } from './controller.registry';
import type { Controller } from './types'; export const RestController = (basePath: `/${string}` = '/'): ClassDecorator => (target) => { const metadata = getControllerMetadata(target as unknown as Controller); metadata.basePath = basePath; return Service()(target); };

Decorator này tự động đăng ký controller và ánh xạ các route tương ứng với các phương thức bên trong class.

Ví dụ: Active Workflows API

Controller ActiveWorkflowsController (nguồn code) dùng để xử lý các API liên quan đến workflow đang hoạt động:

import { Get, RestController } from '@/decorators';
import { ActiveWorkflowRequest } from '@/requests';
import { ActiveWorkflowsService } from '@/services/active-workflows.service'; @RestController('/active-workflows')
export class ActiveWorkflowsController { constructor(private readonly activeWorkflowsService: ActiveWorkflowsService) {} @Get('/') async getActiveWorkflows(req: ActiveWorkflowRequest.GetAllActive) { return await this.activeWorkflowsService.getAllActiveIdsFor(req.user); } @Get('/error/:id') async getActivationError(req: ActiveWorkflowRequest.GetActivationError) { const { user, params: { id: workflowId } } = req; return await this.activeWorkflowsService.getActivationError(workflowId, user); }
}

Mỗi phương thức API chỉ là một hàm bên trong class, và decorators lo hết phần còn lại – từ ánh xạ route, xác thực, kiểm tra quyền, cho đến xác thực request.

Tầng Service: Dependency Injection & truy cập cơ sở dữ liệu

n8n sử dụng dependency injection (thông qua @n8n/di) để quản lý các service.

Ví dụ, ActiveWorkflowsService xử lý các thao tác liên quan đến workflow:

@Service()
export class ActiveWorkflowsService { constructor( private readonly logger: Logger, private readonly workflowRepository: WorkflowRepository, private readonly sharedWorkflowRepository: SharedWorkflowRepository, private readonly activationErrorsService: ActivationErrorsService, ) {} async getAllActiveIdsFor(user: User) { const activationErrors = await this.activationErrorsService.getAll(); const activeWorkflowIds = await this.workflowRepository.getActiveIds(); const hasFullAccess = user.hasGlobalScope('workflow:list'); if (hasFullAccess) { return activeWorkflowIds.filter((workflowId) => !activationErrors[workflowId]); } const sharedWorkflowIds = await this.sharedWorkflowRepository.getSharedWorkflowIds(activeWorkflowIds); return sharedWorkflowIds.filter((workflowId) => !activationErrors[workflowId]); }
}

Lợi ích:

  • Dễ dàng thay thế các thành phần mà không ảnh hưởng đến phần còn lại của hệ thống.
  • Dễ test: Có thể mock các dependency trong các unit test.
  • Code trở nên module hóa và dễ bảo trì hơn rất nhiều.

Kết luận

Nếu bạn xuất phát từ nền tảng Express.js truyền thống, cách tổ chức API của n8n có thể khiến bạn cảm thấy lạ lẫm lúc đầu.

Nhưng một khi bạn vượt qua cảm giác bối rối kiểu "các route đâu hết rồi?", và hiểu được rằng các decorator xử lý routing, xác thực, và middleware như thế nào, bạn sẽ thấy nó thực sự hợp lý.

Thông qua việc sử dụng decorator và dependency injection, n8n đã làm được điều mà nhiều hệ thống lớn mong muốn:

  • API được module hóa,
  • Dễ bảo trì,
  • Và rất dễ mở rộng.

Vậy nên, lần tới khi bạn xây dựng một backend với Express, hãy thử tích hợp một vài decorator xem sao. Biết đâu bạn lại thích cách tiếp cận này đấy!

Nếu bạn đang phát triển một ứng dụng phụ thuộc nhiều vào API, thì cấu trúc này rất đáng để đưa vào danh sách lựa chọn của bạn.

Bình luận

Bài viết tương tự

- vừa được xem lúc

Flutter - GetX - Using GetConnect to handle API request (Part 4)

Giới thiệu. Xin chào các bạn, lại là mình với series về GetX và Flutter.

0 0 366

- vừa được xem lúc

API vs WebSockets vs WebHooks: What to Choose?

. Khi xây dựng bất kì một ứng dụng nào, chúng ta đều cần phải có một cơ chế đáng tin cậy để giao tiếp giữa các thành phần của nó. Đây là khi APIs, WebSockets và WebHooks được ứng dụng vào.

0 0 105

- vừa được xem lúc

Sử dụng Fast JSON API serialization trong Ruby on Rails

Ở bài viết này chúng ta sẽ thử tạo 1 project API sử dụng gem fast_jsonapi cho serializer. Đầu tiên là tạo một project API mới. $ rails new rails-jsonapi --database=postgresql --skip-action-mailbox --skip-action-text --skip-spring -T --skip-turbolinks --api. .

0 0 140

- vừa được xem lúc

Test thử ba loại API chụp màn hình Windows

Hiện tại, Windows cung cấp khoảng ba cách để chụp màn hình. Thế thì cái nào là nhanh nhất? Tôi muốn test thử từng cái.

0 0 74

- vừa được xem lúc

Ngừng sử dụng REST cho API — GraphQL là cách tốt hơn

Mở đầu. REST đã được nhiều developers sử dụng để gửi dữ liệu qua HTTP trong khi GraphQL thường được trình bày như một công nghệ thay thế các API REST.

0 0 107

- vừa được xem lúc

Quản lý và sử dụng API trong Nuxt bằng cách sử dụng Repository Pattern

Mở đầu năm mới, à nhầm, mở đầu bài viết. Cái tên NuxtJS chắc hẳn cũng không còn xa lạ gì với những bạn yêu thích VueJS nữa, đương nhiên mình cũng là một chàng trai dành tình yêu to lớn cho frameworks này.

0 0 234