Astro middleware #469
Replies: 6 comments 4 replies
-
One common pattern for middleware found in Express and Next.js looks something like this: const middleware = (req, res, next) => {
// do stuff
if (condition) {
next();
}
}; However, this is only possible because Node native request/response API is used. Since Astro uses web standard |
Beta Was this translation helpful? Give feedback.
-
I like this a lot! Thanks for sharing the proposal. 🙌🏻 Enforcing namespaces is something I'd want to factor into the proposal to avoid collisions as much as possible. Type-safety is also very important for DX, so I'd love if we could do this in a type-safe way. Here's a quick experiment in that vein on the TypeScript Playground. I've implemented "locals" (renamed to "data") with namespacing. Providers (middleware) could register their "data" using the following pattern, ensuring that their data is properly namespaced. interface AuthData {
user: { name: string };
authenticated: boolean;
}
declare module 'astro:middleware' {
interface Register {
"@acme/auth-middleware": AuthData
}
} Consumers (user, other middleware) could then read from "data" in a type-safe way. // Get all data
const { authenticated, username } = context.data.get('@acme/auth-middleware');
// Get specific nested key
const username = context.data.get('@acme/auth-middleware', 'user.name'); This is possibly by using a type Key = string|number|symbol;
class NamespacedMap<TNamepsace extends Key, TData extends Record<Key, any>> {
#data: Record<TNamepsace, TData> = {} as any;
set(namespace: TNamepsace, value: TData): void;
set<TKey extends keyof TData>(namespace: TNamepsace, key: TKey, value: TData[TKey]): void;
set(namespace: TNamepsace, ...args: any): void {
if (args.length === 0) throw new Error('TODO');
if (args.length === 1) {
const [value] = args;
this.#data[namespace] = value;
return;
}
if (args.length === 2) {
// TODO: handle dot syntax
const [key, value] = args as [keyof TData, TData[keyof TData]];
this.#data[namespace][key] = value;
return;
}
}
get(namespace: TNamepsace): TData;
get<TKey extends keyof TData>(namespace: TNamepsace, key: TKey): TData[TKey];
get(namespace: TNamepsace, ...args: any): any {
if (args.length === 0) return this.#data[namespace];
// TODO: handle dot syntax
const [key] = args as [keyof TData];
return this.#data[namespace][key];
}
} |
Beta Was this translation helpful? Give feedback.
-
This is something we would require to allow for some of the magic that SST does to make sure things work the same in dev and when released. We have a whole hooks system that needs to be booted up when an request comes in |
Beta Was this translation helpful? Give feedback.
-
I built and maintained a middleware system for use in Cloudflare workers, which uses the service-worker environment. The API for that system was export async function preventFraming(fetchEvent: FetchEvent, next: Middleware) {
const upstreamResponse = await next(fetchEvent)
if (!upstreamResponse.headers.has('x-frame-options') {
upstreamResponse.headers.set('x-frame-options', 'DENY')
}
return upstreamResponse
}
export async function adminAuth(fetchEvent: FetchEvent, next: Middleware) {
const { pathname } = new URL(fetchEvent.request.url)
if (!/^\/admin\//.test(pathname)) return next(fetchEvent)
const rawJWT = fetchEvent.request.headers.get('jwt')
if (!rawJWT) {
return new Response('Unauthorized', { status: 401 })
}
const jwt = await parseJWT(rawJWT)
if (jwt?.payload?.roles?.indexOf('admin:read') ?? -1 === -1) {
return new Response('Forbidden', { status: 403 })
}
return next(fetchEvent)
} I like this API because it's closely tied to open standards ( They also compose easily with
|
Beta Was this translation helpful? Give feedback.
-
I like this idea a lot, I think it's on the right track. Couple of questions:
|
Beta Was this translation helpful? Give feedback.
-
This proposal has been accepted and has moved into stage 2: #531 |
Beta Was this translation helpful? Give feedback.
-
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
Non-goals
Example
A quick prototype
Middlewares can be defined in
src/middleware.ts
by exportingmiddleware
(array):A simple middleware looks like so:
context
is the same one provided to API route handlers. Most of Astro's request handling process will be behindresolve()
, and the response object returned frommiddleware
will be sent to the user's browser.Multiple middleware
sequence
can be imported to run multiple middlewares in sequence.The log result for this example is:
locals
This proposal also adds
locals
property toAPIContext
andAsroGlobal
. Thislocals
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.The value type of
locals
can be anything as it won't be JSON-stringified:locals
can 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.
Beta Was this translation helpful? Give feedback.
All reactions