ts4m.dev
MỚIGÓC MÃ NGUỒN MỞ / 006

LinkSwift

Một đường link.
Nhiều lối đến.

Rút gọn một địa chỉ chỉ là khởi đầu.
Câu chuyện thú vị nằm ở điều xảy ra sau cú bấm.

Minh họa của ts4m · một liên kết, nhiều điểm đến
REACT + VITEFASTIFYPRISMA + REDISMIT LICENSE
01 / MỘT Ý TƯỞNG NHỎ, NHIỀU MẢNH GHÉP

Đằng sau
một cú bấm.

Đôi khi, một chiếc link bé xíu
lại là bài học full-stack rất lớn.

LinkSwift là dự án mã nguồn mở về rút gọn và quản lý liên kết của Pranav Saraswat. Repo có dashboard, API, quy tắc chuyển hướng, mã QR và luồng ghi nhận lượt bấm.

Ý tưởng dễ hình dung: bạn chia sẻ một địa chỉ ngắn; hệ thống đọc ngữ cảnh của lượt truy cập rồi chọn trang đích phù hợp. Một người dùng điện thoại có thể được dẫn đến trang mobile, trong khi người khác mở trang mặc định.

Điều mình thích ở repo là có thể lần theo cả hành trình: từ form React, qua API, đến cơ sở dữ liệu và hàng đợi. Đây là một dự án đáng đọc để học cách các phần nối với nhau. [1]

02 / CHẠM VÀO Ý TƯỞNG

Cùng một link.
Bạn sẽ đi đâu?

Đổi ngữ cảnh và thứ tự quy tắc. Xem vì sao một lượt bấm có thể chọn điểm đến khác.

BÀN THỬ CHUYỂN HƯỚNGMÔ PHỎNG / KHÔNG TẠO LINK THẬT
01 / NGỮ CẢNH BẠN CHỌN

go.example/hello

Dữ liệu giả lập do bạn chọn. Bàn thử không đọc IP, vị trí hay thiết bị của bạn.

02 / ĐỌC TỪ TRÊN XUỐNG
  1. 01
    GEO VNTrang dành cho Việt Nam
    Được chọn
  2. 02
    DEVICE mobileTrang dành cho điện thoại
    Dừng xét
  3. 03
    LANGUAGE viNội dung bằng tiếng Việt
    Dừng xét
03 / ĐIỂM ĐẾNTrang Việt Namhttps://example.com/viet-nam

GEO khớp trước. Các quy tắc phía sau không còn được xét. Đây là mô phỏng trong bài, không chuyển trang.

Quy tắc khớp đầu tiên thắng. Hàm getSmartRedirect duyệt mảng rồi trả về ngay khi khớp; không khớp thì dùng originalUrl. Bản demo giữ thứ tự rõ ràng để giải thích cơ chế. [4]

Trong repo có thêm OS. Bốn kiểu là GEO, DEVICE, LANGUAGE và OS. Snapshot chưa có cột ưu tiên hoặc orderBy cho quan hệ targeting, nên cần làm rõ thứ tự nếu dùng thật. [5]

03 / NHỮNG MẢNH ĐÃ CÓ TRONG MÃ

Gọn ở bên ngoài.
Nhiều lớp bên trong.

Nhìn theo từng luồng nhỏ sẽ dễ hiểu repo hơn một danh sách tính năng dài.

SHARE / QR

Đưa link
ra khỏi màn hình.

Route QR tạo ảnh PNG cho địa chỉ ngắn. Dùng trên tờ giới thiệu hay tài liệu là một hướng ứng dụng; mã QR cần đúng BASE_URL để dẫn về dịch vụ của bạn. [4]

WORKSPACES / TEAMS

Gom liên kết.
Chia không gian.

Schema có workspace và vai trò OWNER, ADMIN, MEMBER. API có đăng nhập, API key và kiểm tra thành viên ở các luồng quản lý. Việc rà soát quyền vẫn cần làm trước khi mở cho nhiều người. [5]

CLICKS / CONTEXT

Hiểu lượt bấm
đến từ đâu.

