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.
Đằ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]
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.
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.
- 01GEOĐược chọn
VNTrang dành cho Việt Nam - 02DEVICEDừng xét
mobileTrang dành cho điện thoại - 03LANGUAGEDừng xét
viNội dung bằng tiếng Việt
https://example.com/viet-namGEO 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]
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.
Mỗi link có
một bộ quy tắc.
Endpoint tạo link nhận địa chỉ gốc, alias tùy chọn và targeting. Luồng redirect có kiểm tra trạng thái, hạn dùng và giới hạn lượt bấm. Độ chính xác của giới hạn khi có truy cập đồng thời vẫn cần kiểm thử thêm. [4]
Đư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]
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]
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]
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]
- 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 - 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 - 03 / BỘ NHỚ ĐỆM
Redis
Lưu cache cấu hình link và Redis List cho hàng đợi analytics.
analytics_queue - 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.
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à overlayChạ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.
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.
git clone https://github.com/Pranav-Saraswat/LinkSwift.git
cd LinkSwift
git checkout b57c8d6e49c201f2e80148c0d0d01ba0a8c0176a
npm ci --ignore-scriptsMở 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.
npm run dev --workspace=@url-shortener/web -- --host 127.0.0.1Kiể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.
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Đ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ả đí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.
{
"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"
}
]
}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]
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.
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.
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.
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.
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]
Đ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 đó.
- LinkSwift — repo và README
- Frontend — React, Vite và dependencies
- API — Fastify và dependencies
- API — tạo link, routing, QR và analytics
- Prisma — schema và các quan hệ dữ liệu
- Dashboard — cách tải thống kê
- Web client — API URL và kiểu dữ liệu
- Turbo — cấu hình build ở gốc
- Database workspace — entry TypeScript
- Docker Compose — PostgreSQL và Redis
- Frontend — router, copy link và tải QR
- Redis — BRPOP và kết nối chặn
- LinkSwift — Terraform và cloud scaffolding
- LinkSwift — giấy phép MIT