Visitar URL original
Fastify will not allow route registration after ready · Issue #1771 · fastify/fastify · GitHub
Skip to content

Fastify will not allow route registration after ready #1771

Closed

Description

@areese159

🐛 Bug Report

Calling fastify.ready() causes errors to be thrown if you attempt to add routes after it finishes. Specifically, it will throw Cannot add route when fastify instance is already started! which seems like undesired behavior. This seems to be caused by Avvio emitting the start event when calling ready, since ready comes from Avvio. When start is emitted fastify appears to no longer allow route registration which seems to not be the correct behavior. From looking at the documentation on ready it appears this should not be a side effect and only the plugins should be loaded.

To Reproduce

const fastify = require("fastify");
const fp = require("fastify-plugin");

const plugin = fp(function(fastify, opts, next) {
    next();
});
const start = async function () {
    const app = fastify();
    app.register(plugin);
    await app.ready();
    // this will throw an error, specifically "Cannot add route when fastify instance is already started!"
    app.post('/test', function () {});
};
start();

Expected behavior

I would expect that fastify loads the plugins as expected but will continue to allow route registration until listen is called.

const fastify = require("fastify");
const fp = require("fastify-plugin");

const plugin = fp(function(fastify, opts, next) {
    next();
});
const start = async function () {
    const app = fastify();
    app.register(plugin);
    await app.ready();
    // this will not throw an error and fastify will allow you to register the route"
    app.post('/test', function () {});
};
start();

Your Environment

  • node version: 10.15.3
  • fastify version: >=2.6.0
  • os: Linux (Ubuntu)

Activity

  1. delvedor commented on Jul 26, 2019

    @delvedor
    Member

    Specifically, it will throw Cannot add route when fastify instance is already started! which seems like undesired behavior.

    It is the wanted behavior :)
    Before start listening for requests, Fastify will "compile" all of your plugins and routes (which are a special kind of plugin) to a fast data structure, so it can be accessed later very quickly.
    To allow this, we can't accept new plugins after the invocation of .ready.

    The question is, why you need to register more routes after calling ready or listen?

  2. areese159 commented on Jul 26, 2019

    @areese159
    Author

    I was trying to have a plugin that would dynamically load the routes. I was trying to call ready so the plugin could do its work and then listen after all the routes were there but there are other ways to handle this. As for the ready issue, would it be possible to update the documentation so its more clear once ready is called nothing else can be added?

  3. delvedor commented on Jul 27, 2019

    @delvedor
    Member

    As for the ready issue, would it be possible to update the documentation so its more clear once ready is called nothing else can be added?

    Sure! Would you like to send a pr?

    I was trying to have a plugin that would dynamically load the routes.

    Not sure I'm following you, the plugin system should already take care of loading in order and assuring you that everything will be loaded before start listening.

  4. areese159 commented on Jul 27, 2019

    @areese159
    Author

    Yeah, I would be more than happy to submit a PR, ill try and get to that this weekend. As to the other point, I'm trying to make a decorator library. I know one already exists but I don't like that it has to know your folder structure or the fact that it using fs to load all your files. So I was trying to pre-load the plugin before you create your "controllers" so it can decorator them appropriately.

  5. stale commented on Aug 11, 2019

    @stale

    This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

  6. added
    staleIssue or pr with more than 15 days of inactivity.
    on Aug 11, 2019
  7. neuronetio commented on Jan 28, 2020

    @neuronetio

    I have the same problem, I need to load additional routes dynamically from import(...) without restarting the app.

  8. neuronetio commented on Jan 28, 2020

    @neuronetio

    for example when you want to build CMS you need to add routes to articles dynamically without restarting whole app

  9. mcollina commented on Jan 28, 2020

    @mcollina
    Member

    Can you please articulate more your use case? I’ve literally never had to load routes dynamically. Why do you to load code dynamically?

  10. neuronetio commented on Jan 28, 2020

    @neuronetio

    @mcollina like I sad earlier - good example could be when you create new article in CMS your route is just created dynamically by the user - you don't want to restart an app after that because there may be logged other users as well.
    Other example can be if you need to install modules dynamically into your system (downloaded zip) like in prestashop or wordpress) - if you have more than one user - you should not turn off whole app to load routes again.
    You should strongly emphasize in documentation that fastify cannot add routes after it starts to listen because people may invest a lot of time (and money) in their projects and then they may hit a wall.

  11. neuronetio commented on Jan 28, 2020

    @neuronetio

    CMS, CRM, ERP and similar software very often have dynamic categories/subcategories with dynamic content created by the user. routes are created and removed dynamically.

  12. Eomm commented on Jan 28, 2020

    @Eomm
    Member

    why not register parametric URL?

    /product/:productId
    /plugin/:pluginName

  13. neuronetio commented on Jan 28, 2020

    @neuronetio

    @Eomm but when you don't know how many parameters will be?

  14. neuronetio commented on Jan 28, 2020

    @neuronetio

    plugin can contain custom pages with custom routes like plugin for contact page for example
    oh come on...

  15. Eomm commented on Jan 28, 2020

    @Eomm
    Member

    oh come on...

    I can't know what you want to archive if you don't explain it in one comprehensive message.
    Only you know your use cases.

    There are many options, but you need to manage the routing by your self, like https://github.com/fastify/fastify-websocket did

  16. neuronetio commented on Jan 28, 2020

    @neuronetio

    you don't know how many pages plugin may register and you don't know how many tree like structures your user will create (categories - subcategories etc)

  17. delvedor commented on Jan 28, 2020

    @delvedor
    Member

    plugin can contain custom pages with custom routes like plugin for contact page for example
    oh come on...

    Please, avoid this kind of comments, they are not useful and definitely do not encourage us to spend time helping you.

    Regarding your issue, I agree we can do a better job of explaining that you can't add routes dynamically to Fastify, and there is no plan for allowing that in the future. We do so because the addition or the removal of routes can be a source of memory leaks and other performance issues in production. Writing code that we know will be problematic at load is outside our scope.
    Anyhow, how would you add them in the future (between boots)? I suppose you store them in a database (and I would do the same for the user details), so restarting the server should not be an issue, given that Fastify (and Node.js) has a very fast boot.
    Otherwise, you can build a solution by using wildcard routes, as suggested by @Eomm.

  18. ErisDS commented on Aug 31, 2020

    @ErisDS

    I completely understand why you'd not want to allow routes to be added after ready, but I would have the same use case and maybe I can explain it better purely for interest. I'm not asking for anything, just stumbled across this thread whilst reading about fastify.

    In a CMS style environment (decentralised, multi-install application), it's common for there to be settings that are changed whilst the server is running, and to not want to have to reboot to apply them. In this environment there are quite a few different use cases for dynamically loading routes.

    Assuming a blog-style CMS, a user publishes a new article and they expect it to appear on a new route that wasn't known about when the server booted. This can usually be achieved in most routing frameworks with parameters or wildcards, e.g. /posts/:slug - and that would be the same with fastify.

    Where it gets more interesting, is when the parameterised route itself is configurable by the user during runtime, so they have can some posts on /posts/:slug and some on /recipes/:slug, or even have them top level e.g. /:slug. To achieve this, you need wildcards and essentially to re-implement routing 😬

    Another example from cms/blog land is making /amp/ (which is appended to every route) or /rss/ (which is normally a one-off) optional. I think the recommended pattern for these would be registering the routes, and then having the route itself return 404 if the feature is disabled - but that assumes all possible routes are known & doesn't allow for plugins 🤔

    Finally, the use case for the fully customisable one-off route, an example of which was mentioned, is having an optional contact page on a customisable route. So the user can choose to have it on /contact/ or /about/ or perhaps a non-english word. This is one example, there are lots of variations on this theme.

    Although express doesn't have an API for adding/removing routes, express.Router() being a separate thing and allowing nesting with router.use(new Router()) means there are patterns available for constructing & reconstructing a router after the server is started (annoyingly can't find the issue where a semi-official example is given now). I fully realise that express is a lot slower and this is also considered a bottleneck, and so the answer is likely - go use express 😄

    Again, reiterating I'm not asking for anything here, just thought I'd share. Maybe someone has ideas for how these use cases can be achieved in fastify, which I'd be super interested to hear about.

  19. neuronetio commented on Aug 31, 2020

    @neuronetio

    @ErisDS I've created polka fork with this functionality (dynamic routes) https://github.com/neuronetio/polonez

  20. delvedor commented on Aug 31, 2020

    @delvedor
    Member

    @ErisDS thank you for the detailed answer! :)

    I don't foresee this behavior to change in the future, as the reason why it's happening are deeply written into Fastify (mainly optimization and caching).

    A possible solution could be to have two fastify instances, the first one forwards requests to the second one, which is rebooted every time is needed (or in a A/B deployment fashion), guaranteeing zero downtime.

    Another solution could be using directly find-my-way, our router, and write a plugin that can handle dynamic routing.

    I would explore the second solution, as we have designed Fastify to offer a solid base and enough flexibility for using it as foundation for everything you would like to build :)

  21. jsumners commented on Aug 31, 2020

    @jsumners
    Member

    @ErisDS you could register a fallback /* route and essentially implement your own request time router. The short answer is, though, that Fastify wasn't designed for this sort of scenario. It's an REST-ful API framework first.

  22. ErisDS commented on Aug 31, 2020

    @ErisDS

    I ended up here from playing with fastq (now using in production) and avvio (still figuring this bit out) - so using components rather than the whole framework is definitely a workable approach for me.

    Intention behind my message is purely for interest - I def have a use case for the REST-ful API framework part fastify, but I equally have the dynamic use case - so thought I'd share.

    One addition of thought to the dynamic usecase, is that I think there is very little, if any usecase for adding or removing a single route because cascades mean this is unlikely to ever be robust. I think the real usecase is for the ability to say "hey the routes are changing, reload the whole router (or a sub-router)".

    The only usecase I can imagine for this in API-land is if you want dynamic content types that generate their own endpoint. So if I define my CMS is going to serve "podcasts", which is likely some base type + some custom fields, then that would generate CRUD routes for /api/podcasts 🤔

  23. mcollina commented on Aug 31, 2020

    @mcollina
    Member

    Registering (and deregistering) routes is something that would likely lead to leaked memory somewhere at worst and increase jn memory usage at best. I'm definitely up for adding an option that enables this for advanced users that knows what they are doing. As long as it's disabled by default and comes with adeguate warnings, I'm ok.

  24. Eomm commented on Nov 13, 2022

    @Eomm
    Member

    To share my issue: if you have an application that adds dynamic routes after the ready phase, you need to think of a strategy to manage multiple server instances (+ load balancer).

  25. lord007tn commented on Jun 26, 2023

    @lord007tn

    Registering (and deregistering) routes is something that would likely lead to leaked memory somewhere at worst and increase jn memory usage at best. I'm definitely up for adding an option that enables this for advanced users that knows what they are doing. As long as it's disabled by default and comes with adeguate warnings, I'm ok.

    will this be available in fastify v5 ?

  26. locked and limited conversation to collaborators on Jun 26, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    questionGeneral question about the projectstaleIssue or pr with more than 15 days of inactivity.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions