Body
- Accepted Date: 2023-03-22
- Reference Issues/Discussions:
- Implementation PR:
Summary
Introduce a middleware to Astro, where you can define code that runs on every request. This API should work regardless of the rendering mode (SSG or SSR) or adapter used. Also introduces a simple way to share request-specific data between proposed middleware, API routes, and .astro routes.
Background & Motivation
Middleware has been one of the most heavily requested feature in Astro. It's useful for handling common tasks like auth guards and setting cache headers. For me, it would make handling authentication much easier.
This proposal is heavily inspired by SvelteKit's handle hooks;
Goals
- Provide a way intercept requests and responses, allowing users to set cookies and headers
- Works both in SSR and SSG mode
- Allow users to use community-created middlewares (libraries)
- Make available via integrations API.
- Provide an API for request-specific data
- Non-Node runtimes specific APIs. ie. Cloudflare Durable Objects.
- Add middleware from adapter.
Non-Goals
- Route specific middleware, middlewares that are run only on specific routes
Example
A quick prototype
Middlewares can be defined in src/middleware.ts by exporting middleware (array):
export cost middleware = []
A simple middleware looks like so:
export const middleware: Middleware = async (context: APIContext, resolve: Resolve) => {
const session = await getSession(context.request);
const isProtectedRoute = matchRoute(context.url);
if (!session && isProtectedRoute) {
// early response
return new Response(null, {
status: 400,
});
}
context.cookies.set("random-cookie", "some-value");
const response = await resolve(context); // resolve api route or render page
return response;
};
context is the same one provided to API route handlers. Most of Astro's request handling process will be behind resolve(), and the response object returned from middleware will be sent to the user's browser.
Multiple middleware
sequence can be imported to run multiple middlewares in sequence.
export const middleware: Middleware = sequence(
async (context, resolve) => {
console.log("1a");
const response = await resolve(event);
console.log("1b");
return response;
},
async (context, resolve) => {
console.log("2a");
const response = await resolve(event);
console.log("2b");
return response;
}
);
The log result for this example is:
locals
This proposal also adds locals property to APIContext and AsroGlobal. This locals object will be forwarded across the request handling process, allowing for data to be shared between middlewares, API routes, and .astro pages. This is useful for storing request specific data, such as user data, across the rendering step.
export const middleware = async (context: APIContext, resolve: Resolve) => {
context.session = await getSession(context.request);
const response = await resolve(context);
return response;
};
---
// pages/index.astro
const session = Astro.locals.session;
---
The value type of locals can be anything as it won't be JSON-stringified:
---
// pages/index.astro
const session = await Astro.locals.getSession();
---
locals can be typed inside src/env.d.ts (I think it's possible?):
// src/env.d.ts
// at least one of these should be possible
type Locals {}
declare namespace App {
type Locals = {}
}
SSG mode
The middleware function will run on each route's pre-rendering step, as if it were handling a normal SSR request.
export const middleware = async (context: APIContext, resolve: Resolve) => {
const response = await resolve(context); // pre-render step here
return response;
};
Body
Astro.contextRFC RFC:Astro.context#230Summary
Introduce a middleware to Astro, where you can define code that runs on every request. This API should work regardless of the rendering mode (SSG or SSR) or adapter used. Also introduces a simple way to share request-specific data between proposed middleware, API routes, and
.astroroutes.Background & Motivation
Middleware has been one of the most heavily requested feature in Astro. It's useful for handling common tasks like auth guards and setting cache headers. For me, it would make handling authentication much easier.
This proposal is heavily inspired by SvelteKit's
handlehooks;Goals
Non-Goals
Example
A quick prototype
Middlewares can be defined in
src/middleware.tsby exportingmiddleware(array):A simple middleware looks like so:
contextis the same one provided to API route handlers. Most of Astro's request handling process will be behindresolve(), and the response object returned frommiddlewarewill be sent to the user's browser.Multiple middleware
sequencecan be imported to run multiple middlewares in sequence.The log result for this example is:
localsThis proposal also adds
localsproperty toAPIContextandAsroGlobal. Thislocalsobject will be forwarded across the request handling process, allowing for data to be shared between middlewares, API routes, and.astropages. This is useful for storing request specific data, such as user data, across the rendering step.The value type of
localscan be anything as it won't be JSON-stringified:localscan be typed insidesrc/env.d.ts(I think it's possible?):SSG mode
The middleware function will run on each route's pre-rendering step, as if it were handling a normal SSR request.