Middleware
Middleware in JSandy allows you to add reusable logic between your procedure requests and handlers. It's perfect for cross-cutting concerns like authentication, logging, or error handling.
Basic Middleware Structure
const myMiddleware = j.middleware(async ({ c, next }) => {
// 1️⃣ Code that runs before the handler
// ...
// 2️⃣ Pass data to the next middleware/handler
return await next({ customData: "value" })
})Common Use Cases
Authentication Middleware
The following middleware authenticates a user. When our procedure runs, we can be 100% sure that this user really exists because otherwise the middleware will throw an error and prevent the procedure from running:
import { HTTPException } from "hono/http-exception"
import { jsandy } from "@jsandy/rpc"
interface Env {
Bindings: { DATABASE_URL: string }
}
export const j = jsandy.init<Env>()
const authMiddleware = j.middleware(async ({ c, next }) => {
// Mocked user authentication check...
const isAuthenticated = true
if (!isAuthenticated) {
throw new HTTPException(401, {
message: "Unauthorized, sign in to continue.",
})
}
// 👇 Attach user to `ctx` object
return await next({ user: { name: "John Doe" } })
})
/**
* Public (unauthenticated) procedures
* This is the base piece you use to build new procedures.
*/
export const publicProcedure = j.procedure
export const privateProcedure = publicProcedure.use(authMiddleware)On any privateProcedure we can now safely access the user object:
import { j, privateProcedure } from "../jsandy"
export const postRouter = j.router({
list: privateProcedure.get(({ c, ctx }) => {
// 👇 Access middleware data through ctx
const { user } = ctx
return c.json({ posts: [] })
}),
})Middleware Chaining
Chain multiple middlewares using .use():
const enhancedProcedure = publicProcedure
.use(authMiddleware)
.use(loggingMiddleware)
.use(rateLimitMiddleware)If you have multiple middlewares that depend on each other, by definition JSandy cannot know the order in which they were run. Therefore, use the type inference utility:
import { InferMiddlewareOutput, jsandy } from "@jsandy/rpc"
interface Env {
Bindings: { DATABASE_URL: string }
}
export const j = jsandy.init<Env>()
// 1️⃣ Auth middleware runs first
const authMiddleware = j.middleware(async ({ c, next }) => {
return await next({ user: { name: "John Doe" } })
})
type AuthMiddlewareOutput = InferMiddlewareOutput<typeof authMiddleware>
// 2️⃣ Logging middleware runs second
const loggingMiddleware = j.middleware(async ({ c, ctx, next }) => {
const { user } = ctx as AuthMiddlewareOutput
const start = performance.now()
await next()
const end = performance.now()
console.log(`${user.name}'s request took ${end - start}ms`)
})
/**
* Public (unauthenticated) procedures
* This is the base piece you use to build new procedures.
*/
export const publicProcedure = j.procedure
export const privateProcedure = publicProcedure
.use(authMiddleware)
.use(loggingMiddleware)Using Hono Middleware
JSandy is compatible with Hono middleware via the fromHono adapter:
import { j, publicProcedure } from "./jsandy"
import { cors } from "hono/cors"
const corsMiddleware = j.fromHono(cors())
const procedureWithCors = publicProcedure.use(corsMiddleware)Best Practices
- Keep middleware focused on a single responsibility
- Handle errors via your
appRouter'sonError()
Common Middleware Examples
- Authentication
- Request logging
- Rate limiting
- Error handling
- Request validation
- Performance monitoring
- CORS handling
→ To see all built-in Hono middleware, check out the Hono middleware documentation.
