-
Notifications
You must be signed in to change notification settings - Fork 474
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
allow custom middlewares for controllers and methods #1123
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hello there cheng81 👋
Thank you and congrats 🎉 for opening your first PR on this project.✨
We will review the following PR soon! 👀
*/ | ||
export function Middlewares(middlewares: Middlewares) { | ||
return decorator(target => { | ||
target._expressMiddlewares = middlewares.express || []; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer using the reflect API here.
It seems like you're reimplementing some of the concepts that API offers manually in the template etc. Is there a reason why your approach is preferable that I'm missing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi, I'll try to take a look at the reflect API and let you know.
I'm not sure what I am reimplementing, if there's already a way to set custom middlewares for the various servers please let me know.
I should add that I'd really prefer to not use a custom routes template, and this feature seems to have been requested a number of times, so it seems to me that it's something the tsoa
users would like.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What this PR is doing is adding properties onto the target which constitutes metadat.
However, there's an API that works well and avoids all of the properties: Reflect.defineMetadata
and Reflect.getMetadata
.
https://rbuckton.github.io/reflect-metadata/
The changes should not be too big:
const TSOA_EXPRESS_MIDDLEWARE = Symbol("TSOA_EXPRESS_MIDDLEWARE")
// ....
export function Middlewares(middlewares: Middlewares) {
return (
target: object,
key: string | symbol,
descriptor: TypedPropertyDescriptor<any>,
) => {
Reflect.defineMetadata(TSOA_EXPRESS_MIDDLEWARE, middlewares.express, descriptor.value);
return descriptor;
};
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@WoH now I see what you mean, I didn't know about reflect-metadata
, I updated the code to make use of it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly ready, I have some minor inconveniences when it comes to maintenance and UX for the end users. If you could try to tighten the typing a bit more, that'd be very appreciated.
export type ExpressMiddleware = (req: any, res: any, next: any) => Promise<any>; | ||
export type KoaMiddleware = (ctx: any, next: any) => Promise<any>; | ||
export type HapiMiddlewareBase = (request: any, h: any) => Promise<any>; | ||
export type HapiMiddlewareSimple = HapiMiddlewareBase | { method: HapiMiddlewareBase; assign?: string; failAction?: HapiMiddlewareBase | string }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would prefer if we could try to type this properly as they are visible
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll try to do it, though that means I'll have to add other dependencies (the typing of express
, koa
and hapi
) to the project.
While this means we'll need to keep up-to-date with those dependencies too, at least we'll know when/if they introduce non-retro compatible changes in their middleware definitions (not that I expect those to happen, but in theory they could do something very asinine like, e.g., putting next
as 1st parameter instead of the 3rd
for the express
middleware)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's probably worth the
a) DX
b) as you mentioned, the chance to detect potential issues earlier
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TBH, I wouldn't mind to have middlewares not typed (using any
). I think it's better solution than having dependencies on express, koa and hapi types and maintaining those dependencies...
@cheng81 Sorry for asking, but what's reason behind using framework specific decorator-middlewares? AFAIK, for type inference you can use generics in your import { RequestHandler } from 'express'; // Importing type from express instead of TSOA
import { Route, Get, Middlewares } from '@tsoa/runtime';
// Using generic to determine correct type of middleware
@Middlewares<RequestHandler>(middleware1)
@Route('Foo')
export class MiddlewareTestController {
@Middlewares<RequestHandler>(middleware2, middleware3)
@Get('/foo')
public async foo(): Promise<void> {
return;
}
} |
@dderevjanik no worries, you shouldn't be sorry for asking! Even using a single middleware with generics would be not ideal (in my head, at least): nothing prevents someone from, say, using Perhaps there's a way to define If we follow the same patter Also as I mentioned in the previous comment, in some way, the version of the framework used is already fixed: the code used in the route templates relies on the version it has been used, and should a major version change some of the code the Finally, of course I can adjust and implement a solution everybody can agree on, I just wanted to present all the reasoning behind what I came up with. |
@cheng81 I see and completely understand what are you trying to achieve. I really like idea of having custom middlewares in TSOA (thank you for opening PR 🚀 ). My opinions are highly subjective. Personally, for me, its a little bit confusing to have defined property Some pros/cons with current approach:
Don't get me wrong, but I would like to suggest to split this PR into 2 iteration, or separated PRs.
|
@dderevjanik @WoH this might be overkilling, but what about having different packages (e.g. I'll possibly implement for now what @dderevjanik suggested (though it'll need to be something like |
I'd rather see if we can use declaration merging in that case (not having all 3 types) |
@WoH I tried to look at declaration merging, but I'm failing to see how it would help here, could you perhaps elaborate a bit? |
This may not work as is, because of the non-ambient vs. ambient context in tests, but the idea is roughly to produce: export declare function ExpressMiddlewares(...middlewares: any[]): ClassDecorator & MethodDecorator; and, as a user, I do: declare module '@tsoa/runtime' {
export function ExpressMiddlewares(...middlewares: Array<import('express').RequestHandler>): ClassDecorator & MethodDecorator;
} |
@WoH yeah, I tried and no matter what, it kept complaining about ambient vs non-ambient errors. |
@cheng81 It looks amazing! Thank you 👍 |
I'm not able to get this in latest npm package ... is there a timeline for releasing it ? |
I installed it via yarn add tsoa@next , but my middleware is not getting called, am I missing something?
|
@saqibarfeen same issue for me as well, middleware doesn't called. |
@RevanthGovindan @saqibarfeen Please make sure you have Reflect API in scope and |
Hi @cheng81, do you have a working example with the feature you just added ? Can't find a way to make it work somehow... |
Do we have a fix on this issue yet? ⬆️ |
Any repro yet? |
Middleware decorators
This PR introduces a bunch of new decorators (and small changes to the route templates) that allows the usage of
express
,koa
, andhapi
middlewares at the route and individual method level.Different middlewares can be specified depending on the server technology used (not completely sure this is actually useful, but it helps
type
-wise).Example:
All Submissions:
Closing issues
closes #948
closes #624
closes #62
closes #47
If this is a new feature submission:
Potential Problems With The Approach
I have some quirks with the current solution:
middlewares
are "stored" in controllers constructor and method functions, and while this works (and has been used somewhat intsoa
already - I'm looking at yourequest.user
defined in theauthentication
middleware), it also feels like an hack rather than a proper solution. If people have a better idea in mind, please do share!middleware
types are super vague (e.g.ExpressMiddleware
is defined as(req: any, res: any, next: any) => Promise<any>;
) since the various server libraries are not dependencies oftsoa
. While I don't think it'll cause problems in practice, I would love if there was a way to make them more precise (perhaps requiring just thetypes
libraries?).Middleware
decorator that accepts...middlewares: any[])
and be done with that: in practice I'm pretty sure that all controllers are created for a single server technology, so having@Middlewares({express: [...], koa: [...]})
is really there just for the benefit of the tests.Route
and the varioushttp
methods (Get
,Post
, and so on).Test plan
I added a
MiddlewareTest
controller with fake middlewares and check in the tests that they are indeed executed.