Page Contents

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().

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.

  1. 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 */,
    ),
  ];
}
  1. 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.