Middleware
Edit this pageMiddleware intercepts HTTP requests and responses to perform tasks like authentication, redirection, logging, and more.
It also enables sharing request-scoped data across the application using the event.locals
object.
Common use cases
Here are some common use cases for middleware:
- Request and response header management: Middleware allows modifying headers to control caching (e.g.,
Cache-Control
), improve security (e.g.,Content-Security-Policy
), or implement custom behaviour based on request characteristics. - Global data sharing: The
event.locals
object allows storing and sharing request-scoped data between middleware and any server-side context (e.g., API routes, server-only queries/actions). This is useful for passing information like user authentication status, feature flags, or other request-related data. - Server-side redirects: Middleware can redirect users based on various request properties, such as locale, authentication state, or custom query parameters.
- Request preprocessing: Middleware can perform lightweight preprocessing tasks, such as validating tokens or normalizing paths.
Limitations
While middleware is powerful, certain tasks are better handled in other parts of your application for performance, maintainability, or security reasons:
- Authorization: Middleware does not run on every request, especially during client-side navigations. Relying on it for authorization would create a significant security vulnerability. As a result, authorization checks should be performed as close to the data source as possible. This means it within API routes, server-only queries/actions, or other server-side utilities.
- Heavy computation or long-running processes: Middleware should be lightweight and execute quickly to avoid impacting performance. CPU-intensive tasks, long-running processes, or blocking operations (e.g., complex calculations, external API calls) are best handled by dedicated route handlers, server-side utilities, or background jobs.
- Database operations: Performing direct database queries within middleware can lead to performance bottlenecks and make your application harder to maintain. Database interactions should be handled by server-side utilities or route handlers, which will create better management of database connections and handling of potential errors.
Basic usage
Middleware is configured by exporting a configuration object from a dedicated file (e.g., src/middleware/index.ts
).
This object, created using the createMiddleware
function, defines when middleware functions execute throughout the request lifecycle.
import { createMiddleware } from "@solidjs/start/middleware";
export default createMiddleware({ onRequest: (event) => { console.log("Request received:", event.request.url);
event.locals.startTime = Date.now(); }, onBeforeResponse: (event) => { const endTime = Date.now(); const duration = endTime - event.locals.startTime; console.log(`Request took ${duration}ms`); },});
For SolidStart to recognize the configuration object, the file path is declared in app.config.ts
:
import { defineConfig } from "@solidjs/start/config";
export default defineConfig({ middleware: "src/middleware/index.ts",});
Lifecycle events
A middleware function executes at specific points in the request lifecycle, using two key events: onRequest
and onBeforeResponse
.
onRequest
The onRequest
event is triggered at the beginning of the request lifecycle, before the request is handled by the route handler.
This is the ideal place to:
- Store request-scoped data in
event.locals
for use in later middleware functions or route handlers. - Set or modify request headers.
- Perform early redirects.
onBeforeResponse
The onBeforeResponse
event is triggered after a request has been processed by the route handler but before the response is sent to the client.
This is the ideal place to:
- Set or modify response headers.
- Log response metrics or perform other post-processing tasks.
- Modify the response body.
Locals
In web applications, there's often a need to share request-specific data across different parts of the server-side code. This data might include user authentication status, trace IDs for debugging, or client metadata (e.g., user agent, geolocation).
The event.locals
is a plain JavaScript object that can hold any JavaScript value.
This object provides a temporary, request-scoped storage layer to address this need.
Any data stored within it is only available during the processing of a single HTTP request and is automatically cleared afterward.
import { createMiddleware } from "@solidjs/start/middleware";
export default createMiddleware({ onRequest: (event) => { event.locals.user = { name: "John Wick", }; event.locals.sayHello = () => { return "Hello, " + event.locals.user.name; }; },});
Within middleware, event.locals
can be accessed and modified directly.
Other server-side contexts must use the getRequestEvent
function to access the event.locals
object.
import { getRequestEvent } from "solid-js/web";import { query, createAsync } from "@solidjs/router";
const getUser = query(async () => { "use server"; const event = getRequestEvent(); return { name: event?.locals?.user?.name, greeting: event?.locals?.sayHello(), };}, "user");
export default function Page() { const user = createAsync(() => getUser());
return ( <div> <p>Name: {user()?.name}</p> <button onClick={() => alert(user()?.greeting)}>Say Hello</button> </div> );}
Headers
Request and response headers can be accessed and modified using the event.request.headers
and event.response.headers
objects.
These follow the standard Web API Headers
interface, exposing built-in methods for reading/updating headers.
import { createMiddleware } from "@solidjs/start/middleware";
export default createMiddleware({ onRequest: (event) => { // Reading client metadata for later use const userAgent = event.request.headers.get("user-agent"); // Adding custom headers to request/response event.request.headers.set("x-custom-request-header", "hello"); event.response.headers.set("x-custom-response-header1", "hello"); }, onBeforeResponse: (event) => { // Finalizing response headers before sending to client event.response.headers.set("x-custom-response-header2", "hello"); },});
Headers set in onRequest
are applied before the route handler processes the request, allowing downstream middleware or route handlers to override them.
Headers set in onBeforeResponse
are applied after the route handler and are finalized for the client.
Cookies
HTTP cookies are accessible through the Cookie
request header and Set-Cookie
response header.
While these headers can be manipulated directly, Vinxi, the underlying server toolkit powering SolidStart, provides helpers to simplify cookie management.
See the Vinxi Cookies documentation for more information.
import { createMiddleware } from "@solidjs/start/middleware";import { getCookie, setCookie } from "vinxi/http";
export default createMiddleware({ onRequest: (event) => { // Reading a cookie const theme = getCookie(event.nativeEvent, "theme");
// Setting a secure session cookie with expiration setCookie(event.nativeEvent, "session", "abc123", { httpOnly: true, secure: true, maxAge: 60 * 60 * 24, // 1 day }); },});
Custom responses
Returning a value from a middleware function immediately terminates the request processing pipeline and sends the returned value as the response to the client. This means no further middleware functions or route handlers will be executed.
import { createMiddleware } from "@solidjs/start/middleware";
export default createMiddleware({ onRequest: () => { return new Response("Unauthorized", { status: 401 }); },});
Only Response
objects can be returned from middleware functions.
Returning any other value will result in an error.
Redirects
Solid Router provides the redirect
helper function which simplifies creating redirect responses.
import { createMiddleware } from "@solidjs/start/middleware";import { redirect } from "@solidjs/router";
const REDIRECT_MAP: Record<string, string> = { "/signup": "/auth/signup", "/login": "/auth/login",};
export default createMiddleware({ onRequest: (event) => { const { pathname } = new URL(event.request.url);
// Redirecting legacy routes permanently to new paths if (pathname in REDIRECT_MAP) { return redirect(REDIRECT_MAP[pathname], 301); } },});
This example checks the requested path and returns a redirect response if it matches a predefined path. The 301 status code indicates a permanent redirect. Other redirect status codes (e.g., 302, 307) are available as needed.
JSON responses
Solid Router provides the json
helper function which simplifies sending custom JSON responses.
import { createMiddleware } from "@solidjs/start/middleware";import { json } from "@solidjs/router";
export default createMiddleware({ onRequest: (event) => { // Rejecting unauthorized API requests with a JSON error const authHeader = event.request.headers.get("Authorization"); if (!authHeader) { return json({ error: "Unauthorized" }, { status: 401 }); } },});
Chaining middleware functions
onRequest
and onBeforeResponse
options in createMiddleware
can accept either a single function or an array of middleware functions.
When an array is provided, these functions execute sequentially within the same lifecycle event.
This enables composing smaller, more-focused middleware functions, rather than handling all logic in a single, large middleware function.
import { createMiddleware } from "@solidjs/start/middleware";import { type FetchEvent } from "@solidjs/start/server";
function middleware1(event: FetchEvent) { event.request.headers.set("x-custom-header1", "hello-from-middleware1");}
function middleware2(event: FetchEvent) { event.request.headers.set("x-custom-header2", "hello-from-middleware2");}
export default createMiddleware({ onRequest: [middleware1, middleware2],});
The order of middleware functions in the array determines their execution order. Dependent middleware functions should be placed after the middleware functions they rely on. For example, authentication middleware should typically run before logging middleware.