0
0
Lập trình
TT

Xây dựng Framework Full-Stack Serverless với JS chỉ trong 1 ngày

Đăng vào 7 tháng trước

• 8 phút đọc

Giới thiệu

Bạn không đọc nhầm đâu — tôi đã thiết kế và triển khai một framework full-stack JavaScript hiện đại, serverless chỉ trong một ngày. Đây không phải là chiêu trò quảng cáo. Tôi không làm điều này để khoe khoang hay gây ấn tượng với ai — tôi chỉ đơn giản cần nó cho một dự án mà tôi đang thực hiện.

Dự án đó là một cơ sở dữ liệu lấy lại thông tin dựa trên AI, được thiết kế để chạy serverless và tận dụng mạng lưới của Cloudflare.

Tại sao chọn Cloudflare?

Vì nó miễn phí để bắt đầu, rẻ hơn khi mở rộng, và mang lại hiệu suất toàn cầu ngay từ đầu — hoàn hảo cho các ứng dụng hiện đại sử dụng edge.

Công nghệ tôi đã chọn

Dưới đây là các thành phần chính của framework:

  • Backend: Hono – nhẹ, nhanh và dễ mở rộng.
  • Frontend: React – lựa chọn của tôi để xây dựng UI/UX một cách nhanh chóng.
  • Hệ thống xây dựng: Vite – cực kỳ nhanh cho cả backend và frontend.
  • Runtime: Node.js cho môi trường phát triển, Cloudflare Workers cho môi trường sản xuất.
  • Hệ sinh thái gói: NPM – để tối đa hóa khả năng tương thích với các thư viện JS.
  • Triển khai: Cloudflare Workers (backend) + Cloudflare Pages (frontend).

Công nghệ này đứng sát cánh cùng với các stack hiện đại phổ biến như Next.js của Vercel hoặc OpenNext — nhưng với ít hạn chế hơn và không bị ràng buộc vào nhà cung cấp nào.

Bạn sẽ nhận được gì?

✅ Chức năng edge được hỗ trợ bởi Cloudflare Workers

✅ Mã nguồn hiệu suất cao, có thể mở rộng

✅ Trải nghiệm phát triển tuyệt vời (DX)

Và quan trọng nhất — bạn hoàn toàn tự do xây dựng và mở rộng mà không bị bó buộc.


Bước từng bước: Xây dựng Framework

Chúng ta cần tám thứ để thực hiện điều này:

  1. Backend: Hono
  2. Frontend: React
  3. Bundler: Vite
  4. Liên kết Backend-Frontend: Vite
  5. Runtime: Node cho môi trường phát triển, Workers trong sản xuất
  6. Hệ sinh thái: NPM
  7. Triển khai: Cloudflare Workers + Pages
  8. Nhà phát triển: (Đó là tôi — Ahmed, người kỳ diệu trong công nghệ. 😄)

Cấu trúc thư mục

Chúng ta sẽ theo cấu trúc monorepo, với:

  • packages/ → chứa client/server/
  • types/ → các kiểu dữ liệu chia sẻ ngoài packages/
  • các tệp cấu hình gốc (package.json, tsconfig.json, v.v.)

Cấu hình gốc

Tại đây, chúng ta định nghĩa các cấu hình liên kết backend, frontend và các gói chia sẻ.

package.json

json Copy
{
  "name": "my-hono-react-app",
  "private": true,
  "type": "module",
  "workspaces": [
    "packages/*",
    "types"
  ],
  "scripts": {
    "dev": "concurrently \"npm run dev:client\" \"npm run dev:server\"",
    "dev:client": "npm run dev -w client",
    "dev:server": "npm run dev -w server",
    "watch": "concurrently \"npm run watch -w client\" \"npm run watch -w server\"",
    "build": "npm run build -w client && npm run build -w server",
    "deploy": "npm run deploy -w server"
  },
  "devDependencies": {
    "@rollup/rollup-win32-x64-msvc": "^4.50.1",
    "concurrently": "^8.2.2",
    "wrangler": "^3.0.0"
  },
  "dependencies": {
    "lightningcss": "^1.30.1"
  }
}

💡 Lưu ý: Nếu bạn đang sử dụng Windows, bạn sẽ cần @rollup/rollup-win32-x64-msvc.

tsconfig.json

json Copy
{
  "compilerOptions": {
    "target": "ES2022",
    "useDefineForClassFields": true,
    "lib": ["ES2022", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "skipLibCheck": true,
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,
    "baseUrl": "."
  },
  "include": ["src/**/*", "*.ts"]
}

wrangler.toml

toml Copy
name = "hono-react-app"
main = "src/server/index.ts"
compatibility_date = "2024-01-01"
compatibility_flags = ["nodejs_compat"]

[vars]
NODE_ENV = "development"

[env.production]
[env.production.vars]
NODE_ENV = "production"

[assets]
directory = "./dist/client"
binding = "ASSETS"

[build]
command = "npm run build"

[dev]
port = 8787

(Đúng vậy, bạn vẫn cần .gitignoreREADME.md — haha, hãy tiếp tục 😄)


Các kiểu dữ liệu chia sẻ

Thư mục types lưu trữ mã được chia sẻ giữa client và server. Bạn có thể tạo nhiều thư mục như thế này theo ý muốn.

package.json

json Copy
{
  "name": "types",
  "private": true,
  "type": "module",
  "types": "./index.d.ts",
  "files": [
    "**/*.d.ts",
    "**/*.ts"
  ],
  "scripts": {
    "build": "tsc --project tsconfig.json"
  }
}

tsconfig.json

json Copy
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "declaration": true,
    "declarationMap": true,
    "outDir": "../dist-types",
    "rootDir": ".",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["**/*.ts"],
  "exclude": ["node_modules", "dist-types"]
}

