Skip to content

Routing Basics

Middleware - itty-router

Technically, any function within itty-router can be considered middlware, and you may attach any number of these to a given route.

These follow a few simple rules:

  1. All route handlers are equal in itty.
  2. A handler becomes "middleware" simply by being upstream of subsequent handlers/routes and not returning (or returning undefined or null).
  3. The return of each of each handler is "awaited", allowing any handler to use sync/async syntax.
  4. You may pass any number of handlers to a given route.

Execution of each matched function will proceed until the list is exhausted (no match), or one of them returns anything at all. This is fundamentally different from say, Express.js, where middleware must call a next() function to continue. In itty, just omit a return to continue. This creates fantastically small middleware/route code.

Every handler/middleware has the following signature, where ...args are whatever you passed into the router.fetch(request, ..args) function.

(request: IRequest, ...args): any

Example 1 - simple middleware

In this example, we create a single withKitten middleware, and use it just upstream of a route handler. This embeds the kitten attribute on the request object.

import { Router } from 'itty-router'

// middleware is any function that doesn't return
const withKitten = (request) => {
  request.kitten = {
    name: 'Halsey',
    description: 'adorable',

// define our router
export const router = Router()

  // GET kitten - the kitten is embedded into the request via middleware
  .get('/kitten', withKitten, ({ kitten }) =>

Example 2 - sync vs. async middleware

Since all handlers are awaited during execution, both sync and async handlers are fine to use, with no changes in your route code/flow. 😃

// sync middleware
const withSyncData = (request) => { = 'bar'

// async middleware
const withAsyncData = async (request) => {
  request.items = await db.getItems()

// and to use...

router.get('/test', withSyncData, withAsyncData,
  ({ foo, items }) => `foo is ${foo} and you have ${items.length} items`

Example 3 - global middleware

Any upstream route matches affect all others downstream. This means you can put global middleware at the top of a router, branch, etc. Here we'll show an example of simple user authentication on a particular branch.

import { error, json, Router } from 'itty-router'
import { getUser } from './user-validation'

// MIDDLEWARE: withAuthenticatedUser - embeds user in Request or returns a 401
const withAuthenticatedUser = (request) => {
  const token = request.headers.get('Authorization')
  const user = getUser(token)

  // by returning early here, we cut off all future handlers
  if (!user) return error(401, 'Invalid user.')

  // otherwise, we embed the user for future use
  request.user = user

// define our router
export const router = Router()

  // wildcard will match any route, and "all" matches any HTTP method
  .all('*', withAuthenticatedUser)

  // nothing downstream will be accessible without an authenticated user
  .get('/secret', ({ user }) =>