Overview

A Controller is a class that implements operations defined by an application’sAPI. It implements an application’s business logic and acts as a bridge betweenthe HTTP/REST API and domain/database models. Decorations are added to aController class and its members to map the API operations of the applicationto the corresponding controller’s operations. A Controller operates only onprocessed input and abstractions of backend services / databases.

This page will only cover a Controller’s usage with REST APIs.

Operations

In the Operation example in Routes, the greet() operation wasdefined as a plain JavaScript function. The example below shows this as aController method in TypeScript.

  1. // plain function Operation
  2. function greet(name: string) {
  3. return `hello ${name}`;
  4. }
  5. // Controller method Operation
  6. class MyController {
  7. greet(name: string) {
  8. return `hello ${name}`;
  9. }
  10. }

Routing to Controllers

This is a basic API Specification used in the following examples. It is anOperation Object.

  1. const spec = {
  2. parameters: [{name: 'name', schema: {type: 'string'}, in: 'query'}],
  3. responses: {
  4. '200': {
  5. description: 'greeting text',
  6. content: {
  7. 'application/json': {
  8. schema: {type: 'string'},
  9. },
  10. },
  11. },
  12. },
  13. };

There are several ways to define Routes to Controller methods. The firstexample defines a route to the Controller without any magic.

  1. // ... in your application constructor
  2. this.route('get', '/greet', spec, MyController, 'greet');

Decorators allow you to annotate your Controller methods with routing metadata,so LoopBack can call the app.route() function for you.

  1. import {get} from '@loopback/rest';
  2. class MyController {
  3. @get('/greet', spec)
  4. greet(name: string) {
  5. return `hello ${name}`;
  6. }
  7. }
  8. // ... in your application constructor
  9. this.controller(MyController);

Specifying Controller APIs

For larger LoopBack applications, you can organize your routes into APISpecifications using the OpenAPI specification. The @api decorator takes aspec with type ControllerSpec which comprises of a string basePath and aPaths ObjectNote that it is not the fullOpenAPIspecification.

  1. // ... in your application constructor
  2. this.api({
  3. openapi: '3.0.0',
  4. info: {
  5. title: 'Hello World App',
  6. version: '1.0.0',
  7. },
  8. paths: {
  9. '/greet': {
  10. get: {
  11. 'x-operation-name': 'greet',
  12. 'x-controller-name': 'MyController',
  13. parameters: [{name: 'name', schema: {type: 'string'}, in: 'query'}],
  14. responses: {
  15. '200': {
  16. description: 'greeting text',
  17. content: {
  18. 'application/json': {
  19. schema: {type: 'string'},
  20. },
  21. },
  22. },
  23. },
  24. },
  25. },
  26. },
  27. });
  28. this.controller(MyController);

The @api decorator allows you to annotate your Controller with aspecification, so LoopBack can call the app.api() function for you.

  1. @api({
  2. openapi: '3.0.0',
  3. info: {
  4. title: 'Hello World App',
  5. version: '1.0.0',
  6. },
  7. paths: {
  8. '/greet': {
  9. get: {
  10. 'x-operation-name': 'greet',
  11. 'x-controller-name': 'MyController',
  12. parameters: [{name: 'name', schema: {type: 'string'}, in: 'query'}],
  13. responses: {
  14. '200': {
  15. description: 'greeting text',
  16. content: {
  17. 'application/json': {
  18. schema: {type: 'string'},
  19. },
  20. },
  21. },
  22. },
  23. },
  24. },
  25. },
  26. })
  27. class MyController {
  28. greet(name: string) {
  29. return `hello ${name}`;
  30. }
  31. }
  32. app.controller(MyController);

Writing Controller methods

Below is an example Controller that uses several built in helpers (decorators).These helpers give LoopBack hints about the Controller methods.

  1. import {HelloRepository} from '../repositories';
  2. import {HelloMessage} from '../models';
  3. import {get, param} from '@loopback/rest';
  4. import {repository} from '@loopback/repository';
  5. export class HelloController {
  6. constructor(
  7. @repository(HelloRepository) protected repository: HelloRepository,
  8. ) {}
  9. // returns a list of our objects
  10. @get('/messages')
  11. async list(@param.query.number('limit') limit = 10): Promise<HelloMessage[]> {
  12. if (limit > 100) limit = 100; // your logic
  13. return this.repository.find({limit}); // a CRUD method from our repository
  14. }
  15. }
  • HelloRepository extends from Repository, which is LoopBack’s databaseabstraction. See Repositories for more.
  • HelloMessage is the arbitrary object that list returns a list of.
  • @get('/messages') automatically creates thePaths Item Objectfor OpenAPI spec, which also handles request routing.
  • @param.query.number specifies in the spec being generated that the routetakes a parameter via query which will be a number.

Handling Errors in Controllers

In order to specify errors for controller methods to throw, the classHttpErrors is used. HttpErrors is a class that has been re-exported fromhttp-errors, and can be found inthe @loopback/rest package.

Listed below are some of the most common error codes. The full list of supportedcodes is foundhere.

Status CodeError
400BadRequest
401Unauthorized
403Forbidden
404NotFound
500InternalServerError
502BadGateway
503ServiceUnavailable
504GatewayTimeout

The example below shows the previous controller revamped with HttpErrors alongwith a test to verify that the error is thrown properly.

src/tests/integration/controllers/hello.controller.integration.ts

  1. import {HelloController} from '../../../controllers';
  2. import {HelloRepository} from '../../../repositories';
  3. import {testdb} from '../../fixtures/datasources/testdb.datasource';
  4. import {expect} from '@loopback/testlab';
  5. import {HttpErrors} from '@loopback/rest';
  6. const HttpError = HttpErrors.HttpError;
  7. describe('Hello Controller', () => {
  8. it('returns 422 Unprocessable Entity for non-positive limit', () => {
  9. const repo = new HelloRepository(testdb);
  10. const controller = new HelloController(repo);
  11. return expect(controller.list(0.4)).to.be.rejectedWith(HttpError, {
  12. message: 'limit is non-positive',
  13. statusCode: 422,
  14. });
  15. });
  16. });

src/controllers/hello.controller.ts

  1. import {HelloRepository} from '../repositories';
  2. import {HelloMessage} from '../models';
  3. import {get, param, HttpErrors} from '@loopback/rest';
  4. import {repository} from '@loopback/repository';
  5. export class HelloController {
  6. constructor(@repository(HelloRepository) protected repo: HelloRepository) {}
  7. // returns a list of our objects
  8. @get('/messages')
  9. async list(@param.query.number('limit') limit = 10): Promise<HelloMessage[]> {
  10. // throw an error when the parameter is not a non-positive integer
  11. if (!Number.isInteger(limit) || limit < 1) {
  12. throw new HttpErrors.UnprocessableEntity('limit is non-positive');
  13. } else if (limit > 100) {
  14. limit = 100;
  15. }
  16. return this.repo.find({limit});
  17. }
  18. }