TanStack Start 中文文档
服务器与执行

中间件(Middleware)

什么是中间件?

中间件(Middleware)允许你定制服务器路由(如 GET/POST 等,包括对你的应用进行 SSR 的请求)和用 createServerFn 创建的服务器函数的行为。中间件是可组合的,甚至可以依赖其他中间件,形成一个按层级、按顺序执行的操作链。

我能用中间件做哪些事情?

  • 认证(Authentication):在执行服务器函数之前验证用户身份。
  • 授权(Authorization):检查用户是否拥有执行服务器函数所需的权限。
  • 日志记录(Logging):记录请求、响应和错误。
  • CSP:配置内容安全策略(Content Security Policy)和其他安全措施。
  • 可观测性(Observability):收集指标、追踪和日志。
  • 提供上下文(Provide Context):把数据附加到请求对象上,供其他中间件或服务器函数使用。
  • 错误处理(Error Handling):以一致的方式处理错误。
  • 还有很多!一切皆有可能!

中间件类型

有两种中间件:请求中间件服务器函数中间件

  • 请求中间件用于定制经过它的任何服务器请求的行为,包括服务器函数。
  • 服务器函数中间件专门用于定制服务器函数的行为。

注意

服务器函数中间件是请求中间件的一个子集,它拥有专门为服务器函数设计的额外功能,比如能够校验输入数据,或在服务器函数执行前后执行客户端逻辑。

关键区别

