Implement REST APIs (TypeScript)

Controllers

In API Namespaces, there is a file for each operation. Each file will have a method for the actual operation logic and another method for handling the errors that could appear during the processing of the operation.

The auto generated stub for the API operations look like the following:

import { Context as RequestContext } from 'solution-framework';
import { V1BaseControllers, V1Request as Request, V1Response as Response, V1Schema as Schema } from 'solution-framework';

/**
 * AddDateApi
 */
export class AddDateApi extends V1BaseControllers.AddDateApiBase {
  constructor(requestContext: RequestContext) {
    super(requestContext);
  }

  /**
    * @param request Request.AddDateRequest
    * addDate logic
  */
  addDate = async (request: Request.AddDateRequest) => {
    this.util.log.info('start addDate execution');
    // TODO: add your implementation logic
  }

  /**
    * @param request Request.AddDateRequest
    * @param error TypeError
    * addDateErrorHandler logic
  */
  addDateErrorHandler = async (error: TypeError, request: Request.AddDateRequest) => {
    this.util.log.info('start addDateErrorHandler execution');
    // TODO: error handling should go here
    // this.response;
  }
}
Tip:

Note using Request.AddDateQuery to define variable from operation query type.

Operation Handler

The Operation Handler method should have the actual implementation logic.

The request is provided as a type-safe object which is dependent on what is modelled in the API Namespace and holds the following properties:

  • query (containing query parameters of the request)
  • path (containing path parameters of the request)
  • body (containing the request body - only for PUT and POST operations)
  • _request (the original express request object)

The handler also provides type-safe access to the response of the operation. A response always consists out of the following properties:

  • body (the response body that has been modelled in the API nampespace)
  • statusCode (HTTP status code also modelled in the API Namespace)

Additionally, errors can be thrown inside the operation handler in case of special or unexpected situations.

Access request object and set response object

  addDate = async (request: Request.AddDateRequest) => {
    this.util.log.info('start addDate execution');
    
    const value = request.query.prop1;
    // alternative way to define myAddDateQueryVar using the provided types
    // const value: Request.AddDateQuery = request.query.prop1;

    // further operation logic
    // .....

    if(value == 'Valid') {
      // set response body
      this.response.body = {
        prop1: value
      };

      // set response status code
      this.response.status = 200;
    } else {
      // throw an error
      const myError: Schema.Error = {
        code: 400,
        message: 'Some invalid value'
      };
      throw myError;
    }

  }

Trigger a domain service from an API operation

  addDate = async (request: Request.AddDateRequest) => {

    // ...

    // prepare service input
    const input =  this.factory.entity.ServiceIdentifier_Input();
    input.property1 = request.query.prop1; 

    // trigger service
    const serviceOutput = await this.services.date.ServiceIdentifier(input); 

    // further logic to set response based on service output
    // ...
  }

Trigger a command from an API operation

  addDate = async (request: Request.AddDateRequest) => {

    // ...

    // load entity from repository
    const thing = await this.repo.date.Thing.findById('someId');

    // prepare command input
    const input =  this.factory.entity.Command1_Input();
    input.property1 = request.query.prop1; 

    // trigger commad
    await thing.Command1(input);

    // further logic to set response
    // ...
  }

Error Handler

The Error Handler method should have all error handling logic thrown from the operation handler method. The error that has been thrown and the request will be available as handler parameters. Inside the handler the response can be set to represent what went wrong during the execution of the operation.

API Operation error handling

Operation xyzErrorHandler() method can be used to implement error handling logic for the errors that might occur within an API operation's execution.

The xyzErrorHandler() method is executed automatically if an error occurs and it gets the error that occurred as an argument. The method will have access to the responses modelled in the operation. The passed error can then be checked using the isInstanceOf utility and the operation response (both status code and body) can be set accordingly.

See below example (v1 is the name of the API Namespace and getPizza is the name of the API operation):

/**
 * GetPizzaApi
 */
export class GetPizzaApi extends V1BaseControllers.GetPizzaApiBase {
  constructor(requestContext: RequestContext) {
    super(requestContext);
  }

  /**
    * @param request Request.GetPizzaRequest
    * getPizza logic
  */
  getPizza = async (request: Request.GetPizzaRequest) => {
    // TODO: add your implementation logic
    this.util.log.info('start getPizza execution');
    // this.response.statusCode = 200;
  }

  /**
    * @param request Request.GetPizzaRequest
    * @param error TypeError
    * getPizzaErrorHandler logic
  */
  getPizzaErrorHandler = async (error: TypeError, request: Request.GetPizzaRequest) => {
    this.util.log.info('start getPizzaErrorHandler execution');
    
    // Add Error handling logic below and set this.response that will be returned as operation v1_getPizza response
    if (this.isInstanceOf.error.GeneralError(error)) {
      this.response.status = 500;
      this.response.body = {
        errorCode: 'GE101',
        errorMessage: 'General Error occurred, original Error message: ' + error.message
      };
    }

    if (this.isInstanceOf.error.ValidationError(error)) {
      this.response.status = 400;
      this.response.body = {
        errorCode: 'VE101',
        errorMessage: 'Validation Error occurred, original Error message: ' + error.message
      };
    }

    if (this.isInstanceOf.error.AggregateNotFoundError(error)) {
      this.response.status = 404;
      this.response.body = {
        errorCode: 'AG101',
        errorMessage: 'Aggregate cannot be found: ' + error.message
      };
    }

    if (this.isInstanceOf.businessError.xyzDomainNamespace.BusinessError(error)) {
      this.response.status = 400;
      this.response.body = {
        errorCode: 'BE101',
        errorMessage: 'Business Error occurred, original Error message: ' + error.message
      };
    }
  }
}

If you need to check for other error types (e.g., validation errors, etc.) please see TypeScript Solution Framework (SDK) - isInstanceOf checks for further details.