Luồng analytics ghi ngữ cảnh thiết bị, quốc gia, referrer và UTM. “Unique clicks” hiện đếm IP khác nhau, không phải số người được xác minh; giao diện tải thống kê khi mở, chưa phải luồng realtime. [4] [6]

04 / LẦN THEO ĐƯỜNG ĐI

Một repo.
Bốn mảnh chính.

Frontend thực tế dùng React 19 + Vite 6. Dòng Next.js 15 trong README chưa khớp với package được kiểm tra. [2]

  1. 01 / GIAO DIỆN

    React + Vite

    Form tạo link, workspace, bảng thống kê. Tailwind, Framer Motion và Recharts hỗ trợ phần hiển thị.

    apps/web
  2. 02 / ĐIỀU PHỐI

    Fastify

    Nhận yêu cầu, xác thực, chọn điểm đến và đưa sự kiện click vào hàng đợi.

    apps/api
  3. 03 / BỘ NHỚ ĐỆM

    Redis

    Lưu cache cấu hình link và Redis List cho hàng đợi analytics.

    analytics_queue
  4. 04 / LƯU TRỮ

    PostgreSQL

    Prisma mô tả người dùng, liên kết, workspace, targeting và dữ liệu analytics.

    packages/database

Lượt bấm đi đâu trước?

Route redirect tìm link trong Redis; nếu cache trống, đọc từ PostgreSQL rồi lưu cache trong một giờ. Sau các bước kiểm tra, hệ thống chọn địa chỉ đích và đưa sự kiện click vào hàng đợi.

Worker lấy từng sự kiện để ghi analytics và tăng bộ đếm trong database. Trong snapshot này, API và các worker nằm cùng tiến trình; chưa phải những dịch vụ độc lập chỉ vì có một hàng đợi. [3] [4] [5]

Đây là cách đọc kiến trúc từ source. Bài không đưa ra cam kết độ trễ hay khả năng chịu tải.

Bản đồ repo · những nơi nên đọc trước
LinkSwift/
├── apps/web/          # Dashboard React + Vite
├── apps/api/          # API + redirect + workers
├── packages/database/ # Prisma schema và client
├── docker-compose.yml # PostgreSQL + Redis
├── terraform/         # Bộ khung AWS, GCP, Azure
└── k8s/               # Manifests và overlay
05 / TỪ REPO VỀ MÁY CỦA BẠN

Chạy thử giao diện.
Đọc tiếp phần lõi.

Chuẩn bị Git, Node.js và npm. Phần kiểm chứng dưới đây dùng Node 22.14.0 trên Linux, ở đúng commit được ghim trong bài.

01

Lấy mã nguồn và cài dependency.

Ghim commit để kết quả gần với bài viết. --ignore-scripts bỏ qua lifecycle scripts lúc cài; bước generate Prisma cần thiết sẽ được chạy riêng.

Terminal · lấy đúng bản được đọc trong bài
git clone https://github.com/Pranav-Saraswat/LinkSwift.git
cd LinkSwift
git checkout b57c8d6e49c201f2e80148c0d0d01ba0a8c0176a
npm ci --ignore-scripts
02

Mở giao diện Vite.

Lệnh dưới gọi trực tiếp workspace web. Mở địa chỉ mà Vite in ra, thường là http://127.0.0.1:5173.

Terminal · xem giao diện trên máy của bạn
npm run dev --workspace=@url-shortener/web -- --host 127.0.0.1
03

Kiểm tra từng phần trước khi nối cả hệ thống.

Mở terminal thứ hai, vẫn trong thư mục LinkSwift. Generate Prisma Client, chạy test và build riêng từng workspace. Các lệnh này không khởi động database hay triển khai ứng dụng.

Terminal · generate, test và build từng phần
npm run prisma:generate --workspace=@url-shortener/database
npm test -- --maxWorkers=1 --minWorkers=1
npm run build --workspace=@url-shortener/api
npm run build --workspace=@url-shortener/web
NHẬT KÝ KIỂM CHỨNG / 15.10.2026

Điều đã chạy.
Điều còn vướng.

Đây là kết quả trên bản nguồn nêu trên. Chưa chạy toàn bộ luồng tạo link → chuyển hướng với database/Redis thật.

