Backend Startup Guide
Why backend startup matters
Vona treats startup as a first-class backend runtime capability rather than as ad hoc boot code scattered across modules.
That matters because distributed backend systems need a predictable way to run initialization logic:
- once for the whole application
- once for each initialized instance
- before or after the main runtime becomes ready
- with explicit ordering and environment scoping
Startup is part of a larger lifecycle
In the current Vona runtime, backend startup sits inside a broader application lifecycle.
A practical sequence is:
- app config is loaded and merged
- modules are loaded
appStarthooks runappReadyhooks runappStartedhooks run- shutdown hooks such as
appCloseandappClosedare available for teardown-sensitive logic
This is important because startup beans are not the only lifecycle surface in the backend.
Two startup types
Vona supports two startup types:
- app startup
- instance startup
A useful mental model is:
- app startup runs for backend-wide runtime initialization
- instance startup runs for per-instance initialization in multi-instance or multi-tenant scenarios
This is one of the reasons backend startup belongs in the backend docs, not in the frontend app/system startup guides.
Create a startup bean
Example: create a startup named log in module demo-student.
npm run vona :create:bean startup log -- --module=demo-studentRepresentative shape:
@Startup()
export class StartupLog extends BeanBase implements IStartupExecute {
async execute() {
console.log('Current time: ', Date.now());
}
}The execute() method contains the initialization logic for that startup bean.
Startup options
Representative pattern:
@Startup({
instance: false,
after: false,
debounce: true,
transaction: false,
})
export class StartupLog {}The most important options are:
instanceafterdebouncetransaction
A practical interpretation is:
instance: falsemeans app-startup behaviorinstance: truemeans instance-startup behaviorafter: falseruns before the relevant ready phaseafter: trueruns after the relevant ready phasedebounceprevents repeated startup execution when the runtime churnstransactionwraps the startup logic in a database transaction when needed
Configure startups in app config
Startup options can also be overridden through app config.
In the backend essentials model, startup is one bean scene, so startup configuration also follows the broader bean/onion naming and override conventions used across backend infrastructure.
Representative pattern:
config.onions = {
startup: {
'demo-student:log': {
after: false,
debounce: true,
instance: false,
transaction: false,
},
},
};That keeps startup policy configurable at deployment or project level rather than frozen only in decorator defaults.
Control startup order
Startup ordering matters because backend initialization often depends on other runtime capabilities.
Two important ordering tools are:
dependenciesdependents
Representative patterns:
@Startup({
dependencies: 'a-web:listen',
})
class StartupLog {}@Startup({
dependents: 'a-web:listen',
})
class StartupLog {}Use dependencies when the current startup must run after another startup. Use dependents when the current startup must run before another startup.
Enable or scope a startup
App config can disable a startup explicitly:
config.onions = {
startup: {
'demo-student:log': {
enable: false,
},
},
};Startup decorators can also scope behavior by runtime metadata:
@Startup({
meta: {
flavor: 'normal',
mode: 'dev',
},
})
class StartupLog {}This matters because backend initialization often differs by environment, flavor, CI workflow, Docker workflow, or tenant-sensitive deployment mode.
For the underlying runtime dimensions, also see Runtime and Flavors.
Main hooks, monkey hooks, and startup beans are different surfaces
One of the most important lifecycle distinctions in the current Vona backend is that there are several different hook surfaces.
Module main hooks
A module main can participate in module-level lifecycle hooks such as:
moduleLoadingconfigLoadedmoduleLoaded
These are the right tools when a module needs to customize its own loading or config-processing behavior.
Monkey hooks
Monkey hooks can participate in broader system lifecycle stages such as:
appStartappReadyappStartedappCloseappClosed
Module monkey files can also receive module-oriented hook callbacks, and the app-level monkey file can coordinate app-wide lifecycle behavior.
Representative CLI workflow:
npm run vona :init:monkey demo-studentStartup beans
Startup beans are the preferred lifecycle surface when the real goal is to run backend initialization work as part of the startup onion with ordering, metadata gating, transaction, and debounce support.
A practical rule is:
- use main hooks for module bootstrap customization
- use monkey hooks for deeper lifecycle interception
- use startup beans for normal backend initialization logic that should participate in startup ordering and runtime policy
Hook surface matrix
| Surface | Best for | Ordering / policy support | Teardown fit |
|---|---|---|---|
| Startup bean | normal backend initialization work | strong support through dependencies, dependents, meta, debounce, and transaction | not the main teardown surface |
| Module main hook | module bootstrap customization | tied to module load phases | usually not the main teardown surface |
| Monkey hook | deeper lifecycle interception across app phases | follows lifecycle stage rather than startup-onion ordering | strongest fit for appClose / appClosed cleanup |
This matrix is useful because the same backend task can look like “startup code” while actually belonging to different lifecycle surfaces.
App startup and instance startup in practice
In the current runtime behavior:
- non-instance startups run during app startup / app ready phases according to
after - instance startups run when the app is ready for instances
- in
testanddev, the default instance is started eagerly - in production-style flows, configured static instances are started from
config.instance.instances
A practical timeline is:
- run non-instance startups with
after !== trueduring app start - run non-instance startups with
after === trueduring app ready - trigger instance startup for the relevant instance
- run instance startups with
after !== true - mark
appReadyInstances[instanceName] = true - run instance startups with
after === true
This is why startup should be read together with the instance and datasource story rather than as a purely global boot topic.
Teardown-oriented monkey examples
Lifecycle hooks also matter when the backend is shutting down.
Representative current-runtime examples include:
- queue uses
appCloseto clear workers andappClosedto clear queues - broadcast uses
appCloseto dispose the subscriber side andappClosedto dispose the publisher side - SSR uses
appCloseto remove recorded SSR bean instances from the container
These are good examples of when shutdown-sensitive logic belongs to monkey hooks rather than startup beans.
Inspect the effective startup list
You can inspect the currently effective startup list:
this.bean.onion.startup.inspect();This is useful when you need to debug which startup beans are active after config overrides, metadata filters, and ordering rules are applied.
Built-in startup roles
Some built-in startup behaviors are especially important to understand.
App-startup examples
Representative built-in app startup roles include:
a-version:databaseInita-version:databaseNamea-web:listen
These show that startup is not only for custom business hooks. It is also part of the framework’s core runtime bootstrap.
Instance-startup examples
Representative built-in instance startup roles include:
a-version:instanceInita-printtip:printTipa-queue:loadQueueWorkersa-schedule:loadSchedules
This is the key architectural point: startup is the lifecycle layer that activates other distributed capabilities such as queues and schedules.
Relationship to config, instance, and datasource behavior
Startup should be read together with:
- Config Guide
- Runtime and Flavors
- Model Guide
- Multi-Database and Datasource Guide
- Election Guide
- Queue Guide
- Schedule Guide
- Worker Guide
- Broadcast Guide
A practical split is:
- startup decides when backend capabilities are initialized
- config decides which startup behavior is enabled or overridden
- instance config decides which instance-specific startup flows exist
- datasource behavior decides which database context those startup flows run against
For a practical distributed-runtime reading path, move from Runtime and Flavors and Config Guide into this page, then continue to Worker Guide, Election Guide, Queue Guide, and Schedule Guide.
Implementation checks for startup-sensitive changes
When changing backend startup-sensitive code, ask:
- is this initialization logic app-wide or instance-specific?
- should this be a startup bean, a module main hook, or a monkey hook?
- does the logic need to run before or after the ready phase?
- should ordering be expressed through
dependenciesordependents? - should the startup be gated by
mode,flavor, instance config, or app-config overrides?
That helps AI keep backend initialization aligned with Vona’s current runtime lifecycle rather than scattering boot logic across unrelated files.