Page Contents

Please get yourself familiar with Contributing REST API endpoints first. It’s important to understand different ways how LoopBack 4 components can contribute REST API endpoints to target applications, before learning about the migration from LoopBack 3.

In our research of existing LoopBack 3 components, we have identified several different use cases:

  1. Add a new REST API endpoint at a configured path, e.g. /visualize returning an HTML page.

  2. REST API endpoints providing file upload & download.

  3. Add a new local service (e.g. Ping.ping()) and expose it via REST API (e.g. /ping), allow the user to customize the base path where the API is exposed at (e.g. /pong).

  4. Add new REST API endpoints using Express (req, res, next) style.

In the following text, we will describe how to migrate these use cases from LoopBack 3 style to LoopBack 4.

General instructions

There are different ways how a LoopBack 3 component can contribute new REST API endpoints:

  • Some extensions are adding remote methods to existing or newly-created models.
  • Other extensions are contributing Express-style routes.

We recommend extension authors to convert their LoopBack 3 routes to LoopBack 4 Controller methods wherever possible.

  1. Create a new Controller class and add it to your component, see Contributing REST API endpoints.

  2. Move your LoopBack 3 REST API endpoints to the new controller class.

  3. Decide if you want these endpoints included in OpenAPI spec. Follow the steps in Undocumented endpoints to hide the new endpoints from the OpenAPI spec describing the target application’s API.

  4. Optionally, if you want to make your REST endpoints configurable by target applications, then follow the steps in Dynamic OpenAPI metadata to wrap you controller method in a class factory function.

Migrating REST API endpoints returning HTML pages

To migrate an endpoint that’s returning files (or any other non-JSON payload), please follow General instructions and then perform two additional steps:

  1. Modify the controller constructor to inject the HTTP response.

    import {RestBindings, Response} from '@loopback/rest';
    
    class MyController {
      constructor(@inject(RestBindings.Http.RESPONSE) response: Response) {}
    }
    
  2. In the LoopBack 4 controller method, use Express API like res.contentType() and res.send() to send back the result. (This is a workaround until loopback-next#436 is implemented).

Migrating file upload & download

  1. Follow General instructions to create LoopBack 4 controllers for your endpoints.

  2. Follow the instructions in Upload and download files to implement file upload & download functionality in the migrated endpoints.

Migrating Express routes

We recommend extension authors to use Controllers as a better alternative to low-level Express route API.

If it’s not feasible to implement REST endpoints as LoopBack Controllers:

  1. Follow the instructions in Express routes to setup an Express router in your LoopBack 4 component and contribute it to the target application.

  2. Take the implementation of Express routes from your LoopBack 3 component and mount these routes on the Express router created in the previous step, as explained in Express routes.

Migrating local services exposed via REST API

Consider the following scenario:

  • A LoopBack 3 component is contributing a new Model (e.g. Ping) providing various local services (e.g. Ping.ping()) and exposing these services via REST API (e.g. at GET /ping).

How to migrate such components:

  1. Follow the steps in Creating services in components to add a new local service to your LoopBack 4 component.

  2. Move the implementation of your LoopBack 3 service model (e.g. Ping) to the newly created LoopBack 4 local service class.

  3. Follow the steps in Contributing REST API endpoints to add a new controller class to your LoopBack 4 component.

  4. Use Dependency Injection to inject an instance of the local service into the controller class.

  5. For each service method you want to expose via REST API, implement a new controller method calling the Service class API under the hood.

An example implementation of the ping service class:

import {injectable, BindingScope} from '@loopback/core';

@injectable({scope: BindingScope.TRANSIENT})
export class PingService {
  ping() {
    return {status: 'ok'};
  }
}

An example implementation of the ping controller factory:

import {injectable, BindingScope, Constructor, inject} from '@loopback/core';
import {get} from '@loopback/rest';
import {PingBindings} from '../ping.keys';
import {PingService} from '../services/ping.service.ts';

export function definePingController(basePath: string): Constructor<unknown> {
  @injectable({scope: BindingScope.SINGLETON})
  class PingController {
    constructor(
      @inject(PingBindings.PING_SERVICE)
      private pingService: PingService,
    ) {}

    @get(`${basePath}/ping`)
    ping() {
      return this.pingService.ping();
    }
  }

  return PingController;
}

An example component using contributing a ping service exposed via REST, using the local service and the controller factory implemented in code snippets above:

import {createServiceBinding} from '@loopback/core';
import {definePingController} from './controllers/ping.controller.ts';
import {PingBindings} from './keys';
import {PingService} from './services/ping.service.ts';
import {PingComponentConfig} from './types';

@injectable({tags: {[ContextTags.KEY]: PingBindings.COMPONENT.key}})
export class PingComponent implements Component {
  constructor(
    @config(),
    config: PingComponentConfig = {},
  ) {
   this.bindings = [
      createServiceBinding(PingService),
   ];

    const basePath = this.config.basePath ?? '';
    this.controllers = [
      definePingController(basePath)
    ];
  }
}