Cài dependencies & Prisma generate
Đạt
Unit tests có sẵn của repo
7/7 đạt
Build Vite & compile TypeScript của API
Đạt riêng từng phần
Build ở thư mục gốc qua Turbo
Lỗi cấu hình
Typecheck frontend riêng
11 chẩn đoán lỗi
Nạp API đã compile bằng Node 22.14
Lỗi entry .ts của database

Vite tạo được bundle không đồng nghĩa TypeScript đã kiểm tra mọi kiểu. Build API thành công cũng chưa chứng minh server khởi động được. Phạm vi và lệnh kiểm tra được ghi trong nhật ký đi kèm bài.

MỘT MẪU NHỎ ĐỂ ĐỌC API

Mô tả đích đến
bằng dữ liệu.

Đây là body minh họa cho POST /api/shorten. API yêu cầu xác thực, chẳng hạn Authorization: Bearer …, cùng x-workspace-id của workspace mà bạn là thành viên. [4]

Nếu mảng được xử lý theo thứ tự hiển thị, người dùng mobile đọc tiếng Việt sẽ khớp DEVICE trước. Không có quy tắc khớp thì dùng url làm địa chỉ gốc.

Các địa chỉ example.com chỉ để minh họa. Mẫu này chưa được gửi đến một backend đang chạy; hãy hoàn thiện các điểm triển khai bên dưới trước khi thử luồng thật.

JSON · ví dụ body cho POST /api/shorten
{
  "url": "https://example.com/bai-viet",
  "alias": "bai-viet",
  "targeting": [
    {
      "type": "DEVICE",
      "value": "mobile",
      "redirectUrl": "https://example.com/mobile"
    },
    {
      "type": "LANGUAGE",
      "value": "vi",
      "redirectUrl": "https://example.com/tieng-viet"
    }
  ]
}
06 / ĐỂ MỘT Ý TƯỞNG THÀNH DỊCH VỤ

Trước khi
đưa lên server.

Repo đã đặt nhiều viên gạch. Với snapshot này, mình sẽ hoàn thiện các phần dưới đây rồi mới dùng cho những link cần hoạt động ổn định.

01Làm cho quy trình build và chạy nhất quán.

Root build đang dừng ở cấu hình package manager; turbo.json còn dùng khóa pipeline của cấu hình cũ. Workspace database trỏ entry vào index.ts, gây lỗi khi nạp bản API đã compile bằng Node trong môi trường kiểm tra.

Cần thống nhất phiên bản công cụ, cập nhật cấu hình Turbo, xuất database package thành JavaScript phù hợp và sửa lỗi kiểu ở frontend. Sau đó mới kiểm tra lại lệnh start và image Docker. [8] [9]

02Nối frontend, API và môi trường đúng địa chỉ.

Frontend mặc định là Vite ở cổng 5173; API là 3001, nhưng BASE_URL mặc định lại là 3000. Cần trỏ BASE_URL tới nơi phục vụ redirect, cấu hình API URL cho web và sửa cả địa chỉ tải QR đang được viết cố định riêng.

API đọc process.env, chưa có bước tự nạp file .env. Hãy truyền cấu hình qua môi trường chạy và thay JWT secret mặc định. Compose hiện chỉ khởi động PostgreSQL/Redis, với tài khoản mẫu và cổng công khai; cần cấu hình lại trước khi dùng trên VPS. [4] [7] [10] [11]

03Tách kết nối hàng đợi, kiểm tra lượt bấm đồng thời.

Worker gọi BRPOP chặn trên cùng Redis client mà API dùng cho cache và ghi sự kiện. Từ cách dùng trong source, đây là điểm có nguy cơ làm các lệnh phía sau phải chờ; nên cấp kết nối riêng cho worker.

maxClicks đang đọc bộ đếm có thể còn cũ trong cache; link dùng một lần chưa có bước tiêu thụ nguyên tử. Cần kiểm thử đồng thời và đồng bộ bộ đếm trước khi hứa “chỉ mở đúng một lần”. Những điểm này là nhận xét từ mã nguồn, chưa phải kết quả thử tải. [4] [12]

04Hoàn thiện những hành trình người dùng còn thiếu.

