nitro icon indicating copy to clipboard operation
nitro copied to clipboard

Bootstrapping / startup logic

Open slavafomin opened this issue 2 weeks ago โ€ข 4 comments

Hello! Thank you for this great project.

However, I'm looking for guidance on the recommended way to implement startup behavior in a Nitro application, such as performing asynchronous initialization tasks (e.g., connecting to a database, running migrations, warming caches, or initializing external services) before the server begins accepting requests.

Currently, the natural approach seems to be using Nitro plugins (defineNitroPlugin), as they run during server initialization. However, their behavior leads to several problematic issues:

  1. Errors in plugin code do not cause the server process to exit
    If an error is thrown synchronously or rejected in an async plugin (even with await), the server continues to start and listen for requests. This is counter-intuitive and dangerous in production, as it can result in a running server that is in a partially initialized or broken state (e.g., missing database connection), leading to runtime errors on the first request.

  2. No waiting for async plugin completion
    Even if a plugin is async (e.g., defineNitroPlugin(async nitro => { await someLongInit(); })), the server does not await its completion. This creates a race condition: the server starts listening and can accept requests before the initialization is finished.

These behaviors make it risky to perform critical startup logic in plugins.

Expected Behavior

  • Critical initialization errors should fail fast by exiting the process (similar to how uncaught exceptions behave in Node.js).
  • The server should only start listening after all plugins (or at least async startup tasks) have successfully completed.

Possible Solutions / Feature Request

If plugins are intended for this use case:

  • Make plugin execution fully awaited before the server listens.
  • Propagate unhandled errors/rejections from plugins to exit the process (with a non-zero code in production).

Alternatively, provide a dedicated runtime hook for startup initialization, such as:

  • nitro.hooks.hook('bootstrap', async () => { ... }) hook that is awaited after plugin registration but before listening.

Thanks!

slavafomin avatar Dec 15 '25 17:12 slavafomin

๐Ÿ”— Similar Issues

Related Issues

  • https://github.com/nitrojs/nitro/issues/3456
  • https://github.com/nitrojs/nitro/issues/3579
  • https://github.com/nitrojs/nitro/issues/3378
  • https://github.com/nitrojs/nitro/issues/3833

๐Ÿ”— Related PRs

nitrojs/nitro#3857 - docs: update defineNitroPlugin references [merged]

๐Ÿ‘ค Suggested Assignees


Enable issue planning

To enable issue planning, add the following to your .coderabbit.yaml:

issue_enrichment:
  planning:
    enabled: true

You can then request a plan by commenting @coderabbitai plan on any issue.

๐Ÿงช Issue enrichment is currently in early access.

To disable automatic issue enrichment, add the following to your .coderabbit.yaml:

issue_enrichment:
  auto_enrich:
    enabled: false

coderabbitai[bot] avatar Dec 15 '25 17:12 coderabbitai[bot]

You can use async logic in top level server.ts if effect required globally otherwise i suggest to move it top level await in utils that need it. since it can reduce startup time of server.

pi0 avatar Dec 15 '25 18:12 pi0

You can use async logic in top level server.ts โ€ฆ

Thank you for your reply. But can you please explain the "top level server.ts"? I don't see anything like that in the docs and I don't have this file in my project.

Right now I've decided to use the following middleware to prevent racing conditions:

export default defineEventHandler(event => {

  if (!isBootstrapped()) {

    setResponseHeader(event, 'Retry-After', BOOTSTRAP_SECS);

    throw createError({
      statusCode: 503,
      statusMessage: 'Service Unavailable',
      message: (
        `Service is not ready yet, ` +
        `please try again after ${BOOTSTRAP_SECS} seconds`
      ),
    });

  }

});

slavafomin avatar Dec 15 '25 18:12 slavafomin

It is a nitro v3 feature. You can create a server.ts file in root of your project:

await bootstrap()

export default {}

(In v2, what you are doing is ok)

pi0 avatar Dec 15 '25 19:12 pi0