Gói → Server

package.json

json Copy
{
  "name": "server",
  "type": "module",
  "scripts": {
    "dev": "wrangler dev",
    "deploy": "wrangler deploy --minify",
    "build": "npm run build -w client && tsc --noEmit",
    "watch": "tsc --noEmit --watch --preserveWatchOutput",
    "build:server": "tsc && cp -r ../dist/client ./dist/",
    "cf-typegen": "wrangler types --env-interface CloudflareBindings"
  },
  "dependencies": {
    "hono": "^4.9.6",
    "types": "../types"
  },
  "devDependencies": {
    "@cloudflare/workers-types": "^4.0.0",
    "wrangler": "^4.4.0"
  }
}

tsconfig.json

json Copy
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "lib": ["ES2022"],
    "types": ["@cloudflare/workers-types"],
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@/types": ["../../types/index"]
    },
    "noEmit": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist", "../client"]
}

wrangler.jsonc

json Copy
{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "server",
  "main": "src/index.ts",
  "compatibility_date": "2025-09-11",
  "pages_build_output_dir": "./dist"
}

src/index.ts

Tại đây, chúng ta cần hai thứ, một cách để định nghĩa các API backend bằng cách sử dụng Hono, và cấu hình khác để kích hoạt server-side rendering qua React và Vite.

typescript Copy
import { Hono } from "hono";
import { serveStatic } from "hono/cloudflare-workers";
import { cors } from "hono/cors";

const app = new Hono();

app.use(
  "/api/*",
  cors({
    origin: ["http://localhost:3000", "http://localhost:5173"],
    credentials: true,
  })
);

app.use(
  "/assets/*",
  serveStatic({
    root: "./dist/client",
    manifest: staticManifest,
  })
);

app.get("/api/hello", (c) => {
  return c.json({
    message: "Hello from Hono API!",
    timestamp: new Date().toISOString(),
  });
});

app.get("*", async (c) => {
  try {
    const { render } = await import("../dist/server/entry-server.js");
    const url = new URL(c.req.url);
    const { html, state } = await render(url.pathname, {});

    const template = `
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Hono + React + Vite</title>
      </head>
      <body>
        <div id="root">${html}</div>
        <script>window.__INITIAL_STATE__ = ${state}</script>
        <script type="module" src="/assets/client.js"></script>
      </body>
    </html>`;

    return c.html(template);
  } catch (e) {
    console.error("SSR Error:", e);
    return c.html("Server Error", 500);
  }
});

export default app;

Gói → Client

Chúng ta tạo client bằng cách:

bash Copy
cd packages 
npm create vite@latest

Sau đó cập nhật tsconfig.app.json để thêm các đường dẫn kiểu dữ liệu.

pages/HomePage.tsx

typescript Copy
import { useQuery } from '@tanstack/react-query'
import { apiFetch } from '../api';

function HomePage() {
  const { data: hello, isLoading } = useQuery({
    queryKey: ['hello'],
    queryFn: async () => {
      const response = await apiFetch('/api/hello')
      if (!response.ok) {
        throw new Error('Đáp ứng mạng không ổn định')
      }
      return response.json()
    }
  });

  return (
    <div className="home-container">
      <h1 className="main-title">Chào mừng đến với Stack Hiện Đại</h1>
      <p className="main-description">Trải nghiệm DX giống như NextJS với Hono, Cloudflare Workers, Vite và React.</p>

      <div className="api-status-card">
        <h3 className="api-status-title">Trạng thái API</h3>
        {isLoading ? (
          <div className="loading-pulse">
            <div className="pulse-line pulse-line-1"></div>
            <div className="pulse-line pulse-line-2"></div>
          </div>
        ) : hello ? (
          <div className="api-status-content">
            <div className="status-indicator">
              <span className="status-text">Kết nối với Hono API</span>
            </div>
            <p className="api-response">Phản hồi: {hello.message}</p>
            <p className="api-timestamp">Timestamp: {hello.timestamp}</p>
          </div>
        ) : (
          <div className="status-indicator">
            <span className="status-text">Kết nối API thất bại</span>
          </div>
        )}
      </div>
    </div>
  )
}

export default HomePage;

Hoàn thành. Thật sự.

Vậy là xong — chúng ta đã xây dựng một framework JavaScript full-stack, serverless sẵn sàng cho các ứng dụng sản xuất.

Để bắt đầu:

bash Copy
npm i
npm run build
npm run dev

Phiên bản Node: v20.17.0

Liên kết mã nguồn đầy đủ: GitHub

Bạn đã có một stack hiện đại, serverless nhanh chóng, có thể mở rộng và thân thiện với nhà phát triển — mà không bị ràng buộc bởi các nền tảng thương mại.

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào