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?
- Is it better to return a Message object with ‘Succeeded’ or ‘the error message itself’
- Is it better to return a cart object even if the call did not succeed.
- 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.
Advertisement
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.