Architecture
What happens between GlandFactory.create() and a listening server — the full bootstrap sequence, and the event names behind it.
Understanding the bootstrap is the fastest way to predict Gland’s behaviour, because almost every surprise at startup comes from one of these seven steps running in a different order than you expected.
The bootstrap sequence
Section titled “The bootstrap sequence”GlandFactory.create(AppModule) runs this pipeline. Each step logs when
GLAND_DEBUG is set.
1. Scanning module dependencies walk @Module({ imports }) recursively2. Initializing dependency injector instantiate controllers + channels, resolve ctor params3. Running module initialization onModuleInit() (modules, controllers, channels)4. Binding application components channels subscribe, then routes are broadcast5. Running channel init hooks onChannelInit()6. Bootstrapping application onAppBootstrap()7. Application initializedLayer by layer
Section titled “Layer by layer” ┌───────────────────────────────────────────┐ transport │ Broker ExpressBroker, … │ │ builds a Context per interaction │ └───────────────────┬───────────────────────┘ │ GlandRoute ┌───────────────────▼───────────────────────┐ routing │ Controller @Get, @Post, @Channel … │ │ emits / calls an event, then replies │ └───────────────────┬───────────────────────┘ │ 'product:list' ┌───────────────────▼───────────────────────┐ domain │ Channel @Channel / @On handlers │ │ performs the side effect, returns a value│ └───────────────────────────────────────────┘
underneath everything: EventBroker (namespaces, channels, mesh, watchers) underneath that: EventEmitter (tree routing, wildcards, LRU cache)The two event flows
Section titled “The two event flows”Gland uses the event system for two very different things, and they are worth telling apart.
Framework events
Section titled “Framework events”Internal, prefixed with gland:, used by the runtime to talk to adapters. You only
see them if you write a custom broker.
| Event | Payload | Emitted when |
|---|---|---|
gland:define:route |
GlandRoute |
A controller route is discovered and bound. |
gland:define:channel:<ns>:<event> |
the payload | A channel handler is bound. |
Application events
Section titled “Application events”Yours. Declared in a typed map, addressed by namespace:event, and the only kind a
controller or channel ever references.
import { IOEvent } from '@glandjs/events'
export interface EventTypes { // Fire-and-forget: the return type is void. 'product:viewed': { id: string }
// Request/response: `ctx.call()` gets a typed `Product` back. 'product:list': IOEvent<Record<string, never>, Promise<Product[]>> 'product:create': IOEvent<NewProduct, Promise<Product>>}The IOEvent<Payload, Return> wrapper is what makes a channel handler’s return value
flow back into the controller with a type, instead of any.
How an event name is resolved
Section titled “How an event name is resolved”Channel names are global. At bind time the framework builds a frozen registry that maps every public name to its internal event name:
@Channel('product') + @On('list') -> 'product:list' -> 'gland:define:channel:product:list'Each request’s context carries a reference to that frozen registry, so
ctx.call('product:list', …) is a single object lookup — not a scan over discovered
channels. Calling an unregistered name throws an UnknownEventError that lists the
names you can use.
import { UnknownEventError } from '@glandjs/core'
try { ctx.call('product:lst', {})} catch (error) { if (error instanceof UnknownEventError) { console.error(error.event) // 'product:lst' console.error(error.available) // every valid channel event }}Broker attachment
Section titled “Broker attachment”connectTo() is the seam between the transport-agnostic core and a concrete protocol.
const app = await GlandFactory.create(AppModule)const server = app.connectTo(ExpressBroker)Three things happen, in order:
new ExpressBroker(options)— the adapter builds its own broker and application object.- The adapter’s broker is connected to the core broker, so
gland:define:routeevents cross between them. adapter.initialize()runs, subscribing to route definitions and returning the application object you now hold.
Reading the debug output
Section titled “Reading the debug output”Set GLAND_DEBUG to see each bootstrap step as it happens:
GLAND_DEBUG=1 node dist/main.jsThe HTTP layer adds its own context tags, so you can also see
HTTP:Adapter, HTTP:Server and bound routes and channels:
[GLAND] Scanning module dependencies[GLAND] Initializing dependency injector[GLAND] Running module initialization hooks[GLAND] Binding application components[GLAND] Channel bound: "product:list" -> ProductChannel#list[GLAND] Route bound: [GET] /products -> ProductController#list[GLAND] Running channel initialization hooks[GLAND] Bootstrapping application[GLAND] Application initialized successfully[HTTP:Adapter] Initializing Express adapter[HTTP:Server] Initializing HTTP server...