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.