特性请求中间件服务器函数中间件
作用范围所有服务器请求仅服务器函数
方法.server().client().server()
输入校验有(.validator()
客户端逻辑
依赖可以依赖请求中间件可以依赖两种类型

注意

请求中间件不能依赖服务器函数中间件,但服务器函数中间件可以依赖请求中间件。

核心概念

中间件组合

所有中间件都是可组合的,也就是说一个中间件可以依赖另一个中间件。

import { createMiddleware } from '@tanstack/react-start'

const loggingMiddleware = createMiddleware().server(() => {
  //...
})

const authMiddleware = createMiddleware()
  .middleware([loggingMiddleware])
  .server(() => {
    //...
  })

推进中间件链

中间件是通过 next() 逐级推进的(next-able),也就是说你必须在 .server 方法(如果你是创建服务器函数中间件,还包括 .client 方法)中调用 next 函数,才能执行链中的下一个中间件。这让你可以:

  • 短路(short circuit)中间件链并提前返回
  • 把数据传给下一个中间件
  • 访问下一个中间件的结果
  • 把上下文传给包裹它的中间件
import { createMiddleware } from '@tanstack/react-start'

const loggingMiddleware = createMiddleware().server(async ({ next }) => {
  const result = await next() // <-- This will execute the next middleware in the chain
  return result
})

译者注:next() 就像洋葱模型

中间件的执行模型是「洋葱模型」:.server()next() 之前的代码先执行,await next() 之后(更深层中间件、最终服务器函数都执行完)再继续执行之后的代码。所以你能在请求进入前做前置逻辑,在响应返回后做后置逻辑。这也意味着你可以不调用 next() 来提前结束链路(比如拦截未授权请求)。

请求中间件

请求中间件用于定制经过它的任何服务器请求的行为,包括服务器路由、SSR 和服务器函数。

要创建请求中间件,调用 createMiddleware 函数。你可以把 type 属性设为 'request' 调用,但这也是默认值,所以你也可以省略。

import { createMiddleware } from '@tanstack/react-start'

const loggingMiddleware = createMiddleware().server(() => {
  //...
})

可用方法

请求中间件有以下方法:

  • middleware:向链中添加一个中间件。
  • server:定义中间件在任何嵌套中间件以及最终的服务器函数之前执行的服务器端逻辑,并把结果提供给下一个中间件。

.server 方法

.server 方法用于定义中间件在任何嵌套中间件之前执行的服务器端逻辑,并把结果提供给下一个中间件。它接收 next 方法以及其他东西,比如 context 和请求对象:

import { createMiddleware } from '@tanstack/react-start'

const loggingMiddleware = createMiddleware().server(
  ({ next, context, request }) => {
    return next()
  },
)

为了快速理解这个握手过程,这里有一张图:

sequenceDiagram
  HTTP ->> Middleware.server: Request
  Middleware.server ->> Middleware.server: next()
  Middleware.server ->> ServerFn: payload
  ServerFn ->> Middleware.server: result
  Middleware.server ->> Middleware.server: return
  Middleware.server ->> HTTP: Response

  box Server
  participant Middleware.server
  participant ServerFn
  end

在服务器路由中使用请求中间件

有两种方式在服务器路由中使用请求中间件:

作用于所有服务器路由方法

要让一个服务器路由的所有方法都使用中间件,把中间件数组传给方法构建器对象的 middleware 属性。

import { createMiddleware } from '@tanstack/react-start'

const loggingMiddleware = createMiddleware().server(() => {
  //...
})

export const Route = createFileRoute('/foo')({
  server: {
    middleware: [loggingMiddleware],
    handlers: {
      GET: () => {
        //...
      },
      POST: () => {
        //...
      },
    },
  },
})

作用于特定服务器路由方法

你可以使用 createHandlers 工具,把中间件数组传给方法对象的 middleware 属性,从而给特定的服务器路由方法传中间件。

import { createMiddleware } from '@tanstack/react-start'

const loggingMiddleware = createMiddleware().server(() => {
  //...
})

export const Route = createFileRoute('/foo')({
  server: {
    handlers: ({ createHandlers }) =>
      createHandlers({
        GET: {
          middleware: [loggingMiddleware],
          handler: () => {
            //...
          },
        },
      }),
  },
})

服务器函数中间件

服务器函数中间件是请求中间件的子集,拥有专门为服务器函数设计的额外功能,比如能够校验输入数据,或在服务器函数执行前后执行客户端逻辑。

要创建服务器函数中间件,调用 createMiddleware 函数并把 type 属性设为 'function'

import { createMiddleware } from '@tanstack/react-start'

const loggingMiddleware = createMiddleware({ type: 'function' })
  .client(() => {
    //...
  })
  .server(() => {
    //...
  })

可用方法

服务器函数中间件有以下方法:

  • middleware:向链中添加一个中间件。
  • validator:在数据对象传给这个中间件、任何嵌套中间件以及最终的服务器函数之前,修改它。
  • client:定义中间件在客户端执行的逻辑,在服务器函数向服务器发起调用之前(和之后)执行。
  • server:定义中间件在服务器上执行的逻辑,位于服务器函数执行之前(和之后)。

注意

如果你在使用 TypeScript——希望如此——这些方法的调用顺序由类型系统强制保证,以最大化类型推断和类型安全。

.client 方法

.client 方法用于定义中间件的客户端逻辑,它会包裹对服务器的 RPC 调用的执行和结果。

import { createMiddleware } from '@tanstack/react-start'

const loggingMiddleware = createMiddleware({ type: 'function' }).client(
  async ({ next, context }) => {
    const result = await next() // <-- This will execute the next middleware in the chain and eventually, the RPC to the server
    return result
  },
)

.validator 方法

validator 方法用于在数据对象传给这个中间件、嵌套中间件以及最终的服务器函数之前修改它。这个方法应该接收一个函数,它接收数据对象并返回一个经过校验(并且可选地经过修改)的数据对象。常见做法是用 zod 之类的校验库来做这件事。

import { createMiddleware } from '@tanstack/react-start'
import { zodValidator } from '@tanstack/zod-adapter'
import { z } from 'zod'

const mySchema = z.object({
  workspaceId: z.string(),
})

const workspaceMiddleware = createMiddleware({ type: 'function' })
  .validator(zodValidator(mySchema))
  .server(({ next, data }) => {
    console.log('Workspace ID:', data.workspaceId)
    return next()
  })

使用服务器函数中间件

要让一个中间件包裹某个服务器函数,把中间件数组传给 createServerFn 函数的 middleware 属性。

import { createServerFn } from '@tanstack/react-start'
import { loggingMiddleware } from './middleware'

const fn = createServerFn()
  .middleware([loggingMiddleware])
  .handler(async () => {
    //...
  })

为了快速理解这个握手过程,这里有一张图:

sequenceDiagram
  ServerFn (client) ->> Middleware.client: payload
  Middleware.client ->> Middleware.client: next()
  Middleware.client ->> Middleware.server: Request
  Middleware.server ->> Middleware.server: next()
  Middleware.server ->> ServerFn: payload
  ServerFn ->> Middleware.server: result
  Middleware.server ->> Middleware.server: return
  Middleware.server ->> Middleware.client: Response
  Middleware.client ->> Middleware.client: return
  Middleware.client ->> ServerFn (client): result

  box Client
  participant ServerFn (client)
  participant Middleware.client
  end

  box Server
  participant Middleware.server
  participant ServerFn
  end

上下文管理

通过 next 提供上下文

next 函数可以用一个带 context 属性的对象调用(值为对象)。你传给这个 context 值的任何属性都会合并进父级 context,并提供给下一个中间件。

import { createMiddleware } from '@tanstack/react-start'

const awesomeMiddleware = createMiddleware({ type: 'function' }).server(
  ({ next }) => {
    return next({
      context: {
        isAwesome: Math.random() > 0.5,
      },
    })
  },
)

const loggingMiddleware = createMiddleware({ type: 'function' })
  .middleware([awesomeMiddleware])
  .server(async ({ next, context }) => {
    console.log('Is awesome?', context.isAwesome)
    return next()
  })

把客户端上下文发送到服务器

默认情况下,客户端上下文不会发送到服务器,因为这可能会意外地向服务器发送很大的负载。 如果你需要把客户端上下文发送到服务器,必须在调用 next 函数时带上 sendContext 属性和对象,以向服务器传输任何数据。传给 sendContext 的任何属性都会被合并、序列化,并随着数据一起发送到服务器,并且会出现在任何嵌套服务端中间件的常规 context 对象中。

import { createMiddleware } from '@tanstack/react-start'

const requestLogger = createMiddleware({ type: 'function' })
  .client(async ({ next, context }) => {
    return next({
      sendContext: {
        // Send the workspace ID to the server
        workspaceId: context.workspaceId,
      },
    })
  })
  .server(async ({ next, data, context }) => {
    // Woah! We have the workspace ID from the client!
    console.log('Workspace ID:', context.workspaceId)
    return next()
  })

客户端发送上下文的安全

你可能注意到了,在上面的例子中,客户端发送的上下文虽然是类型安全的,但并不要求在运行时校验。如果你通过 context 传递动态的用户生成数据,那可能带来安全隐患,所以如果你通过 context 从客户端向服务器发送动态数据,在使用它之前,务必在服务端中间件中校验它。

注意

结构校验不等于授权。 一个通过解析的 UUID/数字是_格式正确_的标识符,但不一定是_已授权_的标识符。如果这个值将作为查询 key、过滤条件或路径参数——任何决定读写哪些行(row)的东西——你还必须验证会话主体(session principal)有权访问它。否则,登录用户可以改写自己请求中的这个值,从而读取其他租户的数据。

import { createMiddleware } from '@tanstack/react-start'
import { z } from 'zod'

const requestLogger = createMiddleware({ type: 'function' })
  .client(async ({ next, context }) => {
    return next({
      sendContext: {
        workspaceId: context.workspaceId,
      },
    })
  })
  .middleware([authMiddleware]) // session loaded server-side, NOT from sendContext
  .server(async ({ next, context }) => {
    // 1. Validate shape
    const workspaceId = z.string().uuid().parse(context.workspaceId)
    // 2. Validate access — does this session principal have membership?
    const member = await db.memberships.find({
      userId: context.session.userId,
      workspaceId,
    })
    if (!member) throw new Error('Not a member of this workspace')
    // 3. Now safe to use as a query key.
    return next({ context: { workspaceId } })
  })

始终从服务器可信的来源(authMiddleware 里的 cookie + 数据库查询)推导 session 本身,绝不要从 sendContext 获取。凡是客户端能发送的东西,客户端都可能说谎。

把服务器上下文发送到客户端

与把客户端上下文发送到服务器类似,你也可以通过调用 next 函数并带上 sendContext 属性和对象,把服务器上下文发送到客户端。传给 sendContext 的任何属性都会被合并、序列化,并随着响应一起发送到客户端,并且会出现在任何嵌套客户端中间件的常规 context 对象中。在 client 中调用 next 返回的对象包含服务器发送给客户端的 context,并且是类型安全的。

警告

client 中,next 的返回类型只能从当前中间件链中已知的中间件推断。因此 next 最准确的返回类型位于中间件链末尾的中间件中。

import { createMiddleware } from '@tanstack/react-start'

const serverTimer = createMiddleware({ type: 'function' }).server(
  async ({ next }) => {
    return next({
      sendContext: {
        // Send the current time to the client
        timeFromServer: new Date(),
      },
    })
  },
)

const requestLogger = createMiddleware({ type: 'function' })
  .middleware([serverTimer])
  .client(async ({ next }) => {
    const result = await next()
    // Woah! We have the time from the server!
    console.log('Time from the server:', result.context.timeFromServer)

    return result
  })

全局中间件

全局中间件会对应用中的每个请求自动运行。这对认证、日志记录、监控等应该应用于所有请求的功能很有用。

注意

src/start.ts 文件不包含在默认的 TanStack Start 模板中。当你需要配置全局中间件或其他 Start 级选项时,需要自己创建这个文件。

全局请求中间件

要让一个中间件对 Start 处理的每个请求运行,创建一个 src/start.ts 文件,用 createStart 函数返回你的中间件配置:

// src/start.ts
import { createStart, createMiddleware } from '@tanstack/react-start'

const myGlobalMiddleware = createMiddleware().server(() => {
  //...
})

export const startInstance = createStart(() => {
  return {
    requestMiddleware: [myGlobalMiddleware],
  }
})

注意

全局请求中间件会在每个请求之前运行,包括服务器路由、SSR 和服务器函数。

CSRF 中间件

服务器函数是同源 RPC 端点,应该受到保护,防止跨站请求。如果你的应用没有定义 src/start.ts,TanStack Start 会自动为服务器函数安装它的 CSRF 中间件。

如果你定义了自定义的 src/start.ts,请显式添加 createCsrfMiddleware()

// src/start.ts
import { createStart, createCsrfMiddleware } from '@tanstack/react-start'

const csrfMiddleware = createCsrfMiddleware({
  filter: (ctx) => ctx.handlerType === 'serverFn',
})

export const startInstance = createStart(() => ({
  requestMiddleware: [csrfMiddleware],
}))

默认情况下,OriginReferer 检查会与传入请求的 URL 来源进行比对。如果你的部署需要允许不同的公开来源,可以在 CSRF 中间件上配置:createCsrfMiddleware({ origin: 'https://app.example.com' })

默认情况下,createCsrfMiddleware() 会校验中间件处理的每个请求。当你在全局安装它用于服务器函数保护时,使用 filter: (ctx) => ctx.handlerType === 'serverFn'。它会用 Sec-Fetch-SiteOriginReferer 请求头校验同源浏览器请求元数据,并拒绝无法证明是同源的请求。

你也可以用同一个中间件保护任何其他路由。

export const Route = createFileRoute('/api/foo')({
  server: {
    middleware: [createCsrfMiddleware()],
    handlers: { GET: () => {...} }
  }
})

如果你定义了 src/start.ts 却没有包含 CSRF 中间件,Start 会对服务器函数请求显示一条开发警告。如果你有意用其他方式处理 CSRF,可以禁用这条警告:

// vite.config.ts or rsbuild.config.ts
tanstackStart({
  serverFns: {
    disableCsrfMiddlewareWarning: true,
  },
})

全局服务器函数中间件

要让一个中间件对应用中的每个服务器函数运行,把它添加到 src/start.ts 文件的 functionMiddleware 数组中:

// src/start.ts
import { createStart } from '@tanstack/react-start'
import { loggingMiddleware } from './middleware'

export const startInstance = createStart(() => {
  return {
    functionMiddleware: [loggingMiddleware],
  }
})

中间件执行顺序

中间件按依赖优先的顺序执行,从全局中间件开始,然后是服务器函数中间件。下面这个示例会按这个顺序打印日志:

  • globalMiddleware1
  • globalMiddleware2
  • a
  • b
  • c
  • d
  • fn
import { createMiddleware, createServerFn } from '@tanstack/react-start'

const globalMiddleware1 = createMiddleware({ type: 'function' }).server(
  async ({ next }) => {
    console.log('globalMiddleware1')
    return next()
  },
)

const globalMiddleware2 = createMiddleware({ type: 'function' }).server(
  async ({ next }) => {
    console.log('globalMiddleware2')
    return next()
  },
)

const a = createMiddleware({ type: 'function' }).server(async ({ next }) => {
  console.log('a')
  return next()
})

const b = createMiddleware({ type: 'function' })
  .middleware([a])
  .server(async ({ next }) => {
    console.log('b')
    return next()
  })

const c = createMiddleware({ type: 'function' })
  .middleware()
  .server(async ({ next }) => {
    console.log('c')
    return next()
  })

const d = createMiddleware({ type: 'function' })
  .middleware([b, c])
  .server(async () => {
    console.log('d')
  })

const fn = createServerFn()
  .middleware([d])
  .server(async () => {
    console.log('fn')
  })

请求与响应修改

读取/修改服务器响应

使用 server 方法的中间件与服务器函数在同一个上下文中执行,所以你可以用完全相同的服务器函数上下文工具来读取和修改请求头、状态码等任何东西。

修改客户端请求

使用 client 方法的中间件在与服务器函数完全不同的客户端上下文中执行,所以你不能用相同的工具来读取和修改请求。不过,你仍然可以通过在调用 next 函数时返回额外的属性来修改请求。

设置自定义请求头

你可以通过在 next 中传一个 headers 对象,向发出去的请求添加请求头:

import { createMiddleware } from '@tanstack/react-start'
import { getToken } from 'my-auth-library'

const authMiddleware = createMiddleware({ type: 'function' }).client(
  async ({ next }) => {
    return next({
      headers: {
        Authorization: `Bearer ${getToken()}`,
      },
    })
  },
)

中间件之间的请求头合并

当多个中间件设置请求头时,它们会被合并在一起。后面的中间件可以添加新请求头,或覆盖前面中间件设置的请求头:

import { createMiddleware } from '@tanstack/react-start'

const firstMiddleware = createMiddleware({ type: 'function' }).client(
  async ({ next }) => {
    return next({
      headers: {
        'X-Request-ID': '12345',
        'X-Source': 'first-middleware',
      },
    })
  },
)

const secondMiddleware = createMiddleware({ type: 'function' }).client(
  async ({ next }) => {
    return next({
      headers: {
        'X-Timestamp': Date.now().toString(),
        'X-Source': 'second-middleware', // Overrides first middleware
      },
    })
  },
)

// Final headers will include:
// - X-Request-ID: '12345' (from first)
// - X-Timestamp: '<timestamp>' (from second)
// - X-Source: 'second-middleware' (second overrides first)

你也可以直接在调用点设置请求头:

await myServerFn({
  data: { name: 'John' },
  headers: {
    'X-Custom-Header': 'call-site-value',
  },
})

请求头优先级(所有请求头都会被合并,后面的值覆盖前面的):

  1. 靠前的中间件请求头
  2. 靠后的中间件请求头(覆盖前面的)
  3. 调用点的请求头(覆盖所有中间件请求头)

自定义 fetch 实现

对于高级场景,你可以提供自定义的 fetch 实现,控制服务器函数请求的发起方式。这对以下场景很有用:

  • 添加请求拦截器或重试逻辑
  • 使用自定义的 HTTP 客户端
  • 测试与 mock
  • 添加遥测或监控

通过客户端中间件:

import { createMiddleware } from '@tanstack/react-start'
import type { CustomFetch } from '@tanstack/react-start'

const customFetchMiddleware = createMiddleware({ type: 'function' }).client(
  async ({ next }) => {
    const customFetch: CustomFetch = async (url, init) => {
      console.log('Request starting:', url)
      const start = Date.now()

      const response = await fetch(url, init)

      console.log('Request completed in', Date.now() - start, 'ms')
      return response
    }

    return next({ fetch: customFetch })
  },
)

直接在调用点:

import type { CustomFetch } from '@tanstack/react-start'

const myFetch: CustomFetch = async (url, init) => {
  // Add custom logic here
  return fetch(url, init)
}

await myServerFn({
  data: { name: 'John' },
  fetch: myFetch,
})

fetch 覆盖优先级

当多个层级都提供了自定义 fetch 实现时,应用以下优先级(从高到低):

优先级来源说明
1(最高)调用点serverFn({ fetch: customFetch })
2靠后的中间件链中最后一个提供 fetch 的中间件
3靠前的中间件链中第一个提供 fetch 的中间件
4createStartcreateStart({ serverFns: { fetch: customFetch } })
5(最低)默认全局 fetch 函数

关键原则: 调用点总是赢。这让你在需要时可以为特定调用覆盖中间件行为。

import { createMiddleware, createServerFn } from '@tanstack/react-start'
import type { CustomFetch } from '@tanstack/react-start'

// Middleware sets a fetch that adds logging
const loggingMiddleware = createMiddleware({ type: 'function' }).client(
  async ({ next }) => {
    const loggingFetch: CustomFetch = async (url, init) => {
      console.log('Middleware fetch:', url)
      return fetch(url, init)
    }
    return next({ fetch: loggingFetch })
  },
)

const myServerFn = createServerFn()
  .middleware([loggingMiddleware])
  .handler(async () => {
    return { message: 'Hello' }
  })

// Uses middleware's loggingFetch
await myServerFn()

// Override with custom fetch for this specific call
const testFetch: CustomFetch = async (url, init) => {
  console.log('Test fetch:', url)
  return fetch(url, init)
}
await myServerFn({ fetch: testFetch }) // Uses testFetch, NOT loggingFetch

链式中间件示例:

当多个中间件都提供 fetch 时,最后一个生效:

import { createMiddleware, createServerFn } from '@tanstack/react-start'
import type { CustomFetch } from '@tanstack/react-start'

const firstMiddleware = createMiddleware({ type: 'function' }).client(
  async ({ next }) => {
    const firstFetch: CustomFetch = (url, init) => {
      const headers = new Headers(init?.headers)
      headers.set('X-From', 'first-middleware')
      return fetch(url, { ...init, headers })
    }
    return next({ fetch: firstFetch })
  },
)

const secondMiddleware = createMiddleware({ type: 'function' }).client(
  async ({ next }) => {
    const secondFetch: CustomFetch = (url, init) => {
      const headers = new Headers(init?.headers)
      headers.set('X-From', 'second-middleware')
      return fetch(url, { ...init, headers })
    }
    return next({ fetch: secondFetch })
  },
)

const myServerFn = createServerFn()
  .middleware([firstMiddleware, secondMiddleware])
  .handler(async () => {
    // Request will have X-From: 'second-middleware'
    // because secondMiddleware's fetch overrides firstMiddleware's fetch
    return { message: 'Hello' }
  })

通过 createStart 设置全局 fetch:

你可以在 createStart 中提供 serverFns.fetch,为应用中的所有服务器函数设置默认的自定义 fetch。这对添加全局请求拦截器、重试逻辑或遥测很有用:

// src/start.ts
import { createStart } from '@tanstack/react-start'
import type { CustomFetch } from '@tanstack/react-start'

const globalFetch: CustomFetch = async (url, init) => {
  console.log('Global fetch:', url)
  // Add retry logic, telemetry, etc.
  return fetch(url, init)
}

export const startInstance = createStart(() => {
  return {
    serverFns: {
      fetch: globalFetch,
    },
  }
})

这个全局 fetch 的优先级低于中间件和调用点的 fetch,所以你在需要时仍然可以为特定服务器函数或调用覆盖它。

注意

自定义 fetch 只作用于客户端。在 SSR 期间,服务器函数会被直接调用,不会经过 fetch。

环境与性能

环境 Tree Shaking

中间件功能会根据每个打包产物对应的环境进行 tree-shaking。

  • 在服务器上,不会进行 tree-shaking,所以中间件中用到的所有代码都会包含在服务端打包产物中。
  • 在客户端上,所有服务器专属代码都会从客户端打包产物中移除。这意味着 server 方法中用到的任何代码总是会从客户端打包产物中移除。data 校验代码也会被移除。

中间件工厂

静态中间件创建一次,然后在路由之间复用。中间件工厂(Middleware Factory)把这种创建过程包进一个函数,让它能接收参数,并根据调用者的需求表现出不同的行为。授权(Authorization)是一个常见用例。

认证(静态基础中间件)示例:

这个中间件校验 session,并把它注入 context,供下游中间件使用。

注意

authMiddleware 挂到每个需要认证的 createServerFn 上。服务器函数是 API 端点,所以要保护读取或修改私有数据的端点。路由的 beforeLoad 守卫能改善路由 UX,但它不是数据边界。见认证服务器原语

// middleware.ts
import { createMiddleware } from '@tanstack/react-start'
import { auth } from './my-auth'

export const authMiddleware = createMiddleware().server(
  async ({ next, request }) => {
    const session = await auth.getSession({ headers: request.headers })

    if (!session) {
      throw new Error('Unauthorized')
    }

    return await next({
      context: { session },
    })
  },
)

授权(中间件工厂)示例:

这个中间件基于动态的 permissions 参数校验访问权限,并与 authMiddleware 组合,因此 context.session 已经可用。

// middleware.ts
import { createMiddleware } from '@tanstack/react-start'
import { auth } from './my-auth'

export const authMiddleware = createMiddleware().server(
  async ({ next, request }) => {
    // ... (implementation from authentication example above)
  },
)

type Permissions = Record<string, string[]>

export function authorizationMiddleware(permissions: Permissions) {
  return createMiddleware({ type: 'function' })
    .middleware([authMiddleware])
    .server(async ({ next, context }) => {
      const granted = await auth.hasPermission(context.session, permissions)

      if (!granted) {
        throw new Error('Forbidden')
      }

      return await next()
    })
}

在服务器函数中的使用:

每个服务器函数的访问要求独立定义,不需要重复任何中间件逻辑。

import { createServerFn } from '@tanstack/react-start'
import { authorizationMiddleware } from './middleware'

export const getClients = createServerFn()
  .middleware([
    authorizationMiddleware({
      client: ['read'],
    }),
  ])
  .handler(async ({ context }) => {
    return { message: 'The user can read clients.' }
  })

On this page