Skip to main content

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()
  })

.inputValidator()

Validates input data for function middleware:
validator
Validator
required
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:
data
any
The input data being sent to the server.
context
object
Client-side context accumulated from previous middleware.
sendContext
object
Context to send to the server (must be serializable).
method
'GET' | 'POST'
The HTTP method being used.
signal
AbortSignal
AbortSignal for the request.
serverFnMeta
ClientFnMeta
Metadata about the server function (id).
filename
string
The filename where the server function is defined.
fetch
typeof fetch
The fetch function to use for the request.
next
function
required
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' } })
  })
request
Request
The HTTP Request object.
pathname
string
The request pathname.
context
object
Context accumulated from previous middleware.
serverFnMeta
ServerFnMeta | undefined
Metadata about the server function if this is a server function request, undefined for page requests.
next
function
required
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() } 
    })
  })
data
any
The validated input data.
context
object
Server-side context from previous middleware and sent from client.
method
'GET' | 'POST'
The HTTP method.
serverFnMeta
ServerFnMeta
Server function metadata (id, name, filename).
signal
AbortSignal
AbortSignal for request cancellation.
next
function
required
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]
})

Build docs developers (and LLMs) love