Motia Icon
Development Guide

Middleware

Run code before and after your API handlers

What is Middleware?

Middleware runs before your API handler. Use it for authentication, logging, error handling, or any logic that applies to multiple endpoints.


How It Works

A middleware is a function that receives three arguments:

middleware(req, ctx, next)
  • req - The incoming request (same as handler)
  • ctx - The context object (same as handler)
  • next() - Call this to continue to the handler

If you don't call next(), the request stops. The handler never runs.


Simple Example

import { ApiMiddleware } from 'motia'
 
const authMiddleware: ApiMiddleware = async (req, ctx, next) => {
  if (!req.headers.authorization) {
    return { status: 401, body: { error: 'Unauthorized' } }
  }
  return next()
}
 
export const config = {
  name: 'ProtectedEndpoint',
  type: 'api',
  path: '/protected',
  method: 'GET',
  middleware: [authMiddleware]
}
 
export const handler = async (req, ctx) => {
  return { status: 200, body: { message: 'Success' } }
}

Execution Order

Middleware runs in the order you list them:

export const config = {
  name: 'MyEndpoint',
  type: 'api',
  path: '/endpoint',
  method: 'POST',
  middleware: [
    loggingMiddleware,  // Runs first
    authMiddleware,     // Runs second  
    errorMiddleware     // Runs third
  ]
}

Modifying Responses

Await next() to get the response, then modify it:

const addHeadersMiddleware = async (req, ctx, next) => {
  const response = await next()
  
  return {
    ...response,
    headers: {
      ...response.headers,
      'X-Request-Id': ctx.traceId
    }
  }
}

Error Handling

Catch errors from handlers:

import { ZodError } from 'zod'
 
const errorMiddleware = async (req, ctx, next) => {
  try {
    return await next()
  } catch (error: any) {
    if (error instanceof ZodError) {
      ctx.logger.error('Validation error', { errors: error.errors })
      return { status: 400, body: { error: 'Validation failed' } }
    }
 
    ctx.logger.error('Unexpected error', { error: error.message })
    return { status: 500, body: { error: 'Internal server error' } }
  }
}

Reusing Middleware

Create middleware files in a shared location:

middlewares/core.middleware.ts
export const coreMiddleware = async (req, ctx, next) => {
  try {
    return await next()
  } catch (error) {
    ctx.logger.error('Error', { error })
    return { status: 500, body: { error: 'Internal server error' } }
  }
}

Import and use across steps:

steps/user.step.ts
import { coreMiddleware } from '../middlewares/core.middleware'
 
export const config = {
  name: 'GetUser',
  type: 'api',
  path: '/users/:id',
  method: 'GET',
  middleware: [coreMiddleware]
}

What's Next?

Need help? See our Community Resources for questions, examples, and discussions.

On this page