Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

服务器路由 (Server Routes)

服务器路由是 TanStack Start 的一项强大功能,它允许你在应用程序中创建服务端端点。这对于处理原始 HTTP 请求、表单提交、用户身份验证等场景非常有用。

服务器路由可以定义在项目的 ./src/routes 目录中,与你的 TanStack Router 路由并排存放,并由 TanStack Start 服务器自动处理。

以下是一个简单的服务器路由示例:

// routes/hello.ts
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/hello")({
  server: {
    handlers: {
      GET: async ({ request }) => {
        return new Response("Hello, World!");
      },
    },
  },
});

服务器路由与应用路由共存

由于服务器路由可以与应用路由定义在同一个目录中,你甚至可以在同一个文件中同时编写两者!

// routes/hello.tsx
import { useState } from "react";
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/hello")({
  server: {
    handlers: {
      POST: async ({ request }) => {
        const body = await request.json();
        return new Response(JSON.stringify({ message: `你好, ${body.name}!` }));
      },
    },
  },
  component: HelloComponent,
});

function HelloComponent() {
  const [reply, setReply] = useState("");

  return (
    <div>
      <button
        className="px-4 py-2 bg-blue-500 text-white rounded"
        onClick={() => {
          fetch("/hello", {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
            body: JSON.stringify({ name: "Tanner" }),
          })
            .then((res) => res.json())
            .then((data) => setReply(data.message));
        }}
      >
        打个招呼
      </button>
      {reply && <p>{reply}</p>}
    </div>
  );
}

文件路由约定

TanStack Start 中的服务器路由遵循与 TanStack Router 相同的基于文件的路由约定。这意味着 routes 目录下的任何文件中,只要在 createFileRoute 调用中包含 server 属性,就会被视为 API 路由。以下是一些示例:


唯一路由路径

每个路由路径只能有一个关联的处理程序文件。如果你有一个名为 routes/users.ts 的文件(对应路径 /users),你不能再拥有其他同样解析为该路径的文件。例如,以下文件如果同时存在将会导致错误:


路径无关布局路由与跳出路由

得益于统一的路由系统,服务器路由同样支持路径无关布局路由(Pathless layout routes)和跳出路由(Break-out routes),这在处理中间件时非常有用:


处理服务器路由请求

默认情况下,服务器路由请求由 Start 自动处理。如果你自定义了 src/server.ts 入口文件,则由 createStartHandler 负责。

Start 处理程序负责将传入请求匹配到对应的服务器路由,并执行相关的中间件和处理函数。

如果你需要自定义服务器处理逻辑,可以创建一个自定义处理程序并将事件传递给 Start 处理程序。详情请参阅 服务器入口点


定义服务器路由

通过在 createFileRoute 调用中添加 server 属性来创建服务器路由。server 属性包含:

// routes/hello.ts
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/hello")({
  server: {
    handlers: {
      GET: async ({ request }) => {
        return new Response("来自 " + request.url + " 的问候!");
      },
    },
  },
});

定义服务器路由处理函数 (Handlers)

你可以通过两种方式定义处理函数:

简单处理函数

对于简单的场景,你可以直接在 handlers 对象中提供函数。

// routes/hello.ts
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/hello")({
  server: {
    handlers: {
      GET: async ({ request }) => {
        return new Response("来自 " + request.url + " 的问候!");
      },
    },
  },
});

为特定处理函数添加中间件

对于更复杂的场景,你可以为特定的 HTTP 方法添加中间件。这需要使用 createHandlers 函数:

// routes/hello.ts
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/hello")({
  server: {
    handlers: ({ createHandlers }) =>
      createHandlers({
        GET: {
          middleware: [loggerMiddleware], // 仅适用于 GET 请求
          handler: async ({ request }) => {
            return new Response("来自 " + request.url + " 的问候!");
          },
        },
      }),
  },
});

为所有处理函数添加中间件

你也可以在 server 级别使用 middleware 属性,添加适用于该路由下所有处理函数的中间件:

// routes/hello.ts
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/hello")({
  server: {
    middleware: [authMiddleware, loggerMiddleware], // 适用于所有处理函数
    handlers: {
      GET: async ({ request }) => {
        return new Response("你好,世界!来自 " + request.url);
      },
      POST: async ({ request }) => {
        const body = await request.json();
        return new Response(`你好, ${body.name}!`);
      },
    },
  },
});

结合路由级与函数级中间件

你可以结合这两种方法 —— 路由级中间件将首先运行,随后是特定处理函数的中间件:

