@loopback/docs
Version:
Documentation files rendered at [https://loopback.io](https://loopback.io)
298 lines (241 loc) • 10.4 kB
Markdown
---
lang: en
title: 'Migrating boot scripts'
keywords:
LoopBack 4.0, LoopBack 4, Node.js, TypeScript, OpenAPI, LoopBack 3, Migration
sidebar: lb4_sidebar
permalink: /doc/en/lb4/migration-boot-scripts.html
---
{% include tip.html content="
Missing instructions for your LoopBack 3 use case? Please report a [Migration docs issue](https://github.com/strongloop/loopback-next/issues/new?labels=question,Migration,Docs&template=Migration_docs.md) on GitHub to let us know.
" %}
In LoopBack 3, predefined boot scripts are organized in the `/server/boot`
directory, and are executed right before the server starts to perform some
custom application initialization. The same functionality can also be achieved
in LoopBack 4 application by adding observers.
The LoopBack 3 boot script is mapped to LoopBack 4 observer's `start` function.
And moreover, besides participating in an application's start phase, an observer
also allows you to perform logic in the stop phase.
## LoopBack 3 Boot Script
LoopBack 3 supports three styles of boot scripts:
- synchronous boot script
- asynchronous boot script that takes in a callback
- asynchronous boot script that returns a promise
They all have the first parameter as `server` (some people are used to name it
as `app`). The callback based asynchronous script has a 2nd parameter `done` as
the callback function.
A typical synchronous boot script that prints out a datasource's name before
application runs:
{% include code-caption.html content="server/boot/print-info.js" %}
```js
'use strict';
module.exports = function printInfo(server) {
// SUPPOSE your application has a datasource called `db`, which is usually the default
// memory datasource in a sample LoopBack application created by CLI, regardless if it's
// an LB3 app or LB4 app
const db = server.datasources.db;
console.log('This is a synchronous script.');
console.log('Your app has a datasource called: ', db.name);
};
```
A typical asynchronous boot script that takes in a callback:
{% include code-caption.html content="server/boot/create-sample-cb.js" %}
```js
// in file `server/boot/create-sample-cb.js`
'use strict';
module.exports = function createSample(server, done) {
// SUPPOSE your application has a Todo model
const Todo = server.models.Todo;
const sample = {
title: 'a todo sample - cb',
desc: 'created by callback based script',
};
Todo.create(sample, (err, result) => {
if (err) return done(err);
console.log('Sample created as: ', result);
done();
});
};
```
A typical asynchronous boot script that returns a promise:
{% include code-caption.html content="server/boot/create-sample-promise.js" %}
```js
'use strict';
module.exports = function createSample(server) {
// SUPPOSE your application has a Todo model
const Todo = server.models.Note;
const sample = {
title: 'a todo sample - promise',
desc: 'created by promise based script',
};
return Todo.create(sample).then(result => {
console.log('Sample created as: ', result);
});
};
```
Since a LoopBack 4 application is created in TypeScript, which supports the
latest ECMAScript features including
[`async/await`](https://javascript.info/async-await), there is no such signature
difference any more, the `start` function in the observer is always an `async`
function.
## Migrating to LoopBack 4 Observer
To perform the same logic defined in a LoopBack 3 boot script, you should create
a corresponding observer in your LoopBack 4 application.
LoopBack 4 has its own life cycles at runtime and has `boot`, `start`, `stop` as
its transition states. Observer allows you to participate in the `start` state,
which is equivalent to the LoopBack 3 custom boot script.
{% include note.html content="We highly recommend you learn about
[Life cycle events and observers](https://loopback.io/doc/en/lb4/Life-cycle.html)
first before reading the following guide." %}
Taking the two scripts(`server/boot/print-info.js` and
`server/boot/create-sample-promise.js`) above as examples, let's first migrate
`server/boot/print-info.js` to your LoopBack 4 application.
### Creating Observer
Run command `lb4 observer` to create an observer called `printInfo`:
```sh
lb4 observer
? Observer name: printInfo
? Observer group:
create src/observers/print-info.observer.ts
update src/observers/index.ts
Observer PrintInfo was created in src/observers/
```
You will find a file called `PrintInfo.observer.ts` created in folder
`src/observers/`. Open that file, there is a class called `PrintInfoObserver`,
which implements `LifeCycleObserver` and has `start` and `stop` functions. The
`start` function is where you should copy over the content of LB3's `printInfo`
function.
### Injecting Application
The LoopBack 3 boot script takes in `server` as its first parameter, which is
essentially the `application` injected using key
`CoreBindings.APPLICATION_INSTANCE`:
```ts
// ... imports
export class PrintInfoObserver implements LifeCycleObserver {
constructor(
(CoreBindings.APPLICATION_INSTANCE) private app: Application,
) {
// ... other class members
}
}
```
Now within the class `PrintInfoObserver` you have access to the application.
### Migrating Artifacts
A boot script usually accesses or manipulates an application's artifacts like
models, datasources, etc... So when moving its content to the `start` function,
make sure you edit the code to retrieve those artifacts in LoopBack 4's way.
For the print info script, since it retrieves a datasource's info, you need to
**MIGRATE** the LoopBack datasource `db` to your LoopBack 4 application. The
steps are well documented on page [migration/datasources.md](./datasources.md).
And in the `start` function, **EDIT CODE** to inject the datasource using
`('datasources.db') private ds: juggler.DataSource`:
_The code snippet is the complete observer file, the LB3 boot script logic is
only in the `start` function_
```ts
import {
/* inject, Application, CoreBindings, */
lifeCycleObserver, // The decorator
LifeCycleObserver,
inject,
CoreBindings,
Application, // The interface
} from '/core';
import {juggler} from '@loopback/repository';
/**
* This class will be bound to the application as a `LifeCycleObserver` during
* `boot`
*/
()
export class PrintInfoObserver implements LifeCycleObserver {
constructor(
// inject `app` if you need access to other artifacts by `await this.app.get()`
(CoreBindings.APPLICATION_INSTANCE) private app: Application,
// inject a datasource with key `datasources.${dsName}`
('datasources.db') private ds: juggler.DataSource,
) {}
/**
* This method will be invoked when the application starts
*/
async start(): Promise<void> {
// Add your logic for start
console.log('This is a migrated synchronous boot script.');
console.log(`Your app has a datasource called: ${this.ds.name}`);
}
/**
* This method will be invoked when the application stops
*/
async stop(): Promise<void> {
// Add your logic for stop
console.log('print message observer has stopped.');
}
}
```
For the other script `server/boot/create-sample-promise.js`, the creation of its
corresponding observer is exactly the same as `server/boot/print-info.js`. To
avoid duplicate content, the steps are omitted here.
The script retrieves LoopBack 3 model `Todo` and creates a sample through
`Todo.create()`. While in LoopBack 4, model only describes the shape of data,
and repository processes the persistence-related behavior. Therefore you need to
call `TodoRepository.create(sample)` instead to create the sample. And to
migrate the `Todo` model, you can follow the steps in
[Migrating model definitions and built-in APIs](./models/core.md).
The migrated boot script as `CreateSampleObserver`'s start function:
_The code snippet is the complete observer file, the LB3 boot script logic is
only in the `start` function_
```ts
import {
/* inject, Application, CoreBindings, */
lifeCycleObserver, // The decorator
LifeCycleObserver,
inject,
CoreBindings,
Application, // The interface
} from '/core';
import {TodoRepository} from '../repositories';
/**
* This class will be bound to the application as a `LifeCycleObserver` during
* `boot`
*/
()
export class CreateSampleObserver implements LifeCycleObserver {
constructor(
// inject `app` if you need access to other artifacts by `await this.app.get()`
(CoreBindings.APPLICATION_INSTANCE) private app: Application,
// inject a repository with key `repositories.${repoName}`
// or with the shortcut injector:
// `@repository(TodoRepository) private todoRepo: TodoRepository`
('repositories.TodoRepository') private todoRepo: TodoRepository,
) {}
/**
* This method will be invoked when the application starts
*/
async start(): Promise<void> {
// Add your logic for start
console.log('This is a migrated asynchronous boot script');
const sample = {title: 'a todo sample', desc: 'Something to do.'};
// Create the sample by calling TodoRepo.create()
const result = await todoRepo.create(sample);
console.log('Sample created as ', result);
}
/**
* This method will be invoked when the application stops
*/
async stop(): Promise<void> {
// Add your logic for stop
console.log('create sample observer has stopped.');
}
}
```
### Setting Order for Migrated Boot Scripts
In LoopBack 3, boot scripts are loaded according to the script file's name
(alphabetically). LoopBack 4 observer provides an advanced way to control the
order of scripts by specifying and setting group names. The tutorial is well
documented in the section
[Notify life cycle observers of start/stop related events by order](../Life-cycle.md#notify-life-cycle-observers-of-startstop-related-events-by-order).
## Predefined LoopBack 3 Boot Scripts
LoopBack 3 application has two predefined boot scripts: `/server/boot/root.js`
and `/server/boot/authentication.js`. Please do not create corresponding
observers for them.
In LoopBack 4, the router is automatically registered in the
[rest server](../Server.md) and the authentication system is enabled by
[applying the `authentication` component](../LoopBack-component-authentication.md).