中间件允许您通过共享验证、上下文等来自定义使用 createServerFn 创建的服务器函数的行为。中间件甚至可以依赖其他中间件来创建按层次结构和顺序执行的操作链。
中间件使用 createMiddleware 函数定义。此函数返回一个 Middleware 对象,可用于通过 middleware、validator、server 和 client 等方法继续自定义中间件。
import { createMiddleware } from '@tanstack/react-start'
const loggingMiddleware = createMiddleware({ type: 'function' }).server(
async ({ next, data }) => {
console.log('Request received:', data)
const result = await next()
console.log('Response processed:', result)
return result
},
)
import { createMiddleware } from '@tanstack/react-start'
const loggingMiddleware = createMiddleware({ type: 'function' }).server(
async ({ next, data }) => {
console.log('Request received:', data)
const result = await next()
console.log('Response processed:', result)
return result
},
)
定义中间件后,您可以将其与 createServerFn 函数结合使用,以自定义服务器函数的行为。
import { createServerFn } from '@tanstack/react-start'
import { loggingMiddleware } from './middleware'
const fn = createServerFn()
.middleware([loggingMiddleware])
.handler(async () => {
// ...
})
import { createServerFn } from '@tanstack/react-start'
import { loggingMiddleware } from './middleware'
const fn = createServerFn()
.middleware([loggingMiddleware])
.handler(async () => {
// ...
})
有几种方法可用于自定义中间件。如果您(希望如此)正在使用 TypeScript,这些方法的顺序由类型系统强制执行,以确保最大程度的类型推断和类型安全。
middleware
方法middleware 方法用于将依赖中间件添加到将在当前中间件之前执行的链中。只需使用中间件对象数组调用 middleware 方法即可。
import { createMiddleware } from '@tanstack/react-start'
const loggingMiddleware = createMiddleware({ type: 'function' }).middleware([
authMiddleware,
loggingMiddleware,
])
import { createMiddleware } from '@tanstack/react-start'
const loggingMiddleware = createMiddleware({ type: 'function' }).middleware([
authMiddleware,
loggingMiddleware,
])
类型安全上下文和负载验证也从父中间件继承!
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()
})
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()
})
server
方法server 方法用于定义中间件将在任何嵌套中间件和最终服务器函数之前和之后执行的服务器端逻辑。此方法接收一个具有以下属性的对象:
next 函数用于执行链中的下一个中间件。您必须 await 并返回(或直接返回)提供给您的 next 函数的结果,以使链继续执行。
import { createMiddleware } from '@tanstack/react-start'
const loggingMiddleware = createMiddleware({ type: 'function' }).server(
async ({ next }) => {
console.log('Request received')
const result = await next()
console.log('Response processed')
return result
},
)
import { createMiddleware } from '@tanstack/react-start'
const loggingMiddleware = createMiddleware({ type: 'function' }).server(
async ({ next }) => {
console.log('Request received')
const result = await next()
console.log('Response processed')
return result
},
)
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()
})
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()
})
尽管服务器函数主要是服务器端绑定操作,但客户端仍有大量围绕从客户端发出的 RPC 请求的客户端逻辑。这意味着我们还可以在中间件中定义客户端逻辑,这些逻辑将在客户端围绕任何嵌套中间件以及最终的 RPC 函数及其对客户端的响应执行。
默认情况下,中间件验证仅在服务器上执行,以保持客户端包大小较小。但是,您也可以选择通过将 validateClient: true 选项传递给 createMiddleware 函数来在客户端验证数据。这将在数据发送到服务器之前在客户端对其进行验证,从而可能节省一次往返。
为什么我不能为客户端传递不同的验证模式?
客户端验证模式派生自服务器端模式。这是因为客户端验证模式用于在数据发送到服务器之前对其进行验证。如果客户端模式与服务器端模式不同,服务器将收到它不期望的数据,这可能导致意外行为。
import { createMiddleware } from '@tanstack/react-start'
import { zodValidator } from '@tanstack/zod-adapter'
import { z } from 'zod'
const workspaceMiddleware = createMiddleware({ validateClient: true })
.validator(zodValidator(mySchema))
.server(({ next, data }) => {
console.log('Workspace ID:', data.workspaceId)
return next()
})
import { createMiddleware } from '@tanstack/react-start'
import { zodValidator } from '@tanstack/zod-adapter'
import { z } from 'zod'
const workspaceMiddleware = createMiddleware({ validateClient: true })
.validator(zodValidator(mySchema))
.server(({ next, data }) => {
console.log('Workspace ID:', data.workspaceId)
return next()
})
client
方法客户端中间件逻辑使用 Middleware 对象上的 client 方法定义。此方法用于定义中间件将在任何嵌套中间件以及最终客户端 RPC 函数(如果您正在进行 SSR 或从另一个服务器函数调用此函数)之前和之后执行的客户端逻辑。
客户端中间件逻辑与使用 server 方法创建的逻辑共享许多相同的 API,但它在客户端执行。这包括:
与 server 函数类似,它也接收一个具有以下属性的对象:
const loggingMiddleware = createMiddleware({ type: 'function' }).client(
async ({ next }) => {
console.log('Request sent')
const result = await next()
console.log('Response received')
return result
},
)
const loggingMiddleware = createMiddleware({ type: 'function' }).client(
async ({ next }) => {
console.log('Request sent')
const result = await next()
console.log('Response received')
return result
},
)
默认情况下,客户端上下文不会发送到服务器,因为这可能会无意中向服务器发送大量负载。如果您需要将客户端上下文发送到服务器,则必须调用 next 函数,其中包含 sendContext 属性和对象以将任何数据传输到服务器。传递给 sendContext 的任何属性都将合并、序列化并与数据一起发送到服务器,并且在任何嵌套服务器中间件的正常上下文对象上可用。
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()
})
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()
})
您可能已经注意到,在上面的示例中,虽然客户端发送的上下文是类型安全的,但它不要求在运行时进行验证。如果您通过上下文传递动态用户生成的数据,这可能会带来安全隐患,因此如果您通过上下文从客户端向服务器发送动态数据,则应在服务器端中间件中使用之前对其进行验证。这是一个示例:
import { zodValidator } from '@tanstack/zod-adapter'
import { z } from 'zod'
const requestLogger = createMiddleware({ type: 'function' })
.client(async ({ next, context }) => {
return next({
sendContext: {
workspaceId: context.workspaceId,
},
})
})
.server(async ({ next, data, context }) => {
// Validate the workspace ID before using it
const workspaceId = zodValidator(z.number()).parse(context.workspaceId)
console.log('Workspace ID:', workspaceId)
return next()
})
import { zodValidator } from '@tanstack/zod-adapter'
import { z } from 'zod'
const requestLogger = createMiddleware({ type: 'function' })
.client(async ({ next, context }) => {
return next({
sendContext: {
workspaceId: context.workspaceId,
},
})
})
.server(async ({ next, data, context }) => {
// Validate the workspace ID before using it
const workspaceId = zodValidator(z.number()).parse(context.workspaceId)
console.log('Workspace ID:', workspaceId)
return next()
})
与将客户端上下文发送到服务器类似,您还可以通过调用 next 函数,其中包含 sendContext 属性和对象,将服务器上下文发送到客户端,以将任何数据传输到客户端。传递给 sendContext 的任何属性都将合并、序列化并与响应一起发送到客户端,并且在任何嵌套客户端中间件的正常上下文对象上可用。client 中调用 next 的返回对象包含从服务器发送到客户端的上下文,并且是类型安全的。中间件能够从从 middleware 函数链接的先前中间件推断从服务器发送到客户端的上下文。
警告
client 中 next 的返回类型只能从当前中间件链中已知的中间件推断。因此,next 最准确的返回类型在中间件链末端的中间件中。
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
})
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
})
使用 server 方法的中间件与服务器函数在相同的上下文中执行,因此您可以遵循完全相同的 服务器函数上下文实用程序 来读取和修改请求头、状态码等。
使用 client 方法的中间件与服务器函数在完全不同的客户端上下文中执行,因此您不能使用相同的实用程序来读取和修改请求。但是,在调用 next 函数时,您仍然可以修改请求并返回附加属性。当前支持的属性是:
这是一个使用此中间件为任何请求添加 Authorization 头的示例:
import { getToken } from 'my-auth-library'
const authMiddleware = createMiddleware({ type: 'function' }).client(
async ({ next }) => {
return next({
headers: {
Authorization: `Bearer ${getToken()}`,
},
})
},
)
import { getToken } from 'my-auth-library'
const authMiddleware = createMiddleware({ type: 'function' }).client(
async ({ next }) => {
return next({
headers: {
Authorization: `Bearer ${getToken()}`,
},
})
},
)
中间件可以通过两种不同的方式使用:
全局中间件会自动为应用程序中的每个服务器函数运行。这对于身份验证、日志记录和监控等应适用于所有请求的功能非常有用。
要使用全局中间件,请在项目中创建一个 global-middleware.ts 文件(通常在 app/global-middleware.ts)。此文件在客户端和服务器环境中运行,是您注册全局中间件的地方。
以下是如何注册全局中间件:
// app/global-middleware.ts
import { registerGlobalMiddleware } from '@tanstack/react-start'
import { authMiddleware } from './middleware'
registerGlobalMiddleware({
middleware: [authMiddleware],
})
// app/global-middleware.ts
import { registerGlobalMiddleware } from '@tanstack/react-start'
import { authMiddleware } from './middleware'
registerGlobalMiddleware({
middleware: [authMiddleware],
})
全局中间件类型本质上与服务器函数本身是分离的。这意味着如果全局中间件向服务器函数或其他特定于服务器函数的中间件提供额外的上下文,则类型不会自动传递到服务器函数或其他特定于服务器函数的中间件。
// app/global-middleware.ts
registerGlobalMiddleware({
middleware: [authMiddleware],
})
// app/global-middleware.ts
registerGlobalMiddleware({
middleware: [authMiddleware],
})
// authMiddleware.ts
const authMiddleware = createMiddleware({ type: 'function' }).server(
({ next, context }) => {
console.log(context.user) // <-- This will not be typed!
// ...
},
)
// authMiddleware.ts
const authMiddleware = createMiddleware({ type: 'function' }).server(
({ next, context }) => {
console.log(context.user) // <-- This will not be typed!
// ...
},
)
要解决此问题,请将您尝试引用的全局中间件添加到服务器函数的中间件数组中。全局中间件将被去重为单个条目(全局实例),您的服务器函数将收到正确的类型。
以下是其工作原理的示例:
import { authMiddleware } from './authMiddleware'
const fn = createServerFn()
.middleware([authMiddleware])
.handler(async ({ context }) => {
console.log(context.user)
// ...
})
import { authMiddleware } from './authMiddleware'
const fn = createServerFn()
.middleware([authMiddleware])
.handler(async ({ context }) => {
console.log(context.user)
// ...
})
中间件按依赖关系优先执行,从全局中间件开始,然后是服务器函数中间件。以下示例将按此顺序记录以下内容:
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()
},
)
registerGlobalMiddleware({
middleware: [globalMiddleware1, globalMiddleware2],
})
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')
})
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()
},
)
registerGlobalMiddleware({
middleware: [globalMiddleware1, globalMiddleware2],
})
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')
})
中间件功能根据每个生成的捆绑包的环境进行摇树优化。
您的每周 JavaScript 资讯。每周一免费发送给超过 10 万开发者。