Overview

LoopBack 4 is designed to be highly extensible. For architectural rationale andmotivation, see Crafting LoopBack 4.

Building blocks for extensibility

The@loopback/contextmodule implements anInversion of Control (IoC)container called Context as a service registry that supportsDependency injection.

The IoC container decouples service providers and consumers. A service providercan be bound to the context with a key, which can be treated as an address ofthe service provider.

The diagram below shows how the Context manages services and their dependencies.

loopback-ioc

In the example above, there are three services in the Context and each of themare bound to a unique key.

  • controllers.UserController: A controller to implement user management APIs
  • repositories.UserRepository: A repository to provide persistence for userrecords
  • utilities.PasswordHasher: A utility function to hash passwordsPlease also note that UserController depends on an instance ofUserRepository and PasswordHasher. Such dependencies are also managed by theContext to provide composition capability for service instances.

Service consumers can then either locate the provider using the binding key ordeclare a dependency using @inject('binding-key-of-a-service-provider') sothat the service provider can be injected into the consumer class. The codesnippet below shows the usage of @inject for dependency injection.

  1. import {inject, Context} from '@loopback/context';
  2. /**
  3. * A UserController implementation that depends on UserRepository and PasswordHasher
  4. */
  5. class UserController {
  6. // UserRepository and PasswordHasher are injected via the constructor
  7. constructor(
  8. @inject('repositories.UserRepository') private userRepository: UserRepository,
  9. @inject('utilities.PasswordHasher') private passwordHasher: PasswordHasher),
  10. ) {}
  11. /**
  12. * Login a user with name and password
  13. */
  14. async login(userName: string, password: String): boolean {
  15. const hash = this.passwordHasher.hash(password);
  16. const user = await this.userRepository.findById(userName);
  17. return user && user.passwordHash === hash;
  18. }
  19. }
  20. const ctx = new Context();
  21. // Bind repositories.UserRepository to UserRepository class
  22. ctx.bind('repositories.UserRepository').toClass(MySQLUserRepository);
  23. // Bind utilities.PasswordHash to a function
  24. ctx.bind('utilities.PasswordHash').to(PasswordHasher)
  25. // Bind the UserController class as the user management implementation
  26. ctx.bind('controllers.UserController').toClass(UserController);
  27. // Locate the an instance of UserController from the context
  28. const userController: UserController = await ctx.get<UserController>('controller.UserController');
  29. // Run the login()
  30. const ok = await userController.login('John', 'MyPassWord');

Now you might wonder why the IoC container is fundamental to extensibility.Here’s how it’s achieved.

  • An alternative implementation of the service provider can be bound thecontext to replace the existing one. For example, we can implement differenthashing functions for password encryption. The user management system canthen receive custom password hashing functions.

  • Services can be organized as extension points and extensions. For example,to allow multiple authentication strategies, the authentication componentcan define an extension point as authentication-manager and variousauthentication strategies such as user/password, LDAP, oAuth2 can becontributed to the extension point as extensions. The relation will looklike:

loopback-extension

To allow a list of extensions to be contributed to LoopBack framework andapplications, we introduce Component as the packaging model to bundleextensions. A component is either a npm module or a local folder structure thatcontains one or more extensions. It’s then exported as a class implementing theComponent interface. For example:

  1. ...
  2. import {Component, ProviderMap} from '@loopback/core';
  3. export class UserManagementComponent implements Component {
  4. providers?: ProviderMap;
  5. constructor() {
  6. this.controllers = [UserController];
  7. this.repositories = [UserRepository];
  8. };
  9. }
  10. }

The interaction between the application context and UserManagement componentis illustrated below:

loopback-component

For more information about components, see:

Types of extensions

  • Binding providers
  • Decorators
  • Sequence Actions
  • Connectors
  • Utility functions
  • Controllers
  • Repositories
  • Models
  • MixinsFor a list of candidate extensions, seeloopback-next issue #512.

System vs Application extensions

Some extensions are meant to extend the programming model and integrationcapability of the LoopBack 4 framework. Good examples of such extensions are:

  • Binding providers
  • Decorators
  • Sequence & Actions
  • Connectors
  • Utility functions
  • Mixins (for application)An application may consist of multiple components for the business logic. Forexample, an online shopping application typically has the following component:

  • UserManagement

  • ShoppingCart
  • AddressBook
  • OrderManagementAn application-level component usually contributes:

  • Controllers

  • Repositories
  • Models
  • Mixins (for models)

How to build my own extensions

Learn from existing ones

Create your own extension

You can scaffold a LoopBack 4 extension project using @loopback/cli’slb4 extension command.