Shapeless
Backend

Middleware

Middleware

Middleware in Shapeless lets you add reusable logic that runs between procedure calls and handlers. It’s ideal for cross-cutting concerns like authentication, logging, and error handling.


Basic Middleware Structure

// resources/shapeless.ts
const myMiddleware = app.middleware(async ({ c, next }) => {
  // 1️⃣ Code that runs before the handler
  // ...

  // 2️⃣ Pass data to the next middleware or handler
  return await next({ customData: "value" })
})

Common Use Cases

Authentication Middleware

This middleware ensures the user is authenticated before proceeding. If not authenticated, it throws an error and blocks the procedure:

// resources/shapeless.ts
import { HTTPException } from "hono/http-exception"
import { shapeless } from "@shapelesss/core"

interface Env {
  Bindings: { DATABASE_URL: string }
}

export const app = shapeless.init<Env>()

const authMiddleware = app.middleware(async ({ c, next }) => {
  // Mock authentication check
  const isAuthenticated = true

  if (!isAuthenticated) {
    throw new HTTPException(401, {
      message: "Unauthorized, please sign in.",
    })
  }

  // Attach user info to context for downstream handlers
  return await next({ user: { name: "John Doe" } })
})

export const publicProcedure = app.procedure
export const privateProcedure = publicProcedure.use(authMiddleware)

Now, in any privateProcedure, you can safely access the user info:

// resources/routers/post-router.ts
import { app, privateProcedure } from "../shapeless"

export const postRouter = app.router({
  list: privateProcedure.get(({ c, ctx }) => {
    // Access user info injected by middleware
    const { user } = ctx

    return c.json({ posts: [] })
  }),
})

Middleware Chaining

You can chain multiple middlewares using .use():

const enhancedProcedure = publicProcedure
  .use(authMiddleware)
  .use(loggingMiddleware)
  .use(rateLimitMiddleware)

If middleware depends on previous middleware outputs, use Shapeless’s type inference utility to type ctx correctly:

// resources/shapeless.ts
import { InferMiddlewareOutput, shapeless } from "@shapelesss/core"

interface Env {
  Bindings: { DATABASE_URL: string }
}

export const app = shapeless.init<Env>()

const authMiddleware = app.middleware(async ({ c, next }) => {
  return await next({ user: { name: "John Doe" } })
})

type AuthOutput = InferMiddlewareOutput<typeof authMiddleware>

const loggingMiddleware = app.middleware(async ({ c, ctx, next }) => {
  const { user } = ctx as AuthOutput

  const start = performance.now()
  await next()
  const end = performance.now()

  console.log(`${user.name}'s request took ${end - start}ms`)
})

export const publicProcedure = app.procedure
export const privateProcedure = publicProcedure
  .use(authMiddleware)
  .use(loggingMiddleware)

Using Hono Middleware

Shapeless is compatible with Hono middleware using the fromHono adapter:

import { app } from "../shapeless"
import { cors } from "hono/cors"

const corsMiddleware = app.fromHono(cors())
const procedureWithCors = publicProcedure.use(corsMiddleware)

Best Practices

  • Keep middleware focused on a single responsibility.
  • Handle errors globally with your appRouter's onError() method.

Common Middleware Examples

  • Authentication
  • Request logging
  • Rate limiting
  • Error handling
  • Input validation
  • Performance monitoring
  • CORS handling

→ See all built-in Hono middleware in the Hono docs.