Vite + tRPC
使用 Vite 在 Nitro 中通过 tRPC 实现端到端类型安全的 API。
server/trpc.ts
import { initTRPC } from "@trpc/server";
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
let counter = 0;
const t = initTRPC.create();
export const appRouter = t.router({
get: t.procedure.query(() => {
return { value: counter };
}),
inc: t.procedure.mutation(() => {
counter++;
return { value: counter };
}),
});
export type AppRouter = typeof appRouter;
export default {
async fetch(request: Request): Promise<Response> {
return fetchRequestHandler({
endpoint: "/trpc",
req: request,
router: appRouter,
});
},
};
使用 Vite 和 Nitro 设置 tRPC,实现端到端的类型安全 API,无需代码生成。此示例构建了一个计数器,初始值通过服务器端渲染,客户端进行更新。
概览
配置 Vite,添加 Nitro 插件并路由 tRPC 请求
创建带有过程的 tRPC 路由器
创建带服务器端渲染及客户端交互的 HTML 页面
1. 配置 Vite
添加 Nitro 插件并配置 /trpc/** 路由指向你的 tRPC 处理程序:
vite.config.ts
import { defineConfig } from "vite";
import { nitro } from "nitro/vite";
export default defineConfig({
plugins: [
nitro({
routes: {
"/trpc/**": "./server/trpc.ts",
},
}),
],
});
routes 选项将 URL 模式映射到处理程序文件。所有对 /trpc/* 的请求均由 tRPC 路由器处理。
2. 创建 tRPC 路由器
定义带过程的 tRPC 路由器并将其导出为 fetch 处理函数:
server/trpc.ts
import { initTRPC } from "@trpc/server";
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
let counter = 0;
const t = initTRPC.create();
export const appRouter = t.router({
get: t.procedure.query(() => {
return { value: counter };
}),
inc: t.procedure.mutation(() => {
counter++;
return { value: counter };
}),
});
export type AppRouter = typeof appRouter;
export default {
async fetch(request: Request): Promise<Response> {
return fetchRequestHandler({
endpoint: "/trpc",
req: request,
router: appRouter,
});
},
};
使用 t.procedure.query() 定义读取操作,使用 t.procedure.mutation() 定义写入操作。导出 AppRouter 类型,客户端可获得完整的类型推断。默认导出使用 tRPC 的 fetch 适配器来处理传入请求。
3. 创建 HTML 页面
创建具有服务器端渲染和客户端交互功能的 HTML 页面:
index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>tRPC Counter</title>
<style>
body {
font-family: system-ui, sans-serif;
background: #0f1115;
color: #e5e7eb;
display: grid;
place-items: center;
height: 100vh;
margin: 0;
}
.box {
background: #181b22;
padding: 24px 32px;
border-radius: 10px;
text-align: center;
min-width: 200px;
}
button {
background: #2563eb;
border: none;
color: white;
padding: 8px 14px;
border-radius: 6px;
cursor: pointer;
margin-top: 12px;
font-size: 14px;
}
button:hover {
background: #1d4ed8;
}
.value {
font-size: 36px;
margin: 12px 0;
}
</style>
</head>
<body>
<div class="box">
<div>计数器</div>
<div class="value" id="value">
<script server>
// 服务器端渲染
const { result } = await serverFetch("/trpc/get").then(r => r.json())
echo(result?.data?.value)
</script>
</div>
<button id="inc">递增</button>
</div>
<script setup>
const valueEl = document.getElementById("value");
const incBtn = document.getElementById("inc");
async function call(path, body) {
const res = await fetch(`/trpc/${path}`, {
method: body ? "POST" : "GET",
headers: { "content-type": "application/json" },
body: body ? JSON.stringify(body) : undefined,
});
const json = await res.json();
return json.result.data;
}
async function refresh() {
const data = await call("get");
valueEl.textContent = data.value;
}
incBtn.onclick = async () => {
const data = await call("inc", {});
valueEl.textContent = data.value;
};
refresh();
</script>
</body>
</html>
<script server> 块在服务器上运行,在响应发送前通过 serverFetch 获取初始计数值。<script setup> 块在浏览器中执行,处理递增按钮点击事件。