Skip to Content
ovr

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.

ts
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 Middleware type 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:

  1. Global - any middleware passed directly to App.use
  2. 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().

ts
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.

ts
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:

ts
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.

tsx
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.

ts
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-type header before returning. Check out the demos for SSE or XML for examples.