Chuyển đến nội dung chính (Skip to content)
Tất cả dự án
FeaturedNext.jsReactTypeScriptAdonisJSLucid ORMMySQLGoGin

Knowledge-Base: kiến trúc Next.js, AdonisJS và MySQL

Ảnh dự án Knowledge-Base: kiến trúc Next.js, AdonisJS và MySQL

Phân tích kiến trúc Knowledge-Base: Next.js, AdonisJS, Lucid, MySQL và Go CDN; mô hình nội dung, middleware, truy vấn quan hệ và cache khi xuất bản.

11 phút đọc · Case study dự án

Knowledge-Base là hệ thống xuất bản nội dung gồm bốn ứng dụng: website đọc tài liệu, CMS, API AdonisJS và media service Go. Bài này đi từ cấu trúc source đến cách một bản ghi nội dung được kiểm tra, lưu trong database, trả qua API và hiển thị trên Next.js. Điểm cần phân biệt ngay từ đầu: lớp dữ liệu dùng Lucid với driver mysql2; database đang vận hành được đối chiếu là MySQL 8. MariaDB thuộc nhóm tương thích driver nhưng không phải engine đang được xác nhận cho ứng dụng này.

Kiến trúc bốn ứng dụng và ranh giới trách nhiệm

Các thư mục fe, cms, api và cdn đại diện cho bốn phần có vòng đời build riêng. FE tập trung vào route công khai, metadata và việc đọc; CMS cung cấp biểu mẫu biên tập. API giữ quy tắc dữ liệu cùng quyền thao tác. CDN giải quyết đường dẫn tệp, biến thể kích thước và cache ảnh. Đây là sự phân tách ứng dụng theo trách nhiệm, không có cơ sở để gọi mọi phần là một microservice nghiệp vụ độc lập.

Thành phầnCông nghệDữ liệu và trách nhiệm
WebsiteNext.js App Router, React, TypeScriptDanh sách, bài chi tiết, dự án, công cụ và metadata
CMSNext.js, ReactForm nhập liệu, trạng thái nội dung và quản trị media
APIAdonisJS, VineJS, LucidHTTP contract, validation, authentication, ACL và truy vấn SQL
DatabaseMySQL, mysql2Posts, categories, tags, series, projects, users và quyền
MediaGo, GinĐọc ảnh gốc, resize, cache biến thể và phân phối tệp
CMS → AdonisJS API → Lucid / mysql2 → MySQL
FE  → public API → JSON nội dung → HTML / React
FE và nội dung HTML → Go CDN → ảnh gốc hoặc biến thể cache

Giữ media ngoài database giúp payload bài viết chỉ chứa URL và metadata, thay vì nhúng binary ảnh vào hàng SQL. Đổi lại, bản ghi nội dung và tệp nằm ở hai nơi: việc backup database riêng lẻ chưa đủ để phục hồi ảnh. Một lần sửa tên file hoặc chuẩn URL cũng có thể làm hỏng nội dung đã xuất bản dù database vẫn truy vấn bình thường.

Tổ chức source AdonisJS và hợp đồng API

api/
  start/routes/       # public, admin và automation routes
  app/controllers/    # HTTP actions
  app/validators/     # schema đầu vào
  app/middleware/     # auth, ACL, API token
  app/models/         # Lucid models và quan hệ
  app/services/       # media, sitemap và tích hợp
  database/migrations/
fe/src/app/           # App Router
fe/src/lib/           # API client, metadata và content helpers
cdn/handlers/        # xử lý media

Route được chia theo tài nguyên thay vì gom toàn bộ handler vào một file lớn. Ví dụ, projects có nhóm public để đọc và nhóm admin đặt sau authentication cùng quyền projects.manage. Controller nhận HTTP context, validate payload, gọi model hoặc service và trả JSON. Vai trò controller phù hợp với cách tổ chức action trong tài liệu AdonisJS; các service cụ thể vẫn được xác định từ source của dự án.

