createMiddleware
Creates middleware that can intercept and modify server function calls or HTTP requests.
Basic Usage
import { createMiddleware } from '@tanstack/react-start'
const loggingMiddleware = createMiddleware().server(async ({ next }) => {
console.log('Request started')
const result = await next()
console.log('Request completed')
return result
})
API Reference
type
'request' | 'function'
default:"'request'"
The type of middleware:
'request': Runs for all HTTP requests (pages and server functions)
'function': Runs only for server function calls
Middleware Types
Request Middleware
Runs for all HTTP requests including page loads and server function calls:
const requestMiddleware = createMiddleware({ type: 'request' })
.server(async ({ request, pathname, context, next }) => {
console.log(`Request to ${pathname}`)
return next()
})
// Use in route configuration
export const Route = createFileRoute('/api/users')({
server: {
middleware: [requestMiddleware],
handlers: {
GET: async () => Response.json({ users: [] })
}
}
})
Function Middleware
Runs only for server function calls and can execute on both client and server:
const functionMiddleware = createMiddleware({ type: 'function' })
.client(async ({ next, sendContext }) => {
// Runs on the client before sending request
return next({
sendContext: { timestamp: Date.now() }
})
})
.server(async ({ next, context }) => {
// Runs on the server
console.log('Client timestamp:', context.timestamp)
return next()
})
Builder Methods
.middleware()
Composes middleware by nesting other middleware:
middlewares
Array<Middleware>
required
Array of middleware to run before this middleware executes.
const parentMiddleware = createMiddleware().server(async ({ next }) => {
return next({ context: { parent: true } })
})
const childMiddleware = createMiddleware()
.middleware([parentMiddleware])
.server(async ({ next, context }) => {
console.log(context.parent) // true
return next()
})
Validates input data for function middleware:
Validation schema for the input data.
import { z } from 'zod'
const validatingMiddleware = createMiddleware({ type: 'function' })
.inputValidator(z.object({ userId: z.string() }))
.server(async ({ data, next }) => {
// data is typed and validated as { userId: string }
return next()
})
.client()
Defines client-side middleware for function middleware:
fn
ClientMiddlewareFn
required
Function that runs on the client before sending the request to the server.
const clientMiddleware = createMiddleware({ type: 'function' })
.client(async ({ data, context, sendContext, next, fetch }) => {
// Add authentication token
return next({
headers: { 'Authorization': 'Bearer token' },
sendContext: { clientTime: Date.now() }
})
})
Client Middleware Context:
The input data being sent to the server.
Client-side context accumulated from previous middleware.
Context to send to the server (must be serializable).
The HTTP method being used.
AbortSignal for the request.
Metadata about the server function (id).
The filename where the server function is defined.
The fetch function to use for the request.
Call to proceed to the next middleware or make the server request.
.server()
Defines server-side middleware logic:
fn
ServerMiddlewareFn
required
Function that runs on the server.
For Request Middleware:
const serverRequestMiddleware = createMiddleware({ type: 'request' })
.server(async ({ request, pathname, context, next, serverFnMeta }) => {
// Add to context
return next({ context: { userId: '123' } })
})
Context accumulated from previous middleware.
Metadata about the server function if this is a server function request, undefined for page requests.
Proceeds to the next middleware. Can pass context: next({ context: {...} })
For Function Middleware:
const serverFunctionMiddleware = createMiddleware({ type: 'function' })
.server(async ({ data, context, next, method, serverFnMeta, signal }) => {
// Process the server function call
return next({
sendContext: { timestamp: Date.now() }
})
})
The validated input data.
Server-side context from previous middleware and sent from client.
Server function metadata (id, name, filename).
AbortSignal for request cancellation.
Proceeds to next middleware. Can pass context and sendContext.
Context Flow
Client Context
Context on the client that doesn’t get sent to the server:
const middleware = createMiddleware({ type: 'function' })
.client(async ({ next }) => {
return next({
context: { localData: 'stays on client' }
})
})
Send Context
Serializable context sent from client to server:
const middleware = createMiddleware({ type: 'function' })
.client(async ({ next }) => {
return next({
sendContext: { userId: '123' } // Sent to server
})
})
.server(async ({ context, next }) => {
console.log(context.userId) // '123'
return next()
})
Server Context
Context on the server that gets sent back to client:
const middleware = createMiddleware({ type: 'function' })
.server(async ({ next }) => {
return next({
context: { serverId: 'abc' }, // Stays on server
sendContext: { timestamp: Date.now() } // Sent to client
})
})
.client(async ({ next }) => {
const result = await next()
console.log(result.context.timestamp) // Available on client
return result
})
Examples
Authentication Middleware
const authMiddleware = createMiddleware({ type: 'function' })
.client(async ({ next }) => {
const token = localStorage.getItem('token')
return next({
headers: { 'Authorization': `Bearer ${token}` }
})
})
.server(async ({ request, next }) => {
const token = request.headers.get('Authorization')
if (!token) {
throw new Error('Unauthorized')
}
const user = await verifyToken(token)
return next({ context: { user } })
})
Logging Middleware
const loggingMiddleware = createMiddleware({ type: 'function' })
.client(async ({ next, serverFnMeta }) => {
console.log(`Calling ${serverFnMeta.id}`)
const start = Date.now()
const result = await next()
console.log(`Completed in ${Date.now() - start}ms`)
return result
})
.server(async ({ next, serverFnMeta }) => {
console.log(`Executing ${serverFnMeta.name}`)
return next()
})
CORS Middleware
const corsMiddleware = createMiddleware({ type: 'request' })
.server(async ({ next }) => {
const result = await next()
result.response.headers.set('Access-Control-Allow-Origin', '*')
return result
})
Rate Limiting
const rateLimitMiddleware = createMiddleware({ type: 'request' })
.server(async ({ request, next }) => {
const ip = request.headers.get('x-forwarded-for')
const isRateLimited = await checkRateLimit(ip)
if (isRateLimited) {
return new Response('Too many requests', { status: 429 })
}
return next()
})
Validation Middleware
import { z } from 'zod'
const userSchema = z.object({
userId: z.string().uuid()
})
const validationMiddleware = createMiddleware({ type: 'function' })
.inputValidator(userSchema)
.server(async ({ data, next }) => {
// data.userId is validated as UUID
return next()
})
Using Middleware
With Server Functions
const myFn = createServerFn()
.middleware([authMiddleware, loggingMiddleware])
.handler(async ({ context }) => {
// Access context from middleware
console.log(context.user)
return { success: true }
})
With Routes (Request Middleware)
export const Route = createFileRoute('/api/users')({
server: {
middleware: [corsMiddleware, rateLimitMiddleware],
handlers: {
GET: async () => {
return Response.json({ users: [] })
}
}
}
})
Global Middleware
Configure global middleware in your app configuration:
import { createStart } from '@tanstack/react-start'
const start = createStart({
requestMiddleware: [corsMiddleware],
functionMiddleware: [loggingMiddleware]
})