Skip to content

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.

GlandFactory.create(AppModule) runs this pipeline. Each step logs when GLAND_DEBUG is set.

1. Scanning module dependencies walk @Module({ imports }) recursively
2. Initializing dependency injector instantiate controllers + channels, resolve ctor params
3. Running module initialization onModuleInit() (modules, controllers, channels)
4. Binding application components channels subscribe, then routes are broadcast
5. Running channel init hooks onChannelInit()
6. Bootstrapping application onAppBootstrap()
7. Application initialized
┌───────────────────────────────────────────┐
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)

Gland uses the event system for two very different things, and they are worth telling apart.

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.

Yours. Declared in a typed map, addressed by namespace:event, and the only kind a controller or channel ever references.

src/shared/events.interface.ts
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.

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
}
}

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:

  1. new ExpressBroker(options) — the adapter builds its own broker and application object.
  2. The adapter’s broker is connected to the core broker, so gland:define:route events cross between them.
  3. adapter.initialize() runs, subscribing to route definitions and returning the application object you now hold.

Set GLAND_DEBUG to see each bootstrap step as it happens:

Terminal
GLAND_DEBUG=1 node dist/main.js

The 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...