Implementing HTTP status codes when exposing a REST API in OutSystems
Continuing from my last article "Handling HTTP status codes when consuming a REST in OutSystems", today we take the exposing an API perspective.
A very interesting read for some conceptual background is the "RESTfull API Design: What About Errors?" from Google/apigee blog post, which we will use as a guide for this exercise.
apigee has some very interesting e-books dedicated to API building, including Brian Mulloy ebook "Web API Design - Crafting Interfaces that Developers Love".
Concepts
As Brian Mulloy points out on his blog, the API that you are designing is a black box for developers and most prefer a trial and error approach. These two reasons make it very important to communicate errors in a clear and rich way.
Also mentioned on the ebook the most popular API's out there use 3 distinct patterns:
- Abstract error message and usage of an HTTP status error code
- Rich error message and usage of an HTTP status error code
- An error message but always returning HTTP 200 success code
Best Practices
Brian Mulloy recommends we use HTTP status codes and map them to the standard meaning. There are plenty of flavors for our needs (check the wiki) including the iconic HTTP 418 I'm not a teapot.
We should at least implement 3 codes:
- 200 - OK (all is good)
- 400 - Bad Request (client did something wrong)
- 500 - Internal Server Error (API did something wrong)
From here we can expand in details. Any OutSystems API implements these Built-in HTTP Status Codes: 200, 400, 401, 403, 404, 405, 406, 415 and 500. However, most of them are tied to specific platform events and can be customized to our needs.
For example, a "404 Not Found" is returned by OutSystems if a REST API method is not found, however, you can still raise a 404 if the result of a query is empty.
Anatomy of a REST call in OutSystems
- Security Validations: After receiving the REST API Method request, OutSystems executes the security validations according to the settings in REST API properties HTTP Security and Internal Access Only;
- OnRequest(): OnRequest callback allows you to run logic over the requests after receiving them;
- OnAuthentication(): OnAuthentication callback allows you to add basic authentication or custom authentication to requests;
- Parameters Deserialization and Validation: Deserialization of the input parameters and validation of the data types, mandatory values, etc;
- Execute Method: Executes the action that implements the REST API Method;
- Parameters Serialization: Serialization of the output parameters to return in the response;
- OnResponse(): OnResponse callback allows you to run logic over the responses before sending them. It is always executed, even in an error situation.
Step 1 - Take control over the OutSystems exception handling
OutSystems uses the following HTTP status codes when working with exposed REST API methods.
This should be enough for our needs, but we do want to take a step further and present a nice rich error detail response and also be in total control when to raise the exception.
The default exception for REST is represented by:
{
"Errors": [
"The 'Id' URL parameter is missing in the request.",
"The request body is missing."
],
"StatusCode": 400
}
But we want to add some richness to it, something like:
{"HTTPStatus": {
"HTTPCode": 404,
"developerMessage": "Not Found",
"userMessage": "The server can not find the requested resource. In the browser, this means the URL is not recognized. In an API, this can also mean that the endpoint is valid but the resource itself does not exist. Servers may also send this response instead of 403 to hide the existence of a resource from an unauthorized client. This response code is probably the most famous one due to its frequent occurrence on the web.",
"errorCode": "404",
"moreInfo": "For more information please visit https://en.wikipedia.org/wiki/List_of_HTTP_status_codes"
}
}
}
So, we start by creating this structure, including it on our API response and a user exception, in our case REST.
After this, let's create a generic method to raise the exception. (please note that the odd display of the flow is just to make it look better on a document)
Step 2 - Implement the exceptions
So let's walk through the exception implementation.
Authentication
The first thing to run is the authentication check. Whatever authentication method you're using, just raise the appropriate exception if it fails. Note the exception is raised by the generic method created above.
From this point on you just need to replicate the same concept for Authorization, Request format and the exceptions coming from the implementation itself.
Authorization
Request Validation
Inside the implementation
Here is where you surface the exceptions coming from the implementation into the API itself.
I went a bit overboard with this by actually translating the exceptions coming from the implementation into API exceptions.
This is done by catching all exceptions on the public actions of the implementation, identifying them, and re-raising them as REST user exceptions from this module.
Step 3 - Customize the Response
Create a generic action to handle the customized response.
The following may look a bit complex but it is actually straightforward:
- Check if the response is an exception or a normal response
- If normal response just let the request go forward
- In the example, a simple Deserialize works fine but your mileage may vary
- Check if the exception is a standard exception or a rich exception
- If standard then parse it into a rich exception
- Parse the rich exception into JSON
- Send the JSON back to the client
Playing with the Example
You can try it out here.
Or use a few simple HTTP requests:
- Normal request, 200 OK
- Simulate a 400 bad request
- Simulate a 401 Unauthorized
- Simulate a 403 Forbidden
- Simulate a 404 Not Found
- Simulate a 500 Internal Error
Get the Component
I’ve published the component in the OutSystems Forge.
Give it a try, and go create those APIs.
disagree about 404 (NOT FOUND) - it pretty clear it's about server not being able to process a URI, cause there is no representation behing it, it's in status specification,
Great documents Rui. Will reuse it for sure in our best practices! Cheers!
Gopi Nath
How is this better than native code?