-
-
Notifications
You must be signed in to change notification settings - Fork 640
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[New Standard] Middlewares that work with any framework (Hono, HatTip, ...) #443
Comments
It's an interesting idea, but I don't know how to implement it because the way to implement middleware is similar but different. I do not want to change the ecosystem we have created. What I want is not universal middleware but the "JUST" library that works on any platform. For example, GraphQL.js works on Cloudflare Workers, but I don't know if ajv will work. The real point is that it is easy to create Hono middleware, so if such a library is available, we should only adapt it to Hono. |
I agree with @yusukebe. I also think it is an interesting idea, but I feel that the cost of commonality outweighs the benefits. We only need a good library and users can wrap it as middleware themselves. |
It's an interesting topic, but I have a feeling it's not easy to make it with small effort. If we think of a middleware as a function which takes context as input, middleware = fn(ctx) the context is actually the container of FetchEvent and additional information (created and attached by framework), so actually the middleware is, middleware = fn(FetchEvent + FrameworkCtx) Due to But If a middleware is working like this, middleware = fn(FetchEvent + FrameworkCtx) = fn'(FrameCtx) + fn''(FetchEvent) It might be easy to create an adaptor to make a middleware available for different framework, otherwise there are still a lot manual work need to done. |
For what it's worth, having a unified middleware standard (PSR-15) for PHP continues to be quite successful. More than 1100 packages rely on this standard. The ecosystem fragmentation, the inability to switch from one package to another, the availability of one package for one framework but not another, and so on - these are some of the things keeping me, personally, from recommending Node.JS to, honestly, anyone. There is a lot of turnover in the JS ecosystem. Frameworks come and go. Same with PHP really, but PSR middleware stands and has been stable since it's inception - use middleware from any vendor, switch frameworks freely. Now that the ecosystem finally seems to have settled on the Request/Response models, it would make sense, and it's probably the right time to come to an agreement on standards for handlers and middleware. Just my two cents. 🙂 (I was involved in the standardization of PSR-15 and I might be able to help.) |
What if, as a first step, we pushed for an interoperability standard for handlers only? The thing is, once you get into middleware territory, things start to get rather opinionated. If you stay in handler territory, it's much less opinionated - a handler is basically Now, the If we could land on a standard for handlers, different framework flavors of middleware should be rather easy to integrate, as any handler can have a factory function (constructor) that allows it to be middleware - it just needs to accept another handler. (see for example Shelf which defines middleware as essentially just Practical example: I've decided to try out Hono and maybe I'd like to integrate, say, Angular - which, sadly, provides only express middleware. If I want this, I'd have to port it from Express to Hono - which means I have to know all the intricacies of Express and Hono. I would think, if we had the option to ship universal middleware, which would plug into Express, Hono, HatTip, Koa, or anything with an adapter, that would be a much more attractive option for most people, wouldn't it? I'm tired of choosing PHP only because of middleware. TypeScript is a great language, and frameworks like Hono look awesome, but I don't want to do a lot of reinventing the wheels - and having those wheels fit only one car. 😅 @brillout where is your thinking on this? I'd be very interested in joining an effort to solve this. |
On a related note, here's an attempt at simplifying fetch for the sake of simplifying middleware - it's rough, but the idea here is to removes all the overloads from |
@mindplay-dk I like idea of starting simple with We can start with vite-plugin-ssr's middleware which is very simple: https://github.com/brillout/vite-plugin-ssr/blob/2b2b48950919da7471bc1160e4a7a2df32eb891e/boilerplates/boilerplate-react/server/index.js#L31-L41. I can create a Regarding the lower-level aspects of your proposal, I don't have much opinion (yet). Ping @cyco130 in case you didn't subscribe to the discussion already :-). @mindplay-dk Ideas for a name? Personally, I'd go for a self explanatory name, e.g. |
I wholeheartedly agree that we need a universal server standard for JavaScript runtimes. But it's hard to build consensus between diverging needs, diverging styles, and the "we already have something working for us" feeling. Of the top of my head, there's Hono, HatTip, SvelteKit adapters, Astro adapters, nitro, and whatwg-node. SolidStart also has its own system but I understand they're rebasing on Astro. I've been in contact with all of them but all have slightly different needs and goals. Hono team, for instance, clearly stated on top of this thread that a common middleware system is out of scope for them. @mindplay-dk didn't like HatTip's Koa-like mutable context approach and wanted a functional This is a lot of duplicate effort. That said, the Astro team is more sympathetic and I only recently connected with the GraphQL focused "The Guild", the team behind Footnotes
|
I think if we can manage to convince a couple of successful projects (e.g. Auth.js) to ship a "universal" middleware, then we can get the ball rolling. The thing here is that library authors do not want to implement a middleware for each JavaScript server framework out there. So we first win over library authors, then we win over framework authors. We don't have to convince framework authors for now as we can implement server framework adapters without talking to these frameworks. But, yea, it's definitely a long haul thing. (On my side I'm quite busy and I've only limited resources. Hopefully this will change.) |
The problem is, Auth.js, GraphQL libraries, vite-plugin-ssr etc. don't really need universal middleware. They need universal handlers which have been already standardized on Auth.js, for example, already uses |
Yeah, as said, I don't think anyone needs universal middleware - and I'm not sure it's a realistic pursuit, as it tends to get extremely opinionated. I didn't realize projects were already starting to standardize on So I was wondering how come the Angular team doesn't do that, and by the looks of it, they're using and exposing the Express Request/Response types - unfortunately, these are not implementation details, so they become the actual dependencies of Angular based projects. There's no adapter fix for that, I'm afraid. They've chosen Express not as a detail but as their framework. It seems the only way out of that would be breaking changes, switching from Express to the fetch model. Well, hopefully |
Interestingly, I poked through Hono's built-in middleware, as well as the third-party middleware listed in the documentation - almost all of it uses the Context argument only to look up the Request and Response. Notable exceptions are things like encoding JSON or HTML responses, which could have been provided as plain functions - and the Sentry middleware, which uses Context as a kind of service locator, to make the Sentry service available, which could have been done with dependency injection instead. Mostly what's in Context seems like it could have been put into helpers instead - it seems to be mostly utilities to help end users writing controller functions. It's rarely if ever used by middleware. Just my superficial analysis, but it seems like the middleware interface here is coupled to more things than it needs to be - and likely all of the middleware could have been "universal", if that meant something like |
For context how about
It can be worth it to write a little "spec" for such context convention and also to rise awareness. I'm only aware of Auth.js who uses |
The goal here being to enable Auth.js to expose |
For a modern standard, I think I would propose |
That would be an awesome start of course :)
You can do it in HatTip today, it's just an object either way. We could consider string keys to be reserved for "official" system-level stuff (cookie, session, router stuff etc.). There's a special But, although I understand the worry, I really doubt name collisions have been a real problem for the ecosystem in 10+ years of Express, to be honest. Btw, I also agree response constructors ( One more idea on symbols: We could turn this around and put the context inside of the response object under a symbol (or use a weak map) and provide back and forth converters between the two formats for interoperability with pure |
Heh, well, Express users have put up with a lot of nonsense for 10+ years. 😅 What if This would leave the string space for reserved platform interop features. So something like: type Context = Record<symbol, unknown> & PlatformContext;
type PlatformContext = {
glob: (pattern: string) => FileInfo[];
open: (path: string) => ReadableStream;
// ...
}
interface FileInfo {
path: string;
// ...
} So If we had some basic functions for listing files, getting file stats, and opening a file, we'd already be able to do a lot - not just static file serving, but also guarded downloads, proxies, etc. I mean, ideally, these features should be defined by Web standards, and we should just say "use those", but the reality is, support for web standards is still very spotty on many of the platforms supported by Hono and HatTip. Having a baseline set of functions for basic file IO would enable (or require) some of these other platforms to polyfill - and where they can't polyfill, it would enable them to at least throw descriptive exceptions. I'd love to hear what you guys think? |
I'd rather have a common platform module. I think of context as the request context, i.e. things that change from request to request. HatTip's |
Yeah, good point - that makes sense. The problem with modules is they complicate testing - I'd like to devise something that relies on dependency injection (a proper platform abstraction) which would simplify both testing and bootstrapping in general. What if we separated "request context" from "platform context"? type RequestContext = Record<symbol, unknown>;
type PlatformContext = {
// ...
}
type HandlerFactory = (platform: PlatformContext) => Handler;
type Handler = (request: Request, context: RequestContext) => Response; Or using a single structural type: type Handler = (platform: PlatformContext) => (request: Request, context: RequestContext) => Response; So a platform implementation would provide an implementation of Thoughts? |
Hi there. That is very interesting but is getting out of Hono's scope. I don't want to create a standard, I want to create Hono. We just want to talk about Hono's middleware, not about standardizing middleware. If you want to continue the discussion, please do it elsewhere. I'll close the issue. |
Sorry for hijacking :) @brillout @mindplay-dk and anyone who wants to continue, we can continue here: hattipjs/hattip#45 |
Thanks for understanding :) |
For those interested, there is new work-in-progress about universal handlers/middlewares: |
Both Hono and HatTip enable third-party libraries (auth libraries, GraphQL libraries, ...) to provide server middlewares for automatic integration: for example, a GraphQL library providing a Hono middleware enables the user to use that middleware to easily integrate the GraphQL library to the user's Hono app.
Hono and HatTip support a wide range of server-side JavaScript environments (Node.js, Cloudflare Workers, Deno, ...), which means that these middlewares work in any of these environments.
But, as a library author, I wouldn't want to have to write one middleware per framework (a Hono middleware + a HatTip middleware + ...).
Instead I'd want to define a single universal middleware that works with any framework:
This means that
import middleware from 'some-library/universal-middleware'
would work with Hono as well as with HatTip.A couple of thoughts:
Ideally this standard would be part of WinterCG (CC @crowlKats @legendecas @littledivy @lucacasonato @MylesBorins @panva @RaisinTen @ryzokuken @surma @targos @tniessen). (Sorry for the mass pinging; I'm not sure who I should ping.)
CC @cyco130 (HatTip author).
The text was updated successfully, but these errors were encountered: