在您自己的服务器上部署 TanStack Start 与 SQLite

发布: (2025年12月15日 GMT+8 05:33)
6 min read
原文: Dev.to

Source: Dev.to

为什么选择 TanStack Start + SQLite?

  • 简洁 – SQLite 与你的应用运行在同一进程中,消除了网络延迟和复杂的数据库集群。开发只需 pnpm install,即可开始编码。
  • 性能 – 读取和写入直接命中本地文件系统,速度往往超过网络附加的数据库,能够满足典型的网页请求。
  • 易于部署 – 将应用服务器和数据库文件(挂载为持久卷)一起部署到单个地区,使用 Haloy 即可。

你将构建的内容

一个使用以下技术的全栈 React 应用:

  • TanStack Start – 具备文件路由和服务器函数的 React 元框架
  • SQLite – 轻量级、基于文件的数据库
  • Drizzle ORM – 支持 TypeScript 的类型安全数据库查询 ORM
  • Haloy – 简单的自有服务器部署方案

前置条件

  • 已安装 Node.js 20+
  • 已安装 Haloy(参见 Haloy 快速入门)
  • 一台 Linux 服务器(VPS 或独立服务器)
  • 一个域名或子域名
  • 基本的 React 与 TypeScript 使用经验

项目初始化

mkdir my-tanstack-app
cd my-tanstack-app
pnpm init

tsconfig.json

{
  "compilerOptions": {
    "jsx": "react-jsx",
    "moduleResolution": "Bundler",
    "module": "ESNext",
    "target": "ES2022",
    "skipLibCheck": true,
    "strictNullChecks": true
  }
}

安装核心依赖

pnpm add @tanstack/react-start @tanstack/react-router react react-dom nitro

安装开发依赖

pnpm add -D vite @vitejs/plugin-react typescript @types/react @types/react-dom @types/node vite-tsconfig-paths

安装 Drizzle 与 SQLite 客户端

pnpm add drizzle-orm @libsql/client dotenv drizzle-kit

注意: drizzle-kit 作为生产依赖(而非 -D)安装,因为它必须在 Docker 容器中可用,以在启动时运行迁移。

package.json 脚本

{
  // ...
  "type": "module",
  "scripts": {
    "dev": "vite dev",
    "build": "vite build",
    "start": "node .output/server/index.mjs",
    "db:generate": "drizzle-kit generate",
    "db:migrate": "drizzle-kit migrate"
  }
}

配置

vite.config.ts

import { defineConfig } from "vite";
import { nitro } from "nitro/vite";
import tsConfigPaths from "vite-tsconfig-paths";
import { tanstackStart } from "@tanstack/react-start/plugin/vite";
import viteReact from "@vitejs/plugin-react";

export default defineConfig({
  server: {
    port: 3000,
  },
  plugins: [
    tsConfigPaths(),
    tanstackStart(),
    nitro(),
    // React 的 Vite 插件必须放在 Start 的 Vite 插件之后
    viteReact(),
  ],
  nitro: {},
});

TanStack Start 使用 Nitro 作为服务器引擎。本次部署使用默认的 Node.js 预设,能够完美配合 Haloy。无需额外的 Nitro 配置;空的 nitro: {} 对象即可。

drizzle.config.ts

import { config } from "dotenv";
import { defineConfig } from "drizzle-kit";

config();

const databaseUrl = process.env.DATABASE_URL;
if (!databaseUrl) {
  throw new Error("DATABASE_URL is not set");
}

export default defineConfig({
  out: "./drizzle",
  schema: "./src/db/schema.ts",
  dialect: "sqlite",
  dbCredentials: {
    url: databaseUrl,
  },
});

数据库客户端 (src/db/index.ts)

import "dotenv/config";
import { createClient } from "@libsql/client";
import { drizzle } from "drizzle-orm/libsql";

const databaseUrl = process.env.DATABASE_URL;
if (!databaseUrl) {
  throw new Error("DATABASE_URL is not set");
}

const client = createClient({ url: databaseUrl });
const db = drizzle({ client });
export { client, db };

数据模型 (src/db/schema.ts)

import { sql } from "drizzle-orm";
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";

export const todos = sqliteTable("todos", {
  id: integer("id", { mode: "number" }).primaryKey({
    autoIncrement: true,
  }),
  title: text("title").notNull(),
  createdAt: integer("created_at", { mode: "timestamp" }).default(
    sql`(unixepoch())`
  ),
});

本地开发环境变量 (.env)

DATABASE_URL=file:local.db

生成并运行迁移:

pnpm db:generate
pnpm db:migrate

这些命令会在 drizzle/ 目录下创建迁移文件,随后会在生产环境中使用。

路由

src/router.tsx

import { createRouter } from "@tanstack/react-router";
import { routeTree } from "./routeTree.gen";

export function getRouter() {
  const router = createRouter({
    routeTree,
    scrollRestoration: true,
    defaultNotFoundComponent: () => 404 - not found,
  });

  return router;
}

注意: 你可能会看到关于 ./routeTree.gen 未找到的 TypeScript 错误。这是正常现象——TanStack Start 会在你运行开发服务器时自动生成该文件。

根路由 (src/routes/__root.tsx)

/// 

import {
  createRootRoute,
  HeadContent,
  Outlet,
  Scripts,
} from "@tanstack/react-router";
import type { ReactNode } from "react";

export const Route = createRootRoute({
  head: () => ({
    meta: [
      { charSet: "utf-8" },
      { name: "viewport", content: "width=device-width, initial-scale=1" },
      { title: "TanStack Start Starter" },
    ],
  }),
  component: RootComponent,
});

function RootComponent() {
  return (
    
      
    
  );
}

function RootDocument({ children }: Readonly) {
  return (
    
      
        
      
      
        {children}
        
      
    
  );
}

Todo 路由 (src/routes/index.tsx)

import { createFileRoute, useRouter } from "@tanstack/react-router";
import { createServerFn } from "@tanstack/react-start";
import { eq } from "drizzle-orm";
import { db } from "../db";
import { todos } from "../db/schema";

const getTodos = createServerFn({
  method: "GET",
}).handler(async () => await db.select().from(todos));

const addTodo = createServerFn({ method: "POST" })
  .inputValidator((data: FormData) => {
    if (!(data instanceof FormData)) {
      throw new Error("Expected FormData");
    }
    return {
      title: data.get("title")?.toString() || "",
    };
  })
  .handler(async ({ data }) => {
    await db.insert(todos).values({ title: data.title });
  });

const deleteTodo = createServerFn({ method: "POST" })
  .inputValidator((data: number) => data)
  .handler(async ({ data }) => {
    await db.delete(todos).where(eq(todos.id, data));
  });

export const Route = createFileRoute("/")({
  component: RouteComponent,
  loader: async () => await getTodos(),
});

function RouteComponent() {
  const router = useRouter();
  const todos = Route.useLoaderData();

  return (
    
      

        {todos.map((todo) => (
          
            {todo.title}
             {
                await deleteTodo({ data: todo.id });
                router.invalidate();
              }}
            >
              X
            
          
        ))}
      

      
## Add todo

       {
          e.preventDefault();
          const form = e.currentTarget;
          const data = new FormData(form);
          await addTodo({ data });
          router.invalidate();
          form.reset();
        }}
      >
        
        Add
      
    
  );
}

现在,你已经拥有一个完整的 TanStack Start + SQLite 入门项目,可用于本地开发,也可通过 Haloy 部署到生产环境。按照 Haloy 文档构建 Docker 镜像、为 local.db 配置持久卷,并使用自动 HTTPS 暴露服务。祝编码愉快!

Back to Blog

相关文章

阅读更多 »