Skip to content

Freelance Tech Stack - Tài liệu tham khảo

Tổng hợp từ góc nhìn thực tế cho solo developer / freelancer.
Stack nền tảng: Strapi + NestJS + Refine + Next.js
Deploy: AWS (BE/CMS) + Vercel (Next.js) + Cloudflare Pages (Refine)


Tổng quan kiến trúc

Cloudflare DNS

├── example.com           → Vercel          (Next.js — End User UI)
├── admin.example.com     → CF Pages        (Refine — Admin Panel)
├── api.example.com       → AWS App Runner  (NestJS — Business Logic)
└── cms.example.com       → AWS EC2         (Strapi — CMS)

                              AWS RDS PostgreSQL

                              AWS S3 + CloudFront
                                  (Media)

Layer Map

┌────────────────────────────────────────────────────┐
│                   END USER                         │
│               Next.js (Vercel)                     │
└────────────────────────┬───────────────────────────┘
                         │ HTTP / GraphQL
┌────────────────────────▼───────────────────────────┐
│                  BUSINESS LOGIC                    │
│             NestJS (AWS App Runner)                │
│       Auth │ Payment │ Workflow │ Events           │
└──────┬────────────────────────────────────┬────────┘
       │                                    │
┌──────▼──────┐                    ┌────────▼────────┐
│   Strapi    │                    │     Refine      │
│ (AWS EC2)   │                    │  (CF Pages)     │
│  Content    │                    │  Operations     │
│  Media→S3   │                    │  Analytics      │
└──────┬──────┘                    └────────┬────────┘
       │                                    │
       └─────────────────┬──────────────────┘

                 ┌───────▼───────┐
                 │  PostgreSQL   │
                 │  (AWS RDS)    │
                 └───────────────┘

1. NestJS — Business Logic Layer

Là gì?

Backend framework TypeScript-first, chạy trên Node.js. Đóng vai trò trung tâm xử lý logic của toàn hệ thống.

Nên dùng khi

  • Cần viết business logic phức tạp (tính toán, workflow, rule...)
  • Cần tích hợp third-party (Stripe, email, SMS, OAuth...)
  • Cần auth riêng (JWT, refresh token, RBAC)
  • Cần expose REST API hoặc GraphQL cho frontend
  • Cần background jobs (queue, cron, event-driven)

Không nên dùng khi

  • Project chỉ là landing page tĩnh — Next.js API routes đủ
  • Chỉ cần CRUD đơn giản, không có logic — Strapi tự xử lý được

Ví dụ thực tế

✅ Cần NestJS — E-commerce platform:
  POST /orders           → validate cart, check stock, tính giá
  POST /orders/:id/pay   → tích hợp Stripe, xử lý payment
  POST /users/verify     → gửi OTP, verify phone number
  GET  /reports/revenue  → aggregate data, trả về chart data

❌ Không cần NestJS — Blog cá nhân:
  - Chỉ cần Strapi + Next.js
  - Strapi expose API, Next.js fetch và render
  - Không có business logic phức tạp
  → Thêm NestJS vào là over-engineer

2. Strapi — Content Management Layer

Là gì?

Headless CMS open-source. Đóng vai trò quản lý content và media, expose REST/GraphQL API. Non-technical editor tự quản lý mà không cần dev.

Nên dùng khi

  • Client cần tự quản lý content mà không cần dev can thiệp
  • Schema content thay đổi thường xuyên (thêm field, thêm collection)
  • Cần media library (upload ảnh, video, file)
  • Non-technical editor cần GUI để làm việc
  • Cần draft/publish workflow

Không nên dùng khi

  • Data là transactional (orders, payments) — để NestJS + PostgreSQL xử lý
  • Cần logic phức tạp trên data — Strapi plugin hạn chế
  • Team toàn developer, không có non-technical editor — Payload CMS tốt hơn
  • Cần real-time data

Ví dụ thực tế

✅ Nên dùng Strapi:
  - Website công ty: bài viết blog, news, team members
  - E-commerce: product catalog, categories, banners
  - App du lịch: địa điểm, review, hình ảnh
  - Marketing site: landing page content, testimonials
  - Mobile app: banners, notifications, remote config

❌ Không nên dùng Strapi cho:
  - Quản lý đơn hàng (orders)        → NestJS + DB
  - Quản lý giao dịch tài chính      → NestJS + DB
  - Real-time chat messages           → WebSocket + Redis
  - User activity logs                → NestJS + DB

Strapi vs Payload CMS

Tiêu chíStrapiPayload
Schema tạo bằngGUI kéo thảTypeScript code
Phù hợp vớiNon-tech editorDeveloper team
SQLite dev experience✅ Tốt❌ Rough
Feature gateMột số feature EnterpriseKhông, tất cả free
GraphQLAdd-onAdd-on
Recommend choFreelance (client tự dùng)Internal team toàn dev

3. Refine — Admin Panel Layer

Là gì?

React meta-framework để build admin panel / internal tools. Không có database — chỉ là UI layer connect vào API (NestJS hoặc Strapi). Khác với Strapi, Refine dùng cho thao tác nghiệp vụ, không chỉ quản lý content.

Nên dùng khi

  • Cần admin panel với business actions (verify, approve, ban, refund...)
  • Cần custom workflow phức tạp hơn CRUD
  • Cần analytics / dashboard tự build
  • Internal tool cho operation team
  • Client cần xem report + thao tác trên data

Không nên dùng khi

  • Client chỉ cần quản lý content đơn giản — Strapi admin panel đủ
  • Chỉ cần xem report/chart thuần túy — Metabase nhanh hơn
  • Team không biết React — học curve cao

Ví dụ thực tế

✅ Nên dùng Refine:
  - Order management: xem đơn hàng, approve, cancel, refund
  - User management: xem users, verify KYC, ban account
  - Content moderation: review bài đăng, approve/reject
  - Support dashboard: xem tickets, assign, resolve
  - Finance dashboard: xem transactions, export report

❌ Không nên dùng Refine khi:
  - Client chỉ cần viết blog         → Strapi admin đủ
  - Chỉ cần xem chart doanh thu      → Metabase đơn giản hơn
  - Project quá nhỏ, 1-2 màn CRUD   → tự build nhanh hơn

Refine vs Metabase

RefineMetabase
Là gìAdmin panel framework (code)BI / Analytics tool (no-code)
Business actions✅ Có (verify, approve...)❌ Không, read-only
SetupCần code ReactConnect DB là xong
CustomizationKhông giới hạnHạn chế
Phù hợpOperation team cần actionStakeholder xem report
Khi nào dùngClient cần làm nghiệp vụClient chỉ cần xem số liệu

Cách Refine hoạt động

NestJS expose API:
  GET    /products
  POST   /products
  PATCH  /products/:id
  DELETE /products/:id

Refine tự generate:
  /admin/products          → List với filter, sort, pagination
  /admin/products/create   → Form tạo mới
  /admin/products/:id/edit → Form edit
  /admin/products/:id/show → Detail view

Custom action ngoài CRUD:
  useCustomMutation() → gọi bất kỳ endpoint nào
  Custom page         → Dashboard, Analytics tự viết
  useStepsForm()      → Multi-step form

4. Next.js — Frontend Layer

Là gì?

React framework fullstack. Đóng vai trò giao diện cho end user, deploy trên Vercel.

Nên dùng khi

  • Cần SEO (SSR, SSG)
  • Cần performance tốt (Image optimization, lazy loading)
  • Hầu hết mọi project có end-user facing UI
  • Cần fullstack nhẹ (API Routes cho logic đơn giản)

Không nên dùng khi

  • Project chỉ có admin panel — Refine đủ
  • Static site hoàn toàn — Astro nhẹ hơn, tốt hơn cho SEO

5. PostgreSQL — Database

Là gì?

Relational database. Primary database cho toàn hệ thống, chạy trên AWS RDS.

