Middleware
Create
Middleware are functions that are composed together to handle requests. Most middleware will be created inline as an argument when creating a route. Middleware can also be created on it’s own to be used globally or to apply to routes.
import type { Middleware } from "ovr";
const mw: Middleware = async (c, next) => {
c; // Middleware.Context - request context
await next(); // Middleware.Next - dispatch the next middleware in the stack
};
Middleware are just functions, use the
Middlewaretype helper to easily type the parameters if you are creating them outside of a route.
Composition
There are two levels of middleware in an ovr application:
- Global - any middleware passed directly to
App.use - Route - middleware added to a specific
Route
For each request, ovr creates an array containing the global middleware, then the matched route’s middleware (if there is a match).
The first middleware in the array will be called, then you can dispatch the next middleware within the first by calling await next() or returning next().
app.use(
async (c, next) => {
console.log("1");
await next(); // dispatches the next middleware below
console.log("3");
},
(c, next) => {
console.log("2");
return next(); // or `return` instead of `await`
},
// ...
);
// 1
// 2
// 3
Context
ovr creates Context (in these docs it’s abbreviated as c) with the current request, then passes it as the first argument into each middleware function to be read and modified.
Request
Context.req contains information about the current request such as the url or params.
Route.get("/api/:id", (c) => {
c.req; // original `Request`
c.url; // parsed web `URL`
c.params; // type-safe route parameters { id: "123" }
c.route; // matched `Route`
c.cookie.get(name); // get cookie
c.form(); // multipart form data
});
Response
Context.res stores the arguments that will be passed into new Response() after middleware has executed.
These properties can be set directly:
Route.get("/api/:id", (c) => {
c.res.body = "# Markdown"; // BodyInit | null | undefined
c.res.status = 200; // number | undefined
c.res.headers.set("content-type", "text/markdown; charset=utf-8"); // Headers
});
// internally, ovr creates the Response with final values:
new Response(c.res.body, { status: c.res.status, headers: c.res.headers });
The response can be also be set with helper functions or by returning a value from the middleware.
Route.get("/api/:id", (c) => {
// use helper functions to set common headers
c.html(body, status); // HTML
c.text(body, status); // plain text
c.json(data, status); // JSON
c.redirect(location, status); // redirect
c.cookie.set(name, value, options); // set cookie
if (c.etag(str)) return; // ETag - sets 304 if match
// return anything from middleware (see next section)
return <p>stream</p>;
});
Return value
ovr handles the return value from middleware in one of two ways.
Response
If a Response is returned from middleware, Context.res.body, Context.res.status will be set to the response’s values, and its headers will be merged into Context.res.headers.
app.use(() => new Response("Hello world"));
Stream
Any value that is returned from middleware will be passed into Render.stream and assigned to Context.res.body.
Returning a value sets the content-type header to html unless it has already been set. Markup content types such as html, xml, and svg will be escaped during the render while other content types will not.
Middleware’s flexible return type makes it easy to stream other types of content than HTML. Simply set the
content-typeheader before returning. Check out the demos for SSE or XML for examples.