Instruction file imported from chowjiaming/weather-vibes (
.cursor/rules/tanstack-start-middleware.mdc). Copyright stays with the author.
description: TanStack Start: Middleware Guide globs: src//*.ts,src//*.tsx alwaysApply: false
Middleware
What is Middleware?
Middleware allows you to customize the behavior of both server routes like GET/POST/etc (including requests to SSR your application) and server functions created with createServerFn. Middleware is composable and can even depend on other middleware to create a chain of operations that are executed hierarchically and in order.
What kinds of things can I do with Middleware?
- Authentication: Verify a user's identity before executing a server function.
- Authorization: Check if a user has the necessary permissions to execute a server function.
- Logging: Log requests, responses, and errors.
- CSP: Configure Content Security Policy and other security measures.
- Observability: Collect metrics, traces, and logs.
- Provide Context: Attach data to the request object for use in other middleware or server functions.
- Error Handling: Handle errors in a consistent way.
- And many more! The possibilities are up to you!
Middleware Types
There are two types of middleware: request middleware and server function middleware.
- Request middleware is used to customize the behavior of any server request that passes through it, including server functions.
- Server function middleware is used to customize the behavior of server functions specifically.
[!NOTE] Server function middleware is a subset of request middleware that has extra functionality specifically for server functions like being able to validate input data or perform client-side logic both before and after the server function is executed.
Key Differences
| Feature | Request Middleware | Server Function Middleware |
|---|---|---|
| Scope | All server requests | Server functions only |
| Methods | .server() |
.client(), .server() |
| Input Validation | No | Yes (.inputValidator()) |
| Client-side Logic | No | Yes |
| Dependencies | Can depend on request middleware | Can depend on both types |
[!NOTE] Request middleware cannot depend on server function middleware, but server function middleware can depend on request middleware.
Core Concepts
Middleware Composition
All middleware is composable, which means that one middleware can depend on another middleware.
import { createMiddleware } from '@tanstack/react-start'
const loggingMiddleware = createMiddleware().server(() => {
//...
})
const authMiddleware = createMiddleware()
.middleware([loggingMiddleware])
.server(() => {
//...
})
Progressing the Middleware Chain
Middleware is next-able, which means that you must call the next function in the .server method (and/or .client method if you are creating a server function middleware) to execute the next middleware in the chain. This allows you to:
- Short circuit the middleware chain and return early
- Pass data to the next middleware
- Access the result of the next middleware
- Pass context to the wrapping middleware
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
})
Request Middleware
Request middleware is used to customize the behavior of any server request that passes through it, including server routes, SSR and server functions.
To create a request middleware, call the createMiddleware function. You may call this function with the type property set to 'request', but this is the default value so you can omit it if you'd like.
import { createMiddleware } from '@tanstack/react-start'
const loggingMiddleware = createMiddleware().server(() => {
//...
})
Available Methods
Request middleware has the following methods:
middleware: Add a middleware to the chain.server: Define server-side logic that the middleware will execute before any nested middleware and ultimately a server function, and also provide the result to the next middleware.
The .server method
The .server method is used to define server-side logic that the middleware will execute before any nested middleware, and also provide the result to the next middleware. It receives the next method and other things like context and the request object:
import { createMiddleware } from '@tanstack/react-start'
const loggingMiddleware = createMiddleware().server(
({ next, context, request }) => {
return next()
},
)
Server Function Middleware
Server function middleware is a subset of request middleware that has extra functionality specifically for server functions like being able to validate input data or perform client-side logic both before and after the server function is executed.
To create a server function middleware, call the createMiddleware function with the type property set to 'function'.
import { createMiddleware } from '@tanstack/react-start'
const loggingMiddleware = createMiddleware({ type: 'function' })
.client(() => {
//...
})
.server(() => {
//...
})
Available Methods
Server function middleware has the following methods:
middleware: Add a middleware to the chain.inputValidator: Modify the data object before it is passed to this middleware and any nested middleware and eventually the server function.client: Define client-side logic that the middleware will execute on the client before (and after) the server function calls into the server to execute the function.server: Define server-side logic that the middleware will execute on the server before (and after) the server function is executed.
[!NOTE] If you are (hopefully) using TypeScript, the order of these methods is enforced by the type system to ensure maximum inference and type safety.
The .client method
The .client method is used to define client-side logic that the middleware will wrap the execution and result of the RPC call to the server.
import { createMiddleware } from '@tanstack/react-start'
const loggingMiddleware = createMiddleware({ type: 'function' }).client(
async ({ next, context, request }) => {
const result = await next() // <-- This will execute the next middleware in the chain and eventually, the RPC to the server
return result
},
)
The .inputValidator method
The inputValidator method is used to modify the data object before it is passed to this middleware, nested middleware, and ultimately the server function. This method should receive a function that takes the data object and returns a validated (and optionally modified) data object. It's common to use a validation library like zod to do this.
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' })
.inputValidator(zodValidator(mySchema))
.server(({ next, data }) => {
console.log('Workspace ID:', data.workspaceId)
return next()
})
Using Server Function Middleware
To have a middleware wrap a specific server function, you can pass a middleware array to the middleware property of the createServerFn function.
import { createServerFn } from '@tanstack/react-start'
import { loggingMiddleware } from './middleware'
const fn = createServerFn()
.middleware([loggingMiddleware])
.handler(async () => {
//...
})
Context Management
Providing Context via next
The next function can be optionally called with an object that has a context property with an object value. Whatever properties you pass to this context value will be merged into the parent context and provided to the next middleware.
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()
})
Global Middleware
Global middleware runs automatically for every request in your application. This is useful for functionality like authentication, logging, and monitoring that should apply to all requests.
Global Request Middleware
To have a middleware run for every request handled by Start, you can create a middleware and return it as requestMiddleware in the createStart function in your src/start.ts file:
// src/start.ts
import { createStart } from '@tanstack/react-start'
const myGlobalMiddleware = createMiddleware().server(() => {
//...
})
export const startInstance = createStart(() => {
return {
requestMiddleware: [myGlobalMiddleware],
}
})
[!NOTE] Global request middleware runs before every request, including server routes, SSR and server functions.
Global Server Function Middleware
To have a middleware run for every server function in your application, you can create a middleware and return it to the createStart function as functionMiddleware in your src/start.ts file:
// src/start.ts
import { createStart } from '@tanstack/react-start'
import { loggingMiddleware } from './middleware'
export const startInstance = createStart(() => {
return {
functionMiddleware: [loggingMiddleware],
}
})
Environment and Performance
Environment Tree Shaking
Middleware functionality is tree-shaken based on the environment for each bundle produced.
- On the server, nothing is tree-shaken, so all code used in middleware will be included in the server bundle.
- On the client, all server-specific code is removed from the client bundle. This means any code used in the
servermethod is always removed from the client bundle.datavalidation code will also be removed.