Express: Feature request: pass middleware by promise

Created on 17 Sep 2020  路  6Comments  路  Source: expressjs/express

I've moved to ES modules and I've found it much easier to use dynamic imports for each of my middleware/routers that are from other files rather than managing many ES imports for the routers/functions.

Since I've only needed it for reducing the code of my main tree of routes, I've just created an extension of the router.use() like this:

const getRt = (rt) => rt?.router || rt;
const myRouter = (opts=null) => {
    const router = express.Router(opts);
    router.us = async function us(path, ...middleware) {
        const tmpMid = [];
        for (let mid of middleware) {
            if (mid instanceof Promise) {
                await mid.then((x) => tmpMid.push(getRt(x))).catch((err) => console.log(err));
            } else {
                tmpMid.push(mid?.router || mid);
            }
        }
        this.use(path, ...tmpMid);
    };
    return router;
};
discuss

Most helpful comment

I'm not sure this type of thing is actually in scope of Express.js core, as it seems to be a helper loader for a specific way to do route loading.

All 6 comments

I'm not sure this type of thing is actually in scope of Express.js core, as it seems to be a helper loader for a specific way to do route loading.

I sort of agree, but I also think there should be a better way to work with importing middleware in an ES module environment.
In my commonjs project I'd make heavy use of
router.use('/category', require('./category'))
and structuring my project like

/api
 - index.js
 - /category
    - index.js

Maybe there's a way to structure my project to achieve similar level of conciseness. I'd really like to find a cleaner solution to achieving a similar structure in ES module projects.

Well, maybe there is a misunderstanding :) I'm not saying your problem should not be solved, but that it is out-of-scope of Express.js core, as we currently define it. Express.js itself says out of the business of attempting to be a module loading, specifically because that makes it harder to use Express.js in cases where you need that flexibility; think of it being pre-packed using Webpack, for instance. As in, we currently assume that how to load your data from disk and structure your JavaScript files is a separate responsibility to the web framework itself, and there are many more-encompassing frameworks built on top of Express that does add those additional pieces--it is not too dissimilar to how we keep most of our functionality itself outside of Express.js core, like cors, morgan, csurf, compression, session, and many others.

I'm not sure if a module like https://www.npmjs.com/package/auto-route-loader is something inline with what you are looking to exist, for example.

Ah,
No, I'm not talking about doing the importing, I agree that makes no sense for express to do. I'm thinking along the lines of just being able to pass a promise to express that will eventually resolve to middleware, regardless of what is actually providing it.
If it did that, then just passing it a dynamic import in ES land would do the trick (though then there's the issue that export default is considered bad practice), or it would allow for wrapping express directly with resolvers etc.

Oh, I see, thanks for the clarification! So the idea is that you would, ultimately, want to do app.use(promise) where promise will eventually resolve to a middleware? If I'm understanding you correctly, it _seems_ on the surface needlessly complicated for the router to handle, but that is just a feeling. I think to understand the proposal, we should probably get it flushed out more. For example, what should happen if that promise rejects? When it resolves to something not a middleware, like a string? Should .listen be allowed to be called if there are still unresolved middleware? And of course, many, many more cases. I think the next step would be to work to clarify how all this would work, from your point of view as the proposer, so we can understand what should happen. If there are cases you think of, but you're not sure how it should work, list those out and we can work together to resolve them all.

The more that I think about it, the more I think you're right that it should be a separate package. For my use case, importing ES modules, if you follow the best practice of not using export default, then the structure of the object returned from the promise could get pretty crazy, and specifying strict rules for exports might sabotage new users a bit.
If it was to be in express, it might be easiest to only take instances of Router from the promises, so if doing ES modules you could export routers by any name and import them as middleware, and/or you could have a specific name/key that would be pulled from an object.
When using it myself it seemed to work okay having all the .catch() and handling outside express, and starting the listen() immediately with the completion of the synchronous code and then exiting if any promises passed were uncaught promises. Though I'm sure that whether it awaits on all async routes or not would need to be configurable.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

snowdream picture snowdream  路  3Comments

HafidAbnaou picture HafidAbnaou  路  3Comments

guyisra picture guyisra  路  3Comments

AndrewEQ picture AndrewEQ  路  4Comments

wxs77577 picture wxs77577  路  3Comments