Middleware
Middleware in Switchblade provides a powerful way to intercept and modify requests and responses across your application. They can:
- Modify request or response
- Execute additional code
- End the request-response cycle
Middleware is applied in the chained order of the routes.
TIP
Middleware also have it's own options (validation & openapi, except responses validation) like a route, which then will be passed to the registered route below it.
Usage
WARNING
Be careful, avoid this mistake:
- If the route handler is async function, MAKE SURE to use
await next()instead ofnext(). - Make sure to call
next()at the end of your middleware function. If not the request will be ended, unless you want it that way.
typescript
import { Switchblade } from "@takodotid/switchblade";
const app = new Switchblade()
// Global middleware since it's defined first before any route
.use(
(req, res, next) => {
console.log(`Incoming ${req.method} request to ${req.url}`);
// You can modify request or set additional properties
req.state.requestTime = Date.now();
next();
},
{
openapi: {
// This tags will be passed to /users and /users/:id below
tags: ["Global"],
},
headers: {
// This `authorization` header will also be passed to /users and /users/:id below
authorization: z.string().optional(),
},
}
)
.get(
"/users",
(req, res) => {
return res.json({ users: [] });
},
{
openapi: {
// This will override the `Global` tags above
tags: ["Users"],
},
}
)
.group("/users/:id", (app) => {
return (
app
// This middleware will be applied to all routes in this group
.use(async (req, res, next) => {
let isRatelimited = getRateLimit(req);
if (isRatelimited) {
return res.status(429).json({ error: "Rate limit exceeded" });
}
console.log(`User ID: ${req.params.id}`);
await next();
})
.get("/", (req, res) => {
return res.json({ userId: req.params.id });
})
);
});Execution Order
Just like Hono, unlike Elysia, the next() function is actually a function to call the next middleware/route handler, so you can use like AsyncLocalStorage or decide whether to call the next middleware or not. See the following example:
typescript
new Switchblade()
.use(async (req, res, next) => {
console.log("Middleware 1 Start");
asyncLocalStorage.run(requestCtx, () => {
await next();
});
console.log("Middleware 1 End");
})
.use(async (req, res, next) => {
console.log("Middleware 2 Start");
await next();
console.log("Middleware 2 End");
})
.use(async (req, res, next) => {
console.log("Middleware 3 Start");
await next();
console.log("Middleware 3 End");
})
.get("/", (req, res) => {
const requestCtx = asyncLocalStorage.getStore();
console.log(`Hello world!`);
return res.setHeader("X-Request-ID", requestCtx.requestId).text("Hello!");
});This will result in the following output:
Middleware 1 Start
Middleware 2 Start
Middleware 3 Start
Hello world!
Middleware 3 End
Middleware 2 End
Middleware 1 EndMiddleware State
Middleware can add data to the request state:
typescript
app.use((req, res) => {
// Add custom properties to request
req.state.requestId = generateUniqueId();
});
app.get("/users", (req, res) => {
// Access middleware-added state
console.log(req.state.requestId);
});