服务器路由是 TanStack Start 的一项强大功能,允许你在应用程序中创建服务器端端点,对于处理原始 HTTP 请求、表单提交、用户认证等非常有用。
服务器路由可以在项目的 ./src/routes 目录中与你的 TanStack Router 路由并排定义,并由 TanStack Start 服务器自动处理。
这是一个简单的服务器路由示例
// routes/hello.ts
export const ServerRoute = createServerFileRoute().methods({
GET: async ({ request }) => {
return new Response('Hello, World!')
},
})
// routes/hello.ts
export const ServerRoute = createServerFileRoute().methods({
GET: async ({ request }) => {
return new Response('Hello, World!')
},
})
因为服务器路由可以在与应用路由相同的目录中定义,你甚至可以为两者使用同一个文件!
// routes/hello.tsx
export const ServerRoute = createServerFileRoute().methods({
POST: async ({ request }) => {
const body = await request.json()
return new Response(JSON.stringify({ message: `Hello, ${body.name}!` }))
},
})
export const Route = createFileRoute('/hello')({
component: HelloComponent,
})
function HelloComponent() {
const [reply, setReply] = useState('')
return (
<div>
<button
onClick={() => {
fetch('/hello', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name: 'Tanner' }),
})
.then((res) => res.json())
.then((data) => setReply(data.message))
}}
>
Say Hello
</button>
</div>
)
}
// routes/hello.tsx
export const ServerRoute = createServerFileRoute().methods({
POST: async ({ request }) => {
const body = await request.json()
return new Response(JSON.stringify({ message: `Hello, ${body.name}!` }))
},
})
export const Route = createFileRoute('/hello')({
component: HelloComponent,
})
function HelloComponent() {
const [reply, setReply] = useState('')
return (
<div>
<button
onClick={() => {
fetch('/hello', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name: 'Tanner' }),
})
.then((res) => res.json())
.then((data) => setReply(data.message))
}}
>
Say Hello
</button>
</div>
)
}
TanStack Start 中的服务器路由遵循与 TanStack Router 相同的文件式路由约定。这意味着 routes 目录中带有 ServerRoute 导出的每个文件都将被视为 API 路由。这里有一些例子:
每个路由只能关联一个处理程序文件。因此,如果你有一个名为 routes/users.ts 的文件,其请求路径为 /users,则不能有其他也解析到相同路由的文件。例如,以下文件都将解析到相同路由并会报错:
与普通路由一样,服务器路由可以匹配转义字符。例如,名为 routes/users[.]json.ts 的文件将在 /users.json 创建一个 API 路由。
由于统一路由系统,无路径布局路由和分拆路由也支持类似服务器路由中间件的功能。
在上面的示例中,您可能已经注意到文件命名约定是灵活的,允许您混合搭配目录和文件名。这是有意为之的,允许您以适合您应用程序的方式组织服务器路由。您可以在 TanStack Router 文件路由指南中阅读更多相关信息。
服务器路由请求由 Start 的 createStartHandler 在您的 server.ts 入口文件中处理。
// server.ts
import {
createStartHandler,
defaultStreamHandler,
} from '@tanstack/react-start/server'
import { createRouter } from './router'
export default createStartHandler({
createRouter,
})(defaultStreamHandler)
// server.ts
import {
createStartHandler,
defaultStreamHandler,
} from '@tanstack/react-start/server'
import { createRouter } from './router'
export default createStartHandler({
createRouter,
})(defaultStreamHandler)
启动处理程序负责将传入请求与服务器路由匹配,并执行适当的中间件和处理程序。
请记住,如果您需要自定义服务器处理程序,可以通过创建自定义处理程序,然后将事件传递给启动处理程序来完成
// server.ts
import { createStartHandler } from '@tanstack/react-start/server'
export default defineHandler((event) => {
const startHandler = createStartHandler({
createRouter,
})(defaultStreamHandler)
return startHandler(event)
})
// server.ts
import { createStartHandler } from '@tanstack/react-start/server'
export default defineHandler((event) => {
const startHandler = createStartHandler({
createRouter,
})(defaultStreamHandler)
return startHandler(event)
})
服务器路由是通过从路由文件导出 ServerRoute 来创建的。ServerRoute 导出应通过调用 createServerFileRoute 函数创建。然后可以使用生成的构建器对象来:
// routes/hello.ts
export const ServerRoute = createServerFileRoute().methods({
GET: async ({ request }) => {
return new Response('Hello, World! from ' + request.url)
},
})
// routes/hello.ts
export const ServerRoute = createServerFileRoute().methods({
GET: async ({ request }) => {
return new Response('Hello, World! from ' + request.url)
},
})
有两种方法可以为服务器路由定义处理程序。
对于简单的用例,您可以直接向该方法提供一个处理函数。
// routes/hello.ts
export const ServerRoute = createServerFileRoute().methods({
GET: async ({ request }) => {
return new Response('Hello, World! from ' + request.url)
},
})
// routes/hello.ts
export const ServerRoute = createServerFileRoute().methods({
GET: async ({ request }) => {
return new Response('Hello, World! from ' + request.url)
},
})
对于更复杂的用例,您可以通过方法构建器对象提供处理程序函数。这允许您向该方法添加中间件。
// routes/hello.ts
export const ServerRoute = createServerFileRoute().methods((api) => ({
GET: api.middleware([loggerMiddleware]).handler(async ({ request }) => {
return new Response('Hello, World! from ' + request.url)
}),
}))
// routes/hello.ts
export const ServerRoute = createServerFileRoute().methods((api) => ({
GET: api.middleware([loggerMiddleware]).handler(async ({ request }) => {
return new Response('Hello, World! from ' + request.url)
}),
}))
每个 HTTP 方法处理程序接收一个包含以下属性的对象:
处理完请求后,您可以返回一个 Response 对象或 Promise<Response>,甚至可以使用 @tanstack/react-start 中的任何辅助函数来操作响应。
服务器路由支持动态路径参数的方式与 TanStack Router 相同。例如,名为 routes/users/$id.ts 的文件将在 /users/$id 创建一个接受动态 id 参数的 API 路由。
// routes/users/$id.ts
export const ServerRoute = createServerFileRoute().methods({
GET: async ({ params }) => {
const { id } = params
return new Response(`User ID: ${id}`)
},
})
// Visit /users/123 to see the response
// User ID: 123
// routes/users/$id.ts
export const ServerRoute = createServerFileRoute().methods({
GET: async ({ params }) => {
const { id } = params
return new Response(`User ID: ${id}`)
},
})
// Visit /users/123 to see the response
// User ID: 123
您也可以在单个路由中包含多个动态路径参数。例如,名为 routes/users/$id/posts/$postId.ts 的文件将在 /users/$id/posts/$postId 创建一个接受两个动态参数的 API 路由。
// routes/users/$id/posts/$postId.ts
export const ServerRoute = createServerFileRoute().methods({
GET: async ({ params }) => {
const { id, postId } = params
return new Response(`User ID: ${id}, Post ID: ${postId}`)
},
})
// Visit /users/123/posts/456 to see the response
// User ID: 123, Post ID: 456
// routes/users/$id/posts/$postId.ts
export const ServerRoute = createServerFileRoute().methods({
GET: async ({ params }) => {
const { id, postId } = params
return new Response(`User ID: ${id}, Post ID: ${postId}`)
},
})
// Visit /users/123/posts/456 to see the response
// User ID: 123, Post ID: 456
服务器路由也支持路径末尾的通配符参数,由一个 $ 符号后没有任何字符表示。例如,名为 routes/file/$.ts 的文件将在 /file/$ 创建一个接受通配符参数的 API 路由。
// routes/file/$.ts
export const ServerRoute = createServerFileRoute().methods({
GET: async ({ params }) => {
const { _splat } = params
return new Response(`File: ${_splat}`)
},
})
// Visit /file/hello.txt to see the response
// File: hello.txt
// routes/file/$.ts
export const ServerRoute = createServerFileRoute().methods({
GET: async ({ params }) => {
const { _splat } = params
return new Response(`File: ${_splat}`)
},
})
// Visit /file/hello.txt to see the response
// File: hello.txt
要处理 POST 请求,您可以向路由对象添加一个 POST 处理程序。该处理程序将接收请求对象作为第一个参数,您可以使用 request.json() 方法访问请求体。
// routes/hello.ts
export const ServerRoute = createServerFileRoute().methods({
POST: async ({ request }) => {
const body = await request.json()
return new Response(`Hello, ${body.name}!`)
},
})
// Send a POST request to /hello with a JSON body like { "name": "Tanner" }
// Hello, Tanner!
// routes/hello.ts
export const ServerRoute = createServerFileRoute().methods({
POST: async ({ request }) => {
const body = await request.json()
return new Response(`Hello, ${body.name}!`)
},
})
// Send a POST request to /hello with a JSON body like { "name": "Tanner" }
// Hello, Tanner!
这也适用于其他 HTTP 方法,如 PUT、PATCH 和 DELETE。您可以在路由对象中为这些方法添加处理程序,并使用适当的方法访问请求体。
重要的是要记住,request.json() 方法返回一个解析为请求的解析 JSON 主体的 Promise。您需要 await 结果才能访问主体。
这是在服务器路由中处理 POST 请求的常见模式。您也可以使用其他方法,例如 request.text() 或 request.formData() 来访问请求体。
当使用 Response 对象返回 JSON 时,这是一个常见的模式
// routes/hello.ts
export const ServerRoute = createServerFileRoute().methods({
GET: async ({ request }) => {
return new Response(JSON.stringify({ message: 'Hello, World!' }), {
headers: {
'Content-Type': 'application/json',
},
})
},
})
// Visit /hello to see the response
// {"message":"Hello, World!"}
// routes/hello.ts
export const ServerRoute = createServerFileRoute().methods({
GET: async ({ request }) => {
return new Response(JSON.stringify({ message: 'Hello, World!' }), {
headers: {
'Content-Type': 'application/json',
},
})
},
})
// Visit /hello to see the response
// {"message":"Hello, World!"}
或者您可以使用 json 辅助函数自动将 Content-Type 标头设置为 application/json 并为您序列化 JSON 对象。
// routes/hello.ts
import { json } from '@tanstack/react-start'
export const ServerRoute = createServerFileRoute().methods({
GET: async ({ request }) => {
return json({ message: 'Hello, World!' })
},
})
// Visit /hello to see the response
// {"message":"Hello, World!"}
// routes/hello.ts
import { json } from '@tanstack/react-start'
export const ServerRoute = createServerFileRoute().methods({
GET: async ({ request }) => {
return json({ message: 'Hello, World!' })
},
})
// Visit /hello to see the response
// {"message":"Hello, World!"}
您可以通过以下两种方式设置响应状态码:
将其作为第二个参数的属性传递给 Response 构造函数
// routes/hello.ts
import { json } from '@tanstack/react-start'
export const ServerRoute = createServerFileRoute().methods({
GET: async ({ request, params }) => {
const user = await findUser(params.id)
if (!user) {
return new Response('User not found', {
status: 404,
})
}
return json(user)
},
})
// routes/hello.ts
import { json } from '@tanstack/react-start'
export const ServerRoute = createServerFileRoute().methods({
GET: async ({ request, params }) => {
const user = await findUser(params.id)
if (!user) {
return new Response('User not found', {
status: 404,
})
}
return json(user)
},
})
使用 @tanstack/react-start/server 中的 setResponseStatus 辅助函数
// routes/hello.ts
import { json } from '@tanstack/react-start'
import { setResponseStatus } from '@tanstack/react-start/server'
export const ServerRoute = createServerFileRoute().methods({
GET: async ({ request, params }) => {
const user = await findUser(params.id)
if (!user) {
setResponseStatus(404)
return new Response('User not found')
}
return json(user)
},
})
// routes/hello.ts
import { json } from '@tanstack/react-start'
import { setResponseStatus } from '@tanstack/react-start/server'
export const ServerRoute = createServerFileRoute().methods({
GET: async ({ request, params }) => {
const user = await findUser(params.id)
if (!user) {
setResponseStatus(404)
return new Response('User not found')
}
return json(user)
},
})
在此示例中,如果未找到用户,我们将返回 404 状态码。您可以使用此方法设置任何有效的 HTTP 状态码。
有时您可能需要设置响应头。您可以通过以下两种方式完成此操作:
将一个对象作为第二个参数传递给 Response 构造函数。
// routes/hello.ts
export const ServerRoute = createServerFileRoute().methods({
GET: async ({ request }) => {
return new Response('Hello, World!', {
headers: {
'Content-Type': 'text/plain',
},
})
},
})
// Visit /hello to see the response
// Hello, World!
// routes/hello.ts
export const ServerRoute = createServerFileRoute().methods({
GET: async ({ request }) => {
return new Response('Hello, World!', {
headers: {
'Content-Type': 'text/plain',
},
})
},
})
// Visit /hello to see the response
// Hello, World!
或使用 @tanstack/react-start/server 中的 setHeaders 辅助函数。
// routes/hello.ts
import { setHeaders } from '@tanstack/react-start/server'
export const ServerRoute = createServerFileRoute().methods({
GET: async ({ request }) => {
setHeaders({
'Content-Type': 'text/plain',
})
return new Response('Hello, World!')
},
})
// routes/hello.ts
import { setHeaders } from '@tanstack/react-start/server'
export const ServerRoute = createServerFileRoute().methods({
GET: async ({ request }) => {
setHeaders({
'Content-Type': 'text/plain',
})
return new Response('Hello, World!')
},
})
您的每周 JavaScript 资讯。每周一免费发送给超过 10 万开发者。