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
Multiple endpoints executed #78
Comments
Hi @darko1979 , Yes. it is a problem... But I am not sure what is the best solution to solve it. We need to call next to ensure that any middleware placed after the typescript-rest middlewares be called. Take a look at #68 |
I think we need to add something to give more control to developer to specify if he wants that typescript-rest send a response to the client... I will think about how could be this contract... And we are accepting suggestions |
I have a suggestion: add option in Path decorator to control if next is called. Here are proposed changes: feat: path decorator option to call next if headers are sent In my solution this option to call next if headers are sent is enabled by default, and if somebody does not wan't this it can set this option to false, for example: |
"We need to call next to ensure that any middleware placed after the typescript-rest middlewares be called." For example, a common pattern (straight from Express docs) is a 404 handler at the bottom of your middleware stack.
Obviously this would not work with how things are now. I think |
@greghart |
So I think first we should decide what that would look like in vanilla Express, and then decide how to approach w.r.t. Always call next methodnecessitates all middleware to have to check if response has gone out yet // route handler -- this is not even possible through `typescript-rest` in current implementation
express.get( "/test", (req, res, next) => {
if (!res.headersSent) {
res.send( "hello world" );
}
next(); // Calling next always
});
express.get('/:token', (req, res, next) => {
if (!res.headersSent) {
res.send('some token');
}
next();
});
// Performance handling
express.use((req, res, next ) {
// Do some logging or something
} )
// 404 handler
express.use((req, res, next ) {
if (!res.headersSent) {
res.status(404).send("Sorry can't find that!");
}
} ); As we can see, one case is completely not supported, and it adds complexity across the board. Don't next after respondingAs an alternative, you could just add event handler onto response if you need to do something after response has gone out. // Performance handling -- setup listener before routes
express.use((req, res, next ) {
res.once('finish', () => {
// Do some logging or something
})
})
// route handler
express.get( "/test", (req, res, next) => {
res.send( "hello world" );
});
express.get('/:token', (req, res, next) => {
res.send('some token');
});
// 404 handler
express.use((req, res, next ) {
res.status(404).send("Sorry can't find that!");
} ); And even better, this is supported through typescript-rest -- just setup middleware before your routes, or even use preprocessors to setup cleanup as needed per route. |
Hello, I landed here as we are experiencing this issue. The change linked to this issue looks more like a breaking change to me. Besides, I don't see a good enough reason why you would add a middleware after builder services. I believe this makes the lib less predictable. Adding an option to choose the strategy (nextAfterResponse) globally would fix the issue for me. Cheers |
Just want to add that if you follow the recommended pattern of doing an app.use(...) as the last route to capture and format 404 routes you will get bitten by this as well. Every supposedly handled path will also trigger the 404 catch all. EDIT: And I totally glossed over @greghart his comment saying pretty much the same thing. |
Hi, I've put together the different suggestions made here and I think we have a solution: Next functionBy default, we call the next function after endpoint execution (even if headers are already sent). As discussed previously, there are use cases that need this behaviour (logs, for example). If we need to disable this, we must be able to inform it explicitly. So, the proposal is to have an annotation @Path('test')
class TestService {
@GET
@IgnoreNextMiddlewares
test() {
//...
return 'OK';
}
} Remember that we already have a way to explicitly call next if we need to do it according with a specific condition inside our service handler. We can use Context.next Service ReturnBy default, we serialize the service result to the user as a response. If the method does not return nothing (void), we send an empty response with a 204 status. It is useful to simplify all the situations where we don't have nothing to send as response. But, if you need to handle the user response by yourself, you should be able to explicitly inform this. The proposal here is to have a specific return value to inform that you don't want to return nothing. import {Return} from "typescript-rest";
@Path("noresponse")
class TestNoResponse {
@GET
public test() {
return Return.NoResponse;
}
}
app.use("/noresponse", (req, res, next) => {
res.send("I am handling the response here, in other middleware");
}); or import {Return} from "typescript-rest";
@Path("noresponse")
class TestNoResponse {
@GET
public test(@ContextResponse res: express.Response) {
res.send("I am handling the response here, don't do it automatically");
return Return.NoResponse;
}
} I think that these proposal:
|
Re: Next Function, I still believe the logging use case has better ways of handling than altering the expected workflow for Express users (such as response event handling which is an already existing solution). Or at least switching the default so most users don't have to annotate W.r.t. backwards compatibility, since #68 was itself a breaking change, I see this more as a bugfix than a backwards compatibility issue. |
Hi @greghart ,
I did not disagree about the logging use case. But the question is what is the I believe that if somebody add a middleware, after the Typescript-rest handler, directly in the express router, that person expects that it will be called after a service call. let app: express.Application = express();
Server.buildServices(app);
app.use("/mypath", (req, res, next) => {
// If I add something here, with a middleware in express directly, I expect that it must be called.
}); That is why we handled #68 as a bug and not a change. But we can put a configuration property in the something like: Server.ignoreNextMiddlewares(true); |
I think your example highlights the mis-communication, as I think conventional Express users (and the other people in this thread) would expect that next middleware not to happen if FWIW +1 to a top level switch to reconcile this 👍 |
Hi,
Maybe... but the point is that expressjs users have the chance to choose when and how to call the methods We need to add support to give more control to users of typescript-rest. The changes proposed here allow all possible uses cases. And with the top level switch ( And thanks a lot for all contributions on this topic |
When we have multiple endpoints that can be matched with the request path all endpoints are executed, but I expected only first one to be executed.
I made an example to demonstrate my problem:
If I make a GET request 'http://localhost:8080/api/test' it will execute both f1 and f2 functions but will fail with following error:
Error: Can't set headers after they are sent.
because response headers are set in the first one.
Using pure javascript I don't have this problem:
because after matched endpoint next is not called.
Using typescript-rest I don't have this control and next is always called.
I traced a possible solution in server-container.ts line 328 (part of buildServiceMiddleware function) where I could replace
next();
with
if (!res.headersSent) next();
If this is intended by design could this be made optional by adding another decorator or adding options to path decorator that will indicate to only execute first matched endpoint?
I'm not satisfied with current solution to change paths so this does not happen.
The text was updated successfully, but these errors were encountered: