Skip to content
Advertisement

Best Practice of calling an API endpoint [closed]

What is the best practice of returning when calling an API endpoint? Is it best to return the whole response? Let’s take a simple example. Let’s say for example I am building a webshop.

The endpoint I am calling required 2 parameters and the method is POST. The product ID and the quantity.

Every product has a stock. So when I fill in ’50’ as quantity, I will get an error as a response like this:

Status: 405 Method Not Allowed

{
    "code": "cocart_quantity_invalid_amount",
    "message": "Quantity must be 26 or lower.",
    "data": {
        "status": 405
    }
}

This is good and clear.

If Status is 200 OK, I get a whole bunch of data back. In the code below, I return a Cart object. Or is it better if I return a Message object containing the error message? And return a message with ‘Item successfully added to cart’?

The thing is, that I cannot return an error message when the call failed, and return a cart when the call succeeded. What is the best practice and why? It’s also fine if you can explain it in javascript code.

In the code example below I am returning a cart in both the if and else statements. This is not the best practice if I am correctly…

class Message {
  String message;

  Message({required this.message});

  // Make a message object from retrieved json
  factory Message.fromJson(Map<String, dynamic> json) {
    return Message(message: json['message']);
  }
}
Future<Cart> addToCart(productId, quantity) async {
    String token = await Auth().getToken();
    var response = await http.post(
      Uri.parse('https://websitename.nl/wp-json/cocart/v2/cart/add-item'),
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json',
        'Authorization': token,
      },
      body: <String, String>{"id": productId, "quantity": quantity},
    );
    if (response.statusCode == 200) {
      // String data = response.body;
      // var decodedData = json.decode(data);

      return Cart.fromJson(jsonDecode(response.body));
    } else {
      return Cart.fromJson(jsonDecode(response.body));
    }
  }

So what is the best practice and why?

  1. Is it better to return a Message object with ‘Succeeded’ or ‘the error message itself’
  2. Is it better to return a cart object even if the call did not succeed.
  3. Or what is recommended by you guys? Happy to hear the answers. I could not find a clear answer myself, that’s why I am asking it in this post.

Answer

So you have a method, and you want to return something (Cart) when the method succeeds and something else (Message) when the method fails, I will tell you two possible ways to do this:

1. Make a class that encapsulates both objects:

The idea here is to have a class that contains the information about success or not and also the cart, this would look something like this:

class ApiResult {
  String? errorMessage;
  int code;
  Cart? cart;

  bool get hasError => code != 200;
  bool get hasData => cart != null;

  ApiResult({
    this.errorMessage
    this.cart,
    required this.code
  }): assert(
      (code!=200 && errorMessage!=null)
      || (code==200 && cart!=null)
    );
}

So the above class three important things, a nullable cart, a nullable message, and the status code of the response, so we know which to check. We can also add a couple of named constructors for simplicity’s sake:

class ApiResult {
  String? errorMessage;
  int code;
  Cart? cart;

  bool get hasError => code != 200;
  bool get hasData => cart != null;

  ApiResult({
    this.errorMessage
    this.cart,
    required this.code
  }): assert(
      (code!=200 && errorMessage!=null)
      || (code==200 && cart!=null)
    );

  ApiResult.success({
    required int code,
    required Cart cart,
  }): ApiResult(cart: cart, code: code);

  ApiResult.error({
    required int code,
    required String message
  }): ApiResult(message: message, code: code);
}

Then on your API call:

Future<ApiResult> addToCart(productId, quantity) async {
    String token = await Auth().getToken();
    var response = await http.post(
      Uri.parse('https://websitename.nl/wp-json/cocart/v2/cart/add-item'),
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json',
        'Authorization': token,
      },
      body: <String, String>{"id": productId, "quantity": quantity},
    );
    if (response.statusCode == 200) {
      return ApiResult.success(code: 200, cart: Cart.fromJson(jsonDecode(response.body)));
    } else {
      var data = jsonDecode(response.body);
      return ApiResult.error(code: response.statusCode, message: data['message']);
    }
  }

After writing the whole class, I realize you could probably do without the code parameter, and just use the message and the cart, that would probably simplify the code a lot.

2. Throw the error message:

Your second option is to use a try catch around your call to the method, and throw the error message, something like this:

Future<Cart> addToCart(productId, quantity) async {
    String token = await Auth().getToken();
    var response = await http.post(
      Uri.parse('https://websitename.nl/wp-json/cocart/v2/cart/add-item'),
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json',
        'Authorization': token,
      },
      body: <String, String>{"id": productId, "quantity": quantity},
    );
    if (response.statusCode == 200) {
      return Cart.fromJson(jsonDecode(response.body));
    } else {
      throw Message.fromJson(jsonDecode(response.body));
    }
  }

This way, when calling the method:

try {
  await addToCart(...);
} on Message catch (e) {
  // do something about the error
}

Both of the above solutions have their pros and cons, if we are talking “best practices” probably the second option is better, cause it uses try-catch in the way that it was meant to be used, but in my opinion either is good.

Advertisement