服务器路由是 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 路由。
由于统一的路由系统,无布局路由和 breakout 路由支持用于围绕服务器路由中间件的类似功能。
在上面的示例中,你可能已经注意到文件名约定很灵活,允许你混合使用目录和文件名。这是有意的,并且允许你以适合你应用程序的方式组织你的服务器路由。你可以在 TanStack Router 文件路由指南 中阅读更多相关信息。
服务器路由请求由 Start 的 createStartHandler 在你的 server.ts 入口文件中处理。
// server.ts
import {
createStartHandler,
defaultStreamHandler,
} from '@tanstack/solid-start/server'
import { createRouter } from './router'
export default createStartHandler({
createRouter,
})(defaultStreamHandler)
// server.ts
import {
createStartHandler,
defaultStreamHandler,
} from '@tanstack/solid-start/server'
import { createRouter } from './router'
export default createStartHandler({
createRouter,
})(defaultStreamHandler)
Start handler 负责将传入的请求匹配到服务器路由并执行相应的中间件和处理程序。
请记住,如果你需要自定义服务器处理程序,可以通过创建一个自定义处理程序,然后将事件传递给 Start handler 来实现。
// server.ts
import { createStartHandler } from '@tanstack/solid-start/server'
export default defineHandler((event) => {
const startHandler = createStartHandler({
createRouter,
})(defaultStreamHandler)
return startHandler(event)
})
// server.ts
import { createStartHandler } from '@tanstack/solid-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/solid-start 中的任何辅助函数来操作响应。
服务器路由支持动态路径参数,方式与 TanStack Router 相同。例如,一个名为 routes/users/$id.ts 的文件将在 /users/$id 创建一个 API 路由,该路由接受一个动态 id 参数。
// 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() 方法返回一个 Promise,该 Promise 解析为请求的解析后的 JSON 主体。你需要 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/solid-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/solid-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/solid-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/solid-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/solid-start/server` 的 `setResponseStatus` 辅助函数
// routes/hello.ts
import { json } from '@tanstack/solid-start'
import { setResponseStatus } from '@tanstack/solid-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/solid-start'
import { setResponseStatus } from '@tanstack/solid-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/solid-start/server` 的 `setHeaders` 辅助函数。
// routes/hello.ts
import { setHeaders } from '@tanstack/solid-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/solid-start/server'
export const ServerRoute = createServerFileRoute().methods({
GET: async ({ request }) => {
setHeaders({
'Content-Type': 'text/plain',
})
return new Response('Hello, World!')
},
})
您的每周 JavaScript 资讯。每周一免费发送给超过 10 万开发者。