Nên dùng khi

  • Hầu như mọi project — PostgreSQL là default tốt nhất
  • Data có relation phức tạp
  • Cần ACID transactions (payment, orders)
  • Cần full-text search (built-in)

Mở rộng khi cần

+ Redis          → cache, session, queue, rate limiting
+ S3             → file storage, media uploads
+ TimescaleDB    → time-series data (metrics, logs)

6. Prisma — ORM Layer

Là gì?

TypeScript-native ORM dùng trong NestJS. Thay thế TypeORM. Drizzle ORM là lựa chọn nhẹ hơn nếu cần performance cao.

Nên dùng khi

  • Project mới với NestJS + PostgreSQL
  • Cần type-safe database queries
  • Cần migration workflow rõ ràng
typescript
// TypeORM — verbose, dễ lỗi type
const user = await userRepository.findOne({ where: { id } })

// Prisma — clean, fully typed, autocomplete tốt
const user = await prisma.user.findUnique({ where: { id } })

7. Metabase — Analytics Layer (Optional)

Là gì?

BI tool self-host. Connect thẳng vào PostgreSQL, build chart/report bằng GUI. Read-only — không thao tác được trên data.

Nên dùng khi

  • Client cần xem report phức tạp, nhiều chart
  • Client muốn tự build report không cần dev
  • Cần export PDF/Excel
  • Stakeholder / manager cần dashboard riêng

Không nên dùng khi

  • Client cần thao tác nghiệp vụ (verify, approve) → Refine
  • Project nhỏ, vài con số đơn giản → Refine tự build được

8. Deploy Stack — Cloud Architecture

Domain & Subdomain convention

example.com              → Next.js     (end user)
api.example.com          → NestJS      (REST API)
cms.example.com          → Strapi      (content editor)
admin.example.com        → Refine      (operation team)
analytics.example.com    → Metabase    (stakeholder - optional)

Service → Cloud mapping

ServiceCloudLý do
Next.jsVercelZero config, preview deployments, edge network
RefineCloudflare PagesStatic SPA, free tier rộng, không cần server
NestJSAWS App RunnerManaged container, auto scale, không manage server
StrapiAWS EC2 t3.smallCần persistent storage, full control
DatabaseAWS RDS PostgreSQLManaged, auto backup, production-grade
MediaAWS S3 + CloudFrontStrapi upload provider sẵn có
DNSCloudflareFree, SSL tự động, proxy bảo vệ IP

Chi phí ước tính / tháng (1 client project)

Vercel (Next.js)             → Free (hobby) / $20 (pro)
Cloudflare Pages (Refine)    → Free
AWS App Runner (NestJS)      → ~$10-15
AWS EC2 t3.small (Strapi)    → ~$15
AWS RDS t3.micro (Postgres)  → ~$15
AWS S3 + CloudFront (Media)  → ~$1-5
Cloudflare DNS               → Free

Tổng: ~$40-55/month
→ Charge client riêng, không gộp vào project fee

NestJS deploy options so sánh

Option A — EC2 (t3.micro/small)
  ✅ Full control, free tier 1 năm
  ❌ Tự manage (update, monitor, patch)
  💰 ~$15-20/month

Option B — App Runner ← RECOMMEND
  ✅ Deploy từ Docker / GitHub
  ✅ Auto scale, không manage server
  ✅ Quen với AWS ecosystem
  💰 ~$10-15/month

Option C — ECS Fargate
  ✅ Production-grade, flexible
  ❌ Setup phức tạp hơn
  💰 Đắt hơn cho small project

Nginx reverse proxy (nếu dùng VPS thay cloud)