Validation và authorization là hai lớp khác nhau. Slug hợp lệ hoặc title đủ ký tự chỉ chứng minh request đúng hình dạng; middleware vẫn phải xác định ai được phép sửa nội dung. Tương tự, type TypeScript ở FE không thay thế runtime validation, vì request có thể được gửi trực tiếp mà không đi qua form CMS.

Lucid, quan hệ nội dung và truy vấn danh sách

Model Post có các trường nội dung và SEO như title, slug, content, excerpt, metaTitle, metaDescription, canonicalUrl, coverImage, status và publishedAt. Quan hệ belongsTo liên kết bài với category, series; manyToMany liên kết tags. Mô hình này cho phép một bài có nhiều tag trong khi danh mục chính vẫn là một quan hệ riêng, tránh việc trộn tag và category vào cùng một chuỗi không có ràng buộc.

Controller danh sách dùng preload category, tags và series. Eager loading ở đây nhằm lấy các quan hệ cần cho thẻ bài theo nhóm, tránh để giao diện hoặc mỗi bản ghi tự phát thêm request riêng. Bộ lọc category, tag, trạng thái và soft delete đi vào query trước khi phân trang. Thứ tự cũ đến mới có các khóa phụ createdAt và id để giảm việc các hàng cùng ngày bị đảo thứ tự không ổn định.

Projects là tài nguyên riêng với title, slug, description, content, techStack và thumbnailUrl. Cột techStack được model chuẩn bị thành JSON và chuyển lại thành array khi đọc. Vì project không có đầy đủ các cột SEO như Post, tiêu đề và description của project được dùng trực tiếp cho metadata trang chi tiết. Thiết kế schema phải được đọc theo từng model, không suy từ form bài viết sang mọi tài nguyên.

// Rút gọn từ cách truy vấn trong controller, không phải benchmark
Post.query()
  .preload("category")
  .preload("tags")
  .preload("series")
  .whereNull("deleted_at")
  .orderBy("publishedAt", "desc")

Luồng ghi nội dung và giới hạn transaction

Ở action tạo bài, payload đi qua validator, đường dẫn cover được normalize, các trường được fill vào model rồi save. Sau đó tags được attach qua quan hệ và controller load lại dữ liệu liên quan để trả response. Những bước này giải thích vì sao chỉ kiểm tra response title là chưa đủ: quan hệ tags, ngày xuất bản và URL media cũng thuộc kết quả nghiệp vụ.

Trong action đã đối chiếu, save bài và attach tags là các thao tác nối tiếp; đoạn code này chưa được bao trong một managed transaction chung. Do đó không nên mô tả hệ thống như đã bảo đảm atomicity cho toàn bộ thao tác biên tập. Nếu bước gắn tag thất bại sau khi bài đã lưu, phần ghi trước đó có thể vẫn tồn tại. Đây là giới hạn triển khai cụ thể, không phải hạn chế bắt buộc của Lucid.

Khi cần củng cố luồng này, cùng một transaction client phải được dùng cho cả model và thao tác quan hệ. Lucid transactions cung cấp cơ chế đó; việc tích hợp vào action là một thay đổi code riêng, không được coi là đã hoàn thành chỉ vì dependency có hỗ trợ. Các model còn có hook sau save/delete để yêu cầu xóa cache sitemap, cho thấy ghi nội dung có tác động đến lớp đọc bên ngoài database.

Authentication, ACL và token automation

Luồng CMS dùng session/authentication của ứng dụng và middleware ACL cho tài nguyên quản trị. Luồng automation dùng middleware API token riêng: đọc token từ header được hỗ trợ, hash token để tra cứu bản ghi, loại token đã xóa hoặc hết hạn, sau đó kiểm tra permission cần thiết của route. Việc lưu hash giảm nhu cầu giữ plaintext token trong bảng tra cứu; nó không thay thế TLS hoặc giới hạn scope.

Phân quyền cần được xét ở backend. Ẩn nút chỉnh sửa tại FE chỉ là trải nghiệm giao diện; một request trực tiếp vẫn phải bị chặn nếu thiếu quyền. Cũng không nên giả định token đọc/ghi posts tự động có quyền ghi projects. Tập route và scope hiện hữu quyết định khả năng automation, trong khi cookie quản trị là một cơ chế truy cập khác.