Link có mật khẩu trả về JSON 403 ở route redirect; chưa thấy trang nhập mật khẩu tương ứng trong router frontend. API danh sách trả record chưa có shortUrl mà giao diện copy đang chờ. Hai luồng này cần nối lại và kiểm thử sau khi tải lại trang.

“AI analytics” hiện là quy tắc nhận diện bot và cộng điểm rủi ro, chưa thấy mô hình học máy hay dự báo được tích hợp. Cũng chưa thấy luồng MFA hoàn chỉnh trong bản đã đọc. [4] [11]

05Xem cloud là một bước triển khai riêng.

Repo có Terraform cho AWS, GCP, Azure và manifests Kubernetes. Mình chưa chạy chúng trên cloud; sự hiện diện của các file chưa chứng minh một hệ thống đa cloud đã vận hành được.

Sau khi chạy ổn ở môi trường thử nghiệm, mới cấu hình ingress/domain/HTTPS, secret, mạng riêng cho dữ liệu, backup, health check và quan sát lỗi. Rà soát quyền workspace, cách lấy IP qua proxy và xử lý URL đích. Không chạy terraform apply chỉ vì README có ví dụ. [13]

07 / MANG MỘT Ý TƯỞNG VỀ

Nhỏ vừa đủ
để tự xây tiếp.

Nếu fork repo để học, mình sẽ chọn một luồng thật nhỏ, làm cho nó đúng rồi mới mở rộng.

01 / HỌC BACKEND

Một link đi đến nơi.

Bắt đầu bằng tạo link, truy xuất và redirect. Thêm test cho link không tồn tại, hết hạn và đích mặc định.

02 / HỌC SẢN PHẨM

Một chiến dịch dễ quản lý.

Gom các liên kết theo workspace, sửa trải nghiệm copy và QR, rồi thử lại bằng một người dùng mới hoàn toàn.

03 / HỌC HỆ THỐNG

Một hàng đợi chạy bền.

Tách worker, thêm retry có giới hạn, kiểm tra mất sự kiện và cách giữ bộ đếm nhất quán.

HỎI NHANH / ĐỌC GỌN

Vài điều
nên biết.

LinkSwift có phải dịch vụ rút gọn link đang chạy trên ts4m.dev?

Không. Đây là bài giới thiệu repo và mô phỏng để học cơ chế routing. Bàn thử không gửi yêu cầu đến LinkSwift và không tạo địa chỉ ngắn thật.

Có thể dùng ngay cho link quan trọng không?

Mình chưa khuyến nghị dùng nguyên snapshot này cho link quan trọng. Các phần build/runtime, Redis worker và luồng người dùng cần hoàn thiện, rồi kiểm thử cả hệ thống trước khi vận hành.

Không khớp quy tắc thì chuyển đi đâu?

Hàm routing trả về originalUrl. Trong source, khi không tra được GeoIP thì country mặc định là US; thiếu ngôn ngữ thì mặc định en. Cần cân nhắc các giá trị mặc định này theo cách bạn sử dụng. [4]

Có được fork và sửa lại không?

Repo được phát hành theo MIT. Đọc LICENSE và giữ thông báo bản quyền, nội dung giấy phép khi phân phối phần mã thuộc phạm vi yêu cầu của giấy phép. [14]

ĐỌC TIẾP / NGUỒN ĐỐI CHIẾU

Đi đến tận nguồn.

Bài độc lập của ts4m, đối chiếu ngày 15.10.2026. Nguồn code được ghim theo commit; repo có thể thay đổi sau đó.

  1. LinkSwift — repo và README
  2. Frontend — React, Vite và dependencies
  3. API — Fastify và dependencies
  4. API — tạo link, routing, QR và analytics
  5. Prisma — schema và các quan hệ dữ liệu
  6. Dashboard — cách tải thống kê
  7. Web client — API URL và kiểu dữ liệu
  8. Turbo — cấu hình build ở gốc
  9. Database workspace — entry TypeScript
  10. Docker Compose — PostgreSQL và Redis
  11. Frontend — router, copy link và tải QR
  12. Redis — BRPOP và kết nối chặn
  13. LinkSwift — Terraform và cloud scaffolding
  14. LinkSwift — giấy phép MIT