API 路由

API 路由是 TanStack Start 的一项强大功能,允许你在应用程序中创建服务端端点,而无需单独的服务器。API 路由对于处理表单提交、用户身份验证等非常有用。

默认情况下,API 路由在项目的 ./app/routes/api 目录中定义,并由 TanStack Start 服务器自动处理。

🧠 这意味着默认情况下,你的 API 路由将以 /api 为前缀,并与你的应用程序在同一服务器上提供。你可以通过更改 TanStack Start 配置中的 server.apiBaseURL 来自定义此基本路径。

文件路由约定

TanStack Start 中的 API 路由遵循与 TanStack Router 相同的文件路由约定。这意味着你的 routes 目录中以 api 为前缀(可以配置)的每个文件都将被视为 API 路由。以下是一些示例

  • routes/api.users.ts 将在 /api/users 创建一个 API 路由
  • routes/api/users.ts/api/users 创建一个 API 路由
  • routes/api/users.index.ts/api/users 创建一个 API 路由
  • routes/api/users/$id.ts 将在 /api/users/$id 创建一个 API 路由
  • routes/api/users/$id/posts.ts 将在 /api/users/$id/posts 创建一个 API 路由
  • routes/api.users.$id.posts.ts/api/users/$id/posts 创建一个 API 路由
  • routes/api/file/$.ts 将在 /api/file/$ 创建一个 API 路由

你的以 api 为前缀的路由文件,可以被认为是给定 API 路由路径的处理器。

重要的是要记住,每个路由只能有一个关联的处理器文件。因此,如果你有一个名为 routes/api/users.ts 的文件,它等于请求路径 /api/users,你不能有其他也解析到同一路由的文件,例如

  • routes/api/users.index.ts
  • routes/api.users.ts.
  • routes/api.users.index.ts.

❗ 还有一件事,API 路由没有无路径布局路由或并行路由的概念。因此,名为

  • routes/api/_pathlessLayout/users.ts 的文件将解析为 /api/_pathlessLayout/users,而不是 /api/users

嵌套目录 vs 文件名

在上面的示例中,你可能已经注意到文件命名约定很灵活,允许你混合和匹配目录和文件名。这是故意的,允许你以对你的应用程序有意义的方式组织你的 API 路由。你可以在 TanStack Router 基于文件的路由指南中阅读更多相关信息。

设置入口处理器

在你可以创建 API 路由之前,你需要为你的 TanStack Start 项目设置入口处理器。这个入口处理器,类似于 clientssr,处理传入的 API 请求并将它们路由到适当的 API 路由处理器。API 入口处理器在你的项目的 app/api.ts 文件中定义。

这是一个示例实现

ts
// app/api.ts
import {
  createStartAPIHandler,
  defaultAPIFileRouteHandler,
} from '@tanstack/react-start/api'

export default createStartAPIHandler(defaultAPIFileRouteHandler)
// app/api.ts
import {
  createStartAPIHandler,
  defaultAPIFileRouteHandler,
} from '@tanstack/react-start/api'

export default createStartAPIHandler(defaultAPIFileRouteHandler)

此文件负责创建 API 处理器,该处理器将用于将传入请求路由到适当的 API 路由处理器。defaultAPIFileRouteHandler 是一个辅助函数,它将根据传入请求自动加载和执行适当的 API 路由处理器。

定义 API 路由

API 路由通过调用 createAPIFileRoute 函数导出 APIRoute 实例。与 TanStack Router 中的其他基于文件的路由类似,此函数的第一个参数是路由的路径。返回的函数再次被调用,并带有一个对象,该对象定义了每个 HTTP 方法的路由处理器。

提示

如果你已经运行了开发服务器,当你创建一个新的 API 路由时,它将自动为你设置初始处理器。从那时起,你可以根据需要自定义处理器。

注意

导出变量必须命名为 APIRoute,否则结果响应将是 404 not found

ts
// routes/api/hello.ts
import { createAPIFileRoute } from '@tanstack/react-start/api'

export const APIRoute = createAPIFileRoute('/api/hello')({
  GET: async ({ request }) => {
    return new Response('Hello, World! from ' + request.url)
  },
})
// routes/api/hello.ts
import { createAPIFileRoute } from '@tanstack/react-start/api'

export const APIRoute = createAPIFileRoute('/api/hello')({
  GET: async ({ request }) => {
    return new Response('Hello, World! from ' + request.url)
  },
})

每个 HTTP 方法处理器都会收到一个具有以下属性的对象

  • request:传入的请求对象。你可以在 MDN Web 文档中阅读有关 Request 对象的更多信息。
  • params:一个包含路由动态路径参数的对象。例如,如果路由路径是 /users/$id,并且请求发送到 /users/123,则 params 将是 { id: '123' }。我们将在本指南的后面部分介绍动态路径参数和通配符参数。