Một điểm cần rà soát độc lập là bộ lọc trạng thái của từng public endpoint. Việc CMS gọi một bản ghi là draft chưa đủ để kết luận public API không trả bản ghi đó. Chính sách chỉ xuất bản phải được thực thi trong query public tương ứng, không dựa vào việc giao diện đang giấu liên kết.

Next.js, cache nội dung và biến thể ảnh Go CDN

FE dùng helper API để chuyển JSON thành dữ liệu cho route danh sách và chi tiết. Metadata của project lấy title, description, ảnh Open Graph và canonical theo slug. Những phần như sort, trạng thái quay lại trang hay bài đọc gần nhất chạy ở client; chúng không phải cơ chế đồng bộ hồ sơ học tập trong SQL. Tách rõ hai lớp này giúp tránh suy luận sai rằng xóa database sẽ xóa trạng thái đọc lưu trong trình duyệt.

Các route có chính sách revalidation khác nhau: danh sách dự án có chu kỳ ngắn hơn sitemap. Vì vậy API đã trả dữ liệu mới nhưng HTML cache hoặc sitemap vẫn có thể chưa cập nhật cùng lúc. Đây là bài toán invalidation giữa nhiều lớp, phù hợp với cơ chế ISR của Next.js. Không thể kết luận xuất bản thành công chỉ từ một response API; cần kiểm tra cả trang công khai và URL tìm kiếm được sử dụng.

Ở Go CDN, handler ảnh phân tích kích thước yêu cầu, tạo tên cache theo ảnh gốc và kích thước, kiểm tra biến thể đã có rồi mới resize khi cần. Đường dẫn được resolve trong storage root để hạn chế thoát thư mục. Đây là cache media của ứng dụng, khác với CDN mạng biên do nhà cung cấp bên ngoài triển khai. Một ảnh đã tạo cache cũng không chứng minh mọi bản resize đều tối ưu hoặc mọi URL đã được quyền truy cập đúng.

Điểm kiểm chứng và hướng cải thiện có căn cứ

  • Đối chiếu relation và metadata trả về sau khi lưu, không chỉ HTTP status.
  • Đo query thực tế trước khi thêm index cho filter/sort; source có preload không tự chứng minh đã xử lý mọi N+1.
  • Kiểm tra ranh giới transaction giữa post và tag trước khi khẳng định ghi nguyên tử.
  • Theo dõi tính nhất quán của public status filter, cache trang và cache sitemap.
  • Phục hồi thử cả database lẫn media trong môi trường riêng để đánh giá khả năng rollback.

Các mục trên là tiêu chí kỹ thuật để đánh giá và phát triển tiếp; bài viết không đưa ra throughput, tỷ lệ cache hit hoặc số liệu load test chưa được đo. Phần đã quan sát được là cấu trúc ứng dụng, model, query, middleware và pipeline ảnh trong source.

Giao diện ứng dụng và dữ liệu minh họa

Các ảnh dưới đây được chụp trực tiếp từ FE hoặc trang quản trị của ứng dụng. Với các màn hình chạy trong phiên preview, dữ liệu mẫu được tách khỏi hệ thống vận hành; bố cục và thành phần giao diện do chính source của dự án render.

Thư viện tài liệu và lộ trình đọc

Thư viện tài liệu và lộ trình đọc
Ảnh chụp FE đang hoạt động: danh mục Linux Essentials, danh sách EP và điều hướng tài liệu.

Công cụ JSON Formatter

Công cụ JSON Formatter
Ảnh chụp công cụ thật với chuỗi JSON mẫu, hiển thị đầu vào và kết quả định dạng.

Tài liệu kỹ thuật đối chiếu

Xem thêm các dự án khác hoặc trao đổi về kiến trúc và phạm vi triển khai.

Knowledge-Base: kiến trúc Next.js, AdonisJS và MySQL | thanhnh.id.vn