What does Booting an Application mean?
A typical LoopBack application is made up of many artifacts in different files, organized in different folders. Booting an Application means:
- Discovering artifacts automatically based on a convention (a specific folder containing files with a given suffix)
- Processing those artifacts (this usually means automatically binding them to the Application’s Context)
@loopback/boot
provides a Bootstrapper that uses Booters to automatically
discover and bind artifacts, all packaged in an easy-to-use Mixin.
What is an artifact?
An artifact is any LoopBack construct usually defined in code as a Class. LoopBack constructs include Controllers, Repositories, Models, etc.
Usage
@loopback/cli
New projects generated using @loopback/cli
or lb4
are automatically enabled
to use @loopback/boot
for booting the Application using the conventions
followed by the CLI.
Adding to existing project
See Using the BootMixin to add Boot to your Project manually.
The rest of this page describes the inner workings of @loopback/boot
for
advanced use cases, manual usage or using @loopback/boot
as a standalone
package (with custom booters).
BootMixin
Boot functionality can be added to a LoopBack 4 Application by mixing it with
the BootMixin
mixin.
This mixin adds the BootComponent
to your Application as well as convenience
methods such as app.boot()
and app.booters()
. The Mixin also allows
Components to set the property booters
as an Array of Booters
. They will be
bound to the Application and called by the Bootstrapper
.
Since this is a convention-based Bootstrapper, it is important to set a
projectRoot
, as all other artifact paths will be resolved relative to this
path.
Tip: application.ts
will likely be at the root of your project, so its path
can be used to set the projectRoot
by using the __dirname
variable. (See
example below)
Using the BootMixin
import {BootMixin} from "@loopback/boot";
class MyApplication extends BootMixin(Application) {
constructor(options?: ApplicationConfig) {
super(options);
// Setting the projectRoot
this.projectRoot = __dirname;
// Set project conventions
this.bootOptions: BootOptions = {
controllers: {
dirs: ['controllers'],
extensions: ['.controller.js'],
nested: true,
}
}
}
}
Now just call app.boot()
from index.ts
before starting your Application
using app.start()
.
app.boot()
A convenience method to retrieve the Bootstrapper
instance bound to the
Application and calls its boot
function. This should be called before an
Application’s start()
method is called. This is an async
function and
should be called with await
.
class MyApp extends BootMixin(Application) {}
async main() {
const app = new MyApp();
app.projectRoot = __dirname;
await app.boot();
await app.start();
}
app.booters()
A convenience method to manually bind Booters
. You can pass any number of
Booter
classes to this method and they will all be bound to the Application
using the prefix (booters.
) and tag (booter
) used by the Bootstrapper
.
// Binds MyCustomBooter to `booters.MyCustomBooter`
// Binds AnotherCustomBooter to `booters.AnotherCustomBooter`
// Both will have the `booter` tag set.
app.booters(MyCustomBooter, AnotherCustomBooter);
BootComponent
This component is added to an Application by BootMixin
if used. This
Component:
- Provides a list of default
booters
as a property of the component - Binds the conventional Bootstrapper to the Application
If using this as a standalone component without the BootMixin
, you will need
to bind the booters
of a component manually.
app.component(BootComponent);
Bootstrapper
A Class that acts as the “manager” for Booters. The Bootstrapper is designed to
be bound to an Application as a SINGLETON
. The Bootstrapper class provides a
boot()
method. This method is responsible for getting all bound Booters
and
running their phases
. A phase
is a method on a Booter
class.
Each boot()
method call creates a new Context
that sets the app
context as
its parent. This is done so each Context
for boot
gets a new instance of
booters
but the same context can be passed into boot
so selective phases
can be run in different calls of boot
.
The Bootstrapper can be configured to run specific booters or boot phases by
passing in BootExecOptions
. This is experimental and subject to change.
Hence, this functionality is not exposed when calling boot()
via
BootMixin
.
To use BootExecOptions
, you must directly call bootstrapper.boot()
instead
of app.boot()
. You can pass in the BootExecOptions
object with the following
properties:
Property | Type | Description |
---|---|---|
booters |
Constructor<Booter>[] |
Array of Booters to bind before running boot() |
filter.booters |
string[] |
Names of Booter classes that should be run |
filter.phases |
string[] |
Names of Booter phases to run |
Example
import {BootMixin, Booter, Binding, Bootstrapper} from '@loopback/boot';
class MyApp extends BootMixin(Application) {}
const app = new MyApp();
app.projectRoot = __dirname;
const bootstrapper: Bootstrapper = await this.get(
BootBindings.BOOTSTRAPPER_KEY,
);
bootstrapper.boot({
booters: [MyCustomBooter],
filter: {
booters: ['MyCustomBooter'],
phases: ['configure', 'discover'], // Skip the `load` phase.
},
});
Booters
A Booter is a class that is responsible for booting an artifact. A Booter does
its work in phases
which are called by the Bootstrapper. The following Booters
are a part of the @loopback/boot
package and loaded automatically via
BootMixin
.
Controller Booter
This Booter’s purpose is to discover Controller type Artifacts and to bind them to the Application’s Context.
You can configure the conventions used in your project for a Controller by
passing a controllers
object on BootOptions
property of your Application.
The controllers
object supports the following options:
Options | Type | Default | Description |
---|---|---|---|
dirs |
string \| string[] |
['controllers'] |
Paths relative to projectRoot to look in for Controller artifacts |
extensions |
string \| string[] |
['.controller.js'] |
File extensions to match for Controller artifacts |
nested |
boolean |
true |
Look in nested directories in dirs for Controller artifacts |
glob |
string |
A glob pattern string. This takes precendence over above 3 options (which are used to make a glob pattern). |
Model Booter
This Booter’s purpose is to discover Model type Artifacts and to
bind them to the Application’s Context. The use of this Booter requires
RepositoryMixin
from @loopback/repository
to be mixed into your Application
class.
You can configure the conventions used in your project for a Repository by
passing a models
object on BootOptions
property of your Application. The
models
object supports the following options:
Options | Type | Default | Description |
---|---|---|---|
dirs |
string \| string[] |
['models'] |
Paths relative to projectRoot to look in for Model artifacts |
extensions |
string \| string[] |
['.model.js'] |
File extensions to match for Model artifacts |
nested |
boolean |
true |
Look in nested directories in dirs for Model artifacts |
glob |
string |
A glob pattern string. This takes precendence over above 3 options (which are used to make a glob pattern). |
Repository Booter
This Booter’s purpose is to discover Repository type Artifacts
and to bind them to the Application’s Context. The use of this Booter requires
RepositoryMixin
from @loopback/repository
to be mixed into your Application
class.
You can configure the conventions used in your project for a Repository by
passing a repositories
object on BootOptions
property of your Application.
The repositories
object supports the following options:
Options | Type | Default | Description |
---|---|---|---|
dirs |
string \| string[] |
['repositories'] |
Paths relative to projectRoot to look in for Repository artifacts |
extensions |
string \| string[] |
['.repository.js'] |
File extensions to match for Repository artifacts |
nested |
boolean |
true |
Look in nested directories in dirs for Repository artifacts |
glob |
string |
A glob pattern string. This takes precendence over above 3 options (which are used to make a glob pattern). |
DataSource Booter
This Booter’s purpose is to discover DataSource type Artifacts
and to bind them to the Application’s Context. The use of this Booter requires
RepositoryMixin
from @loopback/repository
to be mixed into your Application
class.
You can configure the conventions used in your project for a DataSource by
passing a datasources
object on BootOptions
property of your Application.
The datasources
object support the following options:
Options | Type | Default | Description |
---|---|---|---|
dirs |
string \| string[] |
['datasources'] |
Paths relative to projectRoot to look in for DataSource artifacts |
extensions |
string \| string[] |
['.datasource.js'] |
File extensions to match for DataSource artifacts |
nested |
boolean |
true |
Look in nested directories in dirs for DataSource artifacts |
glob |
string |
A glob pattern string. This takes precendence over above 3 options (which are used to make a glob pattern). |
Service Booter
Description
Discovers and binds remote service proxies or local service classes or providers
using app.service()
.
Note:
IMPORTANT: For a class to be recognized by ServiceBooter
as a service
provider, it either has to be decorated with @injectable
/@inject
or the
class name must end with Provider
suffix and must have a static or prototype
value()
method.
The following are some examples for service classes:
import {injectable, BindingScope, inject, Provider} from '@loopback/core';
// With `@injectable`
@injectable({
tags: {serviceType: 'local'},
scope: BindingScope.SINGLETON,
})
export class BindableGreetingService {
greet(whom = 'world') {
return Promise.resolve(`Hello ${whom}`);
}
}
@injectable({tags: {serviceType: 'local', name: 'CurrentDate'}})
export class DateProvider implements Provider<Date> {
value(): Promise<Date> {
return Promise.resolve(new Date());
}
}
// Provider class
export class BindableDateProvider implements Provider<Date> {
value(): Promise<Date> {
return Promise.resolve(new Date());
}
}
// Dynamic factory provider class
export class DynamicDateProvider {
static value() {
return new Date();
}
}
// With `@inject`
export class ServiceWithConstructorInject {
constructor(@inject('currentUser') private user: string) {}
}
export class ServiceWithPropertyInject {
@inject('currentUser') private user: string;
}
export class ServiceWithMethodInject {
greet(@inject('currentUser') user: string) {
return `Hello, ${user}`;
}
}
Options
The options for this can be passed via BootOptions
when calling
app.boot(options: BootOptions)
.
The options for this are passed in a services
object on BootOptions
.
Available options on the services
object on BootOptions
are as follows:
Options | Type | Default | Description |
---|---|---|---|
dirs |
string \| string[] |
['services'] |
Paths relative to projectRoot to look in for Service artifacts |
extensions |
string \| string[] |
['.service.js'] |
File extensions to match for Service artifacts |
nested |
boolean |
true |
Look in nested directories in dirs for Service artifacts |
glob |
string |
A glob pattern string. This takes precedence over above 3 options (which are used to make a glob pattern). |
Interceptor Booter
Description
Discovers and binds global or local interceptor provider classes using
app.interceptor()
.
Options
The options for this can be passed via BootOptions
when calling
app.boot(options: BootOptions)
.
The options for this are passed in a interceptors
object on BootOptions
.
Available options on the interceptors
object on BootOptions
are as follows:
Options | Type | Default | Description |
---|---|---|---|
dirs |
string \| string[] |
['interceptors'] |
Paths relative to projectRoot to look in for Interceptor artifacts |
extensions |
string \| string[] |
['.interceptor.js'] |
File extensions to match for Interceptor artifacts |
nested |
boolean |
true |
Look in nested directories in dirs for Interceptor artifacts |
glob |
string |
A glob pattern string. This takes precedence over above 3 options (which are used to make a glob pattern). |
Custom Booters
A custom Booter can be written as a Class that implements the Booter
interface. The Class must implement methods that corresponds to a phase
name.
The phases
are called by the Bootstrapper in a pre-determined order (unless
overridden by BootExecOptions
). The next phase is only called once the
previous phase has been completed for all Booters.
Phases
configure
Used to configure the Booter
with its default options.
discover
Used to discover the artifacts supported by the Booter
based on convention.
load
Used to bind the discovered artifacts to the Application.
Boot an application using component
For a complex project, we may break it down into multiple LoopBack applications, each of which has controllers, datasources, services, repositories, and other artifacts. How do we compose these sub applications into the main application? The component application booter can be created to support this use case.
- Create a component for the sub-application:
import {createComponentApplicationBooterBinding} from '@loopback/boot';
import {Component} from '@loopback/core';
export class SubAppComponent implements Component {
bindings = [
createComponentApplicationBooterBinding(
new SubApp(), /* an optional binding filter */,
),
];
}
- Mount the sub-application as a component to the main application:
const mainApp = new MainApp();
// This can be done in the constructor of `MainApp` too. Make sure the component
// is registered before calling `app.boot()`.
mainApp.component(SubAppComponent);
// Boot the main application. It will invoke the component application booter
// to add artifacts from the `SubApp`.
await mainApp.boot();
A binding filter function can be provided to select what bindings from the
component application should be added to the main application. The booter skips
bindings that exist in the component application before boot
. It does not
override locked bindings in the main application.