一旦你处理了请求,你需要返回一个 Response 对象或 Promise<Response>。这可以通过创建一个新的 Response 对象并从处理器返回它来完成。你可以在 MDN Web 文档中阅读有关 Response 对象的更多信息。

动态路径参数

API 路由支持动态路径参数,动态路径参数用 $ 后跟参数名称表示。例如,名为 routes/api/users/$id.ts 的文件将在 /api/users/$id 创建一个 API 路由,该路由接受动态 id 参数。

ts
// routes/api/users/$id.ts
import { createAPIFileRoute } from '@tanstack/react-start/api'

export const APIRoute = createAPIFileRoute('/users/$id')({
  GET: async ({ params }) => {
    const { id } = params
    return new Response(`User ID: ${id}`)
  },
})

// Visit /api/users/123 to see the response
// User ID: 123
// routes/api/users/$id.ts
import { createAPIFileRoute } from '@tanstack/react-start/api'

export const APIRoute = createAPIFileRoute('/users/$id')({
  GET: async ({ params }) => {
    const { id } = params
    return new Response(`User ID: ${id}`)
  },
})

// Visit /api/users/123 to see the response
// User ID: 123

你还可以在单个路由中包含多个动态路径参数。例如,名为 routes/api/users/$id/posts/$postId.ts 的文件将在 /api/users/$id/posts/$postId 创建一个 API 路由,该路由接受两个动态参数。

ts
// routes/api/users/$id/posts/$postId.ts
import { createAPIFileRoute } from '@tanstack/react-start/api'

export const APIRoute = createAPIFileRoute('/users/$id/posts/$postId')({
  GET: async ({ params }) => {
    const { id, postId } = params
    return new Response(`User ID: ${id}, Post ID: ${postId}`)
  },
})

// Visit /api/users/123/posts/456 to see the response
// User ID: 123, Post ID: 456
// routes/api/users/$id/posts/$postId.ts
import { createAPIFileRoute } from '@tanstack/react-start/api'

export const APIRoute = createAPIFileRoute('/users/$id/posts/$postId')({
  GET: async ({ params }) => {
    const { id, postId } = params
    return new Response(`User ID: ${id}, Post ID: ${postId}`)
  },
})

// Visit /api/users/123/posts/456 to see the response
// User ID: 123, Post ID: 456

通配符/Splat 参数

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

ts
// routes/api/file/$.ts
import { createAPIFileRoute } from '@tanstack/react-start/api'

export const APIRoute = createAPIFileRoute('/file/$')({
  GET: async ({ params }) => {
    const { _splat } = params
    return new Response(`File: ${_splat}`)
  },
})

// Visit /api/file/hello.txt to see the response
// File: hello.txt
// routes/api/file/$.ts
import { createAPIFileRoute } from '@tanstack/react-start/api'

export const APIRoute = createAPIFileRoute('/file/$')({
  GET: async ({ params }) => {
    const { _splat } = params
    return new Response(`File: ${_splat}`)
  },
})

// Visit /api/file/hello.txt to see the response
// File: hello.txt

处理带请求体的请求

要处理 POST 请求,你可以向路由对象添加一个 POST 处理器。处理器将接收请求对象作为第一个参数,你可以使用 request.json() 方法访问请求体。

ts
// routes/api/hello.ts
import { createAPIFileRoute } from '@tanstack/react-start/api'

export const APIRoute = createAPIFileRoute('/api/hello')({
  POST: async ({ request }) => {
    const body = await request.json()
    return new Response(`Hello, ${body.name}!`)
  },
})

// Send a POST request to /api/hello with a JSON body like { "name": "Tanner" }
// Hello, Tanner!
// routes/api/hello.ts
import { createAPIFileRoute } from '@tanstack/react-start/api'

export const APIRoute = createAPIFileRoute('/api/hello')({
  POST: async ({ request }) => {
    const body = await request.json()
    return new Response(`Hello, ${body.name}!`)
  },
})

// Send a POST request to /api/hello with a JSON body like { "name": "Tanner" }
// Hello, Tanner!

这也适用于其他 HTTP 方法,如 PUTPATCHDELETE。你可以在路由对象中为这些方法添加处理器,并使用适当的方法访问请求体。

重要的是要记住,request.json() 方法返回一个 Promise,它解析为请求的已解析 JSON 体。你需要 await 结果才能访问请求体。

这是在 API 路由中处理 POST 请求的常见模式。你还可以使用其他方法,如 request.text()request.formData() 来访问请求体。

响应 JSON

当使用 Response 对象返回 JSON 时,这是一种常见模式

ts
// routes/api/hello.ts
import { createAPIFileRoute } from '@tanstack/react-start/api'

export const APIRoute = createAPIFileRoute('/api/hello')({
  GET: async ({ request }) => {
    return new Response(JSON.stringify({ message: 'Hello, World!' }), {
      headers: {
        'Content-Type': 'application/json',
      },
    })
  },
})

