// TODO: Add redirect from api-routes to server-routes
服务器路由是 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/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 处理程序负责将传入的请求匹配到服务器路由并执行适当的中间件和处理程序。
请记住,如果您需要自定义服务器处理程序,您可以通过创建自定义处理程序然后将事件传递给 start 处理程序来实现:
// 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 创建一个接受动态 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() 方法返回一个 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!')
},
})