Services, whether on the client or server, should return a predictable result for their consumers by relaying the status of the service request back in a consumable form without raising exceptions. One way to accomplish this is for each service call to return a generic service result model that encapsulates the status (success, failure, etc) as well as errors or, if successful, the response data.

Consumers of the service can expect each call to return data in a consistent form, for example:

{
    errors: [],
    type: "success",
    data: ["this", "returns", "an", "array"]
}

Simple enough. In order to keep our code DRY and adhere to the Single Responsibility Principle we can create a base Result abstract class from which our other result types will inherit.

import ResultType from "./ResultType";

abstract class Result<T> {
    public abstract type: ResultType | string;
    public abstract errors: string[];
    public abstract data: T;
}

export default Result;

Here we define that our result type will be a string or an enum value from ResultType, that errors is an array of string, and that T is our generic data type that we will define on each result.

From here we can begin building result types that are descriptive of our service's requirements. Let's start with a SuccessResult:

import Result from "./Result";
import ResultType from "./ResultType";

class SuccessResult<T> extends Result<T> {
    constructor(data: T)  {
        super();
        this.data = data;
    }
    public type: ResultType = ResultType.Ok;
    public errors: string[] = [];
    public data: T;
}

export default SuccessResult;

The above example describes what our service will return if everything goes as planned and the call was a success. We help adhere to the Single Responsibility Principle by defining what the Success result looks like with preset values and building a constructor that allows us to easily consume it in our service layer.


const getCustomer = (id: number): SuccessResult<Customer> => {
    const result = await customerRepository.find(id);
    return new SuccessResult(result);
}

If your service is called and returns a value through a controller in your web api you can easily convert your result types to HTTP response codes:

const resultToResponse = <T>(result: Result<T>, res: Response) => {
    switch(result.type) {
        case ResultType.Success:
            res.status(200).send(result.data);
            break;
        case ResultType.Failure:
            res.status(400).send(result.errors);
            break;
    }
}

A predictable an uniform response helps mediate communication between your controllers and repository layer. This example can be extended to include uniformity for logging, error chaining, handling, etc. For an example set of Results you can check out my GitHub repo here.