nginx
# cms.example.com → Strapi :1337
server {
    server_name cms.example.com;
    location / { proxy_pass http://localhost:1337; }
}

# api.example.com → NestJS :3000
server {
    server_name api.example.com;
    location / { proxy_pass http://localhost:3000; }
}

# admin.example.com → Refine :4000
server {
    server_name admin.example.com;
    location / { proxy_pass http://localhost:4000; }
}

Lưu ý quan trọng khi freelance

✅ Tạo AWS account riêng cho mỗi client
✅ Transfer ownership khi bàn giao project
✅ Charge infrastructure cost riêng (không gộp vào fee)
❌ Không để nhiều client chung 1 AWS account
   → Rủi ro billing, security, isolation

9. Environments — Staging & Production

Triết lý

Lúc đầu chỉ cần Staging. Production thêm sau khi client confirm.

Lý do không setup Production ngay từ đầu:

  • Tiết kiệm chi phí trong giai đoạn dev/review
  • Tránh maintain 2 môi trường song song khi còn đang build
  • Client chưa cần Production cho đến khi go-live

Hai môi trường cần có

Staging (STG)Production (PROD)
Mục đíchDev, test, client reviewEnd user thực tế
DataFake / seed dataReal data
Downtime OK?✅ Có thể❌ Không
Deploy triggerMỗi push lên main / developManual hoặc tag release
Chi phíThấp hơn (instance nhỏ hơn)Đầy đủ, có thể multi-AZ
Khi nào setupNgay từ đầuTrước go-live 1-2 tuần

Convention subdomain theo environment

── Staging ──────────────────────────────────────
stg.example.com              → Next.js   (STG)
api.stg.example.com          → NestJS    (STG)
cms.stg.example.com          → Strapi    (STG)
admin.stg.example.com        → Refine    (STG)

── Production ───────────────────────────────────
example.com                  → Next.js   (PROD)
api.example.com              → NestJS    (PROD)
cms.example.com              → Strapi    (PROD)
admin.example.com            → Refine    (PROD)

Infrastructure theo environment

Staging — Setup tối giản, tiết kiệm chi phí

Next.js      → Vercel (preview branch deploy — free)
Refine       → Cloudflare Pages (preview branch — free)
NestJS       → AWS App Runner (nhỏ hơn: 0.25 vCPU / 0.5GB RAM)
Strapi       → AWS EC2 t3.micro (free tier nếu còn)
PostgreSQL   → AWS RDS t3.micro (single-AZ, no backup bắt buộc)
S3           → Cùng bucket với PROD nhưng prefix khác: /stg/...

Chi phí STG ước tính: ~$15-25/month


Production — Đầy đủ, reliable

Next.js      → Vercel (production branch)
Refine       → Cloudflare Pages (production branch)
NestJS       → AWS App Runner (lớn hơn: 1 vCPU / 2GB RAM)
Strapi       → AWS EC2 t3.small (với EBS snapshot hàng ngày)
PostgreSQL   → AWS RDS t3.small (Multi-AZ, auto backup 7 ngày)
S3           → Bucket riêng, versioning enabled
CloudFront   → CDN cho S3 media

Chi phí PROD ước tính: ~$50-70/month


Environment Variables — Tách biệt hoàn toàn

Mỗi environment có file .env riêng, không bao giờ share secret giữa STG và PROD.

bash
# .env.staging
NODE_ENV=staging
DATABASE_URL=postgresql://user:pass@stg-db.rds.amazonaws.com/myapp_stg
STRIPE_SECRET_KEY=sk_test_xxxx          # Stripe test key
NEXT_PUBLIC_API_URL=https://api.stg.example.com
STRAPI_URL=https://cms.stg.example.com
AWS_S3_BUCKET=myapp-stg-media

# .env.production
NODE_ENV=production
DATABASE_URL=postgresql://user:pass@prod-db.rds.amazonaws.com/myapp_prod
STRIPE_SECRET_KEY=sk_live_xxxx          # Stripe live key
NEXT_PUBLIC_API_URL=https://api.example.com
STRAPI_URL=https://cms.example.com
AWS_S3_BUCKET=myapp-prod-media

Lưu trữ secrets:

AWS Secrets Manager  → secrets của NestJS, Strapi (PROD)
Vercel Env Vars      → secrets của Next.js
CF Pages Env Vars    → secrets của Refine
GitHub Secrets       → secrets dùng trong CI/CD

Git branching strategy phù hợp

main ──────────────────────────────────────► PRODUCTION

  └── develop ───────────────────────────► STAGING

        ├── feature/login
        ├── feature/payment
        └── fix/order-bug
Workflow:
  1. Developer tạo feature branch từ develop
  2. PR vào develop → auto deploy lên STG
  3. Client review trên stg.example.com
  4. Khi đủ features → merge develop vào main
  5. main → auto deploy lên PROD

CI/CD với GitHub Actions

yaml
# .github/workflows/deploy.yml

name: Deploy

on:
  push:
    branches:
      - develop    # → Staging
      - main       # → Production

jobs:
  deploy-nestjs:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set environment
        run: |
          if [ "${{ github.ref }}" == "refs/heads/main" ]; then
            echo "ENV=production" >> $GITHUB_ENV
            echo "APP_RUNNER_ARN=${{ secrets.PROD_APP_RUNNER_ARN }}" >> $GITHUB_ENV
          else
            echo "ENV=staging" >> $GITHUB_ENV
            echo "APP_RUNNER_ARN=${{ secrets.STG_APP_RUNNER_ARN }}" >> $GITHUB_ENV
          fi

      - name: Build & push Docker image
        run: |
          docker build -t nestjs-app .
          # push to ECR...

      - name: Deploy to App Runner
        run: |
          aws apprunner start-deployment \
            --service-arn $APP_RUNNER_ARN

Vercel và Cloudflare Pages tự động detect branch — không cần config CI/CD thêm.


Checklist trước khi go-live (STG → PROD)

Infrastructure:
  ☐ Tạo RDS PROD (Multi-AZ, backup enabled)
  ☐ Tạo EC2 PROD cho Strapi (với snapshot policy)
  ☐ Tạo App Runner PROD cho NestJS
  ☐ Setup S3 PROD bucket (versioning on)
  ☐ Setup CloudFront distribution
  ☐ Config subdomain PROD trên Cloudflare DNS
  ☐ SSL certificate tự động qua Cloudflare

Environment:
  ☐ Tạo secrets PROD trên AWS Secrets Manager
  ☐ Set env vars PROD trên Vercel
  ☐ Set env vars PROD trên Cloudflare Pages
  ☐ Đổi Stripe key từ test → live
  ☐ Kiểm tra tất cả NEXT_PUBLIC_* URLs trỏ đúng PROD

Data:
  ☐ Seed data cần thiết cho PROD (categories, config...)
  ☐ Tạo admin account PROD (khác STG)
  ☐ KHÔNG copy data từ STG sang PROD

Testing:
  ☐ Smoke test toàn bộ flow trên PROD trước khi announce
  ☐ Test payment với Stripe live (small amount)
  ☐ Test email/SMS gửi đúng
  ☐ Kiểm tra SSL hoạt động

Lộ trình setup thực tế

Tuần 1-2 (bắt đầu project):
  ✅ Setup STG hoàn chỉnh
  ✅ CI/CD tự động deploy lên STG
  ✅ Client bắt đầu review trên stg.example.com

Tuần N-1 (trước go-live 1-2 tuần):
  ✅ Setup PROD infrastructure
  ✅ Test deployment lên PROD
  ✅ Smoke test toàn bộ

Ngày go-live:
  ✅ Deploy lên PROD
  ✅ DNS cutover (trỏ domain về PROD)
  ✅ Monitor 24h đầu

Decision Framework — Chọn tool như thế nào?

Client cần gì?

├── Quản lý content (bài viết, sản phẩm, media)?
│   └── → Strapi

├── Business logic phức tạp (payment, auth, workflow)?
│   └── → NestJS

├── Admin panel với nghiệp vụ (verify, approve, ban)?
│   └── → Refine

├── Xem report / chart?
│   ├── Đơn giản, vài cái, có action  → Refine tự build
│   └── Phức tạp, read-only, client tự dùng → Metabase

├── Frontend cho end user, cần SEO?
│   └── → Next.js (Vercel)

└── Chỉ admin/internal, không có end user?
    └── → Không cần Next.js

Stack theo loại project

Project 1 — Blog / Website công ty

Strapi (EC2)       → bài viết, pages, team members
Next.js (Vercel)   → render frontend, SEO
RDS PostgreSQL     → database

KHÔNG cần: NestJS, Refine, Metabase
Lý do: không có business logic, không cần admin phức tạp
Chi phí: ~$20-30/month

Project 2 — E-commerce đơn giản

Strapi (EC2)       → product catalog, categories, banners
NestJS (App Runner)→ orders, payment (Stripe), auth
Next.js (Vercel)   → storefront cho end user
Refine (CF Pages)  → admin quản lý orders, users
RDS PostgreSQL     → database

KHÔNG cần: Metabase (Refine đủ cho basic report)
Chi phí: ~$45-55/month

Project 3 — SaaS platform

Strapi (EC2)       → marketing content, docs
NestJS (App Runner)→ core business logic, subscriptions, auth
Next.js (Vercel)   → app UI cho end user
Refine (CF Pages)  → admin panel cho operation team
Metabase (EC2)     → analytics dashboard cho stakeholder
RDS PostgreSQL     → primary database
Redis (ElastiCache)→ cache, queue, session

Chi phí: ~$80-120/month

Project 4 — Internal Tool / Back-office thuần túy

NestJS (App Runner)→ API + business logic
Refine (CF Pages)  → toàn bộ UI
RDS PostgreSQL     → database

KHÔNG cần: Strapi, Next.js
Chi phí: ~$25-35/month

Project 5 — Mobile App Backend

NestJS (App Runner)→ REST API cho mobile app
Strapi (EC2)       → app content (banners, notifications, config)
RDS PostgreSQL     → database
Refine (CF Pages)  → admin panel

KHÔNG cần: Next.js (mobile app là client)
Chi phí: ~$40-50/month

Câu hỏi nên hỏi client trước khi bắt đầu

1. "Ai sẽ dùng hệ thống này hàng ngày?"
   → Non-technical editor  →  Cần Strapi
   → Developer / ops team  →  Refine đủ

2. "Ngoài quản lý content, cần làm gì khác?"
   → Chỉ CRUD content      →  Strapi đủ
   → Có workflow phức tạp  →  NestJS + Refine

3. "Có end user không? Họ cần làm gì?"
   → Có, cần SEO, đẹp      →  Next.js (Vercel)
   → Chỉ internal          →  Không cần Next.js

4. "Cần xem báo cáo gì?"
   → Vài con số, có action →  Refine tự build
   → Nhiều chart, read-only→  Metabase

5. "Có tích hợp payment không?"
   → Có                    →  NestJS + Stripe
   → Không                 →  Có thể bỏ NestJS nếu logic đơn giản

6. "Budget infrastructure hàng tháng là bao nhiêu?"
   → < $30/month           →  Strapi + Next.js only
   → $40-60/month          →  Full stack cơ bản
   → > $80/month           →  Full stack + Metabase + Redis

Nguyên tắc chung

Bắt đầu đơn giản, thêm tool khi thực sự cần.

Mọi project bắt đầu với:
  Strapi + NestJS + PostgreSQL (RDS)

Thêm khi cần:
  + Next.js (Vercel)      → khi có end-user UI cần SEO
  + Refine (CF Pages)     → khi cần admin panel với business actions
  + Metabase              → khi client cần tự xem report phức tạp
  + Redis (ElastiCache)   → khi cần cache hoặc queue
  + Prisma                → thay TypeORM cho project mới
  + S3 + CloudFront       → khi Strapi cần media storage

Over-engineering từ đầu là lỗi phổ biến nhất của freelancer.
Client trả tiền cho kết quả, không phải cho số lượng tool.


Cập nhật: June 2026

Today I Learned