// Visit /api/hello to see the response
// {"message":"Hello, World!"}
// routes/api/hello.ts
import { createAPIFileRoute } from '@tanstack/react-start/api'

export const APIRoute = createAPIFileRoute('/api/hello')({
  GET: async ({ request }) => {
    return new Response(JSON.stringify({ message: 'Hello, World!' }), {
      headers: {
        'Content-Type': 'application/json',
      },
    })
  },
})

// Visit /api/hello to see the response
// {"message":"Hello, World!"}

使用 json 辅助函数

或者你可以使用 json 辅助函数来自动将 Content-Type 标头设置为 application/json 并为你序列化 JSON 对象。

ts
// routes/api/hello.ts
import { json } from '@tanstack/react-start'
import { createAPIFileRoute } from '@tanstack/react-start/api'

export const APIRoute = createAPIFileRoute('/api/hello')({
  GET: async ({ request }) => {
    return json({ message: 'Hello, World!' })
  },
})

// Visit /api/hello to see the response
// {"message":"Hello, World!"}
// routes/api/hello.ts
import { json } from '@tanstack/react-start'
import { createAPIFileRoute } from '@tanstack/react-start/api'

export const APIRoute = createAPIFileRoute('/api/hello')({
  GET: async ({ request }) => {
    return json({ message: 'Hello, World!' })
  },
})

// Visit /api/hello to see the response
// {"message":"Hello, World!"}

响应状态码

你可以通过以下任一方式设置响应的状态码

  • 将其作为第二个参数的属性传递给 Response 构造函数

    ts
    // routes/api/hello.ts
    import { json } from '@tanstack/react-start'
    import { createAPIFileRoute } from '@tanstack/react-start/api'
    
    export const APIRoute = createAPIFileRoute('/users/$id')({
      GET: async ({ request, params }) => {
        const user = await findUser(params.id)
        if (!user) {
          return new Response('User not found', {
            status: 404,
          })
        }
        return json(user)
      },
    })
    
    // routes/api/hello.ts
    import { json } from '@tanstack/react-start'
    import { createAPIFileRoute } from '@tanstack/react-start/api'
    
    export const APIRoute = createAPIFileRoute('/users/$id')({
      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/serversetResponseStatus 辅助函数

    ts
    // routes/api/hello.ts
    import { json } from '@tanstack/react-start'
    import { createAPIFileRoute } from '@tanstack/react-start/api'
    import { setResponseStatus } from '@tanstack/react-start/server'
    
    export const APIRoute = createAPIFileRoute('/users/$id')({
      GET: async ({ request, params }) => {
        const user = await findUser(params.id)
        if (!user) {
          setResponseStatus(404)
          return new Response('User not found')
        }
        return json(user)
      },
    })
    
    // routes/api/hello.ts
    import { json } from '@tanstack/react-start'
    import { createAPIFileRoute } from '@tanstack/react-start/api'
    import { setResponseStatus } from '@tanstack/react-start/server'
    
    export const APIRoute = createAPIFileRoute('/users/$id')({
      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 构造函数。

    ts
    // routes/api/hello.ts
    import { createAPIFileRoute } from '@tanstack/react-start/api'
    
    export const APIRoute = createAPIFileRoute('/api/hello')({
      GET: async ({ request }) => {
        return new Response('Hello, World!', {
          headers: {
            'Content-Type': 'text/plain',
          },
        })
      },
    })
    
    // Visit /api/hello to see the response
    // Hello, World!
    
    // routes/api/hello.ts
    import { createAPIFileRoute } from '@tanstack/react-start/api'
    
    export const APIRoute = createAPIFileRoute('/api/hello')({
      GET: async ({ request }) => {
        return new Response('Hello, World!', {
          headers: {
            'Content-Type': 'text/plain',
          },
        })
      },
    })
    
    // Visit /api/hello to see the response
    // Hello, World!
    
  • 或使用来自 @tanstack/react-start/serversetHeaders 辅助函数。

    ts
    // routes/api/hello.ts
    import { createAPIFileRoute } from '@tanstack/react-start/api'
    import { setHeaders } from '@tanstack/react-start/server'
    
    export const APIRoute = createAPIFileRoute('/api/hello')({
      GET: async ({ request }) => {
        setHeaders({
          'Content-Type': 'text/plain',
        })
        return new Response('Hello, World!')
      },
    })
    
    // routes/api/hello.ts
    import { createAPIFileRoute } from '@tanstack/react-start/api'
    import { setHeaders } from '@tanstack/react-start/server'
    
    export const APIRoute = createAPIFileRoute('/api/hello')({
      GET: async ({ request }) => {
        setHeaders({
          'Content-Type': 'text/plain',
        })
        return new Response('Hello, World!')
      },
    })
    
订阅 Bytes

你的每周 JavaScript 新闻。每周一免费发送给超过 10 万名开发者。

Bytes

无垃圾邮件。随时取消订阅。