// routes/hello.ts
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/hello")({
  server: {
    middleware: [authMiddleware], // 首先为所有处理函数运行
    handlers: ({ createHandlers }) =>
      createHandlers({
        GET: async ({ request }) => {
          return new Response("你好,世界!");
        },
        POST: {
          middleware: [validationMiddleware], // 在 authMiddleware 之后运行,且仅针对 POST
          handler: async ({ request }) => {
            const body = await request.json();
            return new Response(`你好, ${body.name}!`);
          },
        },
      }),
  },
});

处理函数上下文 (Handler Context)

每个 HTTP 方法处理函数都会接收到一个包含以下属性的对象:

处理完请求后,你可以返回一个 Response 对象、Promise<Response>,甚至可以使用 @tanstack/react-start 提供的任何辅助函数来操作响应。


动态路径参数 (Dynamic Path Params)

服务器路由支持动态路径参数,方式与 TanStack Router 完全一致。例如,名为 routes/users/$id.ts 的文件将创建一个接受动态 id 参数的 API 路由 /users/$id

// routes/users/$id.ts
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/users/$id")({
  server: {
    handlers: {
      GET: async ({ params }) => {
        const { id } = params; // 从参数中结构出 ID
        return new Response(`用户 ID: ${id}`);
      },
    },
  },
});

// 访问 /users/123 将看到响应:
// 用户 ID: 123

你也可以在单个路由中使用多个动态路径参数:

// routes/users/$id/posts/$postId.ts
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/users/$id/posts/$postId")({
  server: {
    handlers: {
      GET: async ({ params }) => {
        const { id, postId } = params;
        return new Response(`用户 ID: ${id}, 帖子 ID: ${postId}`);
      },
    },
  },
});

// 访问 /users/123/posts/456 将看到响应:
// 用户 ID: 123, 帖子 ID: 456

通配符参数 (Wildcard/Splat Param)

服务器路由还支持在路径末尾使用通配符参数,由 $ 后接空内容表示。例如,名为 routes/file/$.ts 的文件将创建一个接受通配符参数的 API 路由 /file/$

// routes/file/$.ts
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/file/$")({
  server: {
    handlers: {
      GET: async ({ params }) => {
        // 通配符匹配的内容会存储在 _splat 属性中
        const { _splat } = params;
        return new Response(`文件路径: ${_splat}`);
      },
    },
  },
});

// 访问 /file/hello.txt 将看到响应:
// 文件路径: hello.txt

处理带有请求体 (Body) 的请求

要处理 POST 请求,只需在 handlers 对象中添加 POST 处理函数即可。该函数会接收请求对象,你可以通过 request.json() 方法访问请求体。

// routes/hello.ts
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/hello")({
  server: {
    handlers: {
      POST: async ({ request }) => {
        const body = await request.json();
        return new Response(`你好, ${body.name}!`);
      },
    },
  },
});

// 向 /hello 发送一个带有 JSON 请求体(如 { "name": "Tanner" })的 POST 请求
// 响应内容:你好, Tanner!

这也适用于其他 HTTP 方法,如 PUTPATCHDELETE。你可以添加相应的处理函数,并使用适当的方法访问请求体。

注意request.json() 返回一个 Promise,因此你需要使用 await。根据需要,你也可以使用 request.text()request.formData()


响应 JSON 数据

使用 Response 对象返回 JSON 时,通常采用以下模式:

// routes/hello.ts
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/hello")({
  server: {
    handlers: {
      GET: async ({ request }) => {
        return new Response(JSON.stringify({ message: "你好,世界!" }), {
          headers: {
            "Content-Type": "application/json",
          },
        });
      },
    },
  },
});

使用 Response.json 辅助函数

或者,你可以使用 Response.json 辅助函数,它会自动为你设置 Content-Type: application/json 并序列化对象。

// routes/hello.ts
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/hello")({
  server: {
    handlers: {
      GET: async ({ request }) => {
        return Response.json({ message: "你好,世界!" });
      },
    },
  },
});

响应状态码 (Status Code)

你可以通过 Response 构造函数的第二个参数来设置响应的状态码。

// routes/hello.ts
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/hello")({
  server: {
    handlers: {
      GET: async ({ request, params }) => {
        const user = await findUser(params.id);
        if (!user) {
          return new Response("未找到用户", {
            status: 404,
          });
        }
        return Response.json(user);
      },
    },
  },
});

在上面的示例中,如果未找到用户,我们会返回 404 状态码。你可以使用此方法设置任何有效的 HTTP 状态码。


设置响应头 (Headers)

有时你需要自定义响应头。可以通过向 Response 构造函数的第二个参数传递 headers 对象来实现。

// routes/hello.ts
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/hello")({
  server: {
    handlers: {
      GET: async ({ request }) => {
        return new Response("你好,世界!", {
          headers: {
            "Content-Type": "text/plain",
            "Cache-Control": "no-cache",
          },
        });
      },
    },
  },
});