Backend Tutorial
Ktor REST Apis — Exception handling
Catch it if you can!
In this Rest Api series using Ktor, we earlier explored how can we create rest apis. But exceptions are unsaid reality of any code base. It’s very important that our code is ready for any such unexpected scenarios. So in this article, we’ll be exploring how can we handle exceptions in our rest apis.
In case you want to start from the beginning, please read the following and you won’t be disappointed.
Ktor REST Apis - Part 1 (Project Set up)
Ktor REST Apis - Part 2 (Create Routes)
Ktor REST Apis - Part 3 (Testing Routes)
Ktor REST Apis - Integrating SQL Database using Ktorm
StatusPages
In Ktor, exception handling is done using StatusPages. This is a plugin which gracefully handles any failure state based on a thrown exception or a status code. It means that we can provide implementations for
- any uncaught exception or throwable i.e exception raised from anywhere during the execution.
- any number of specific status codes
Set up
To use this plugin we need to add its dependency as follows.
// status pages for exception handling
implementation("io.ktor:ktor-server-status-pages:$ktor_version")
Then we need to install this plugin like we do with any other plugin as follows
fun Application.configureExceptions() {
install(StatusPages)
}
Then we can call this configuration from our application’s main module and we’re ready to use the plugin.
StatusPages using Exception based handling
Once we install the plugin, right there itself, we can provide the implementation for returning content based on exceptions raised.
Now, to define the exception handling we simply create an exception block and passes a <Throwable> type. It now indicates that any throwable, if not handled, should come here and response is returned as per the definition inside this block.
We create two custom exceptions that we assume, are thrown from different apis at runtime. These are simply throwable classes as follows.
class ValidationException(override val message: String): Throwable()
class ParsingException(override val message: String): Throwable()
We override the message field so that we can pass custom messages and now the complete handling looks like as follows.
We simply checks what kind of unhandled exception have we received and returns a response accordingly. Here ExceptionResponse is a data class used to provide json response format.
@Serializable
data class ExceptionResponse(
val message: String,
val code: Int
)
We’ll see the apis in action later in the Action section of the article.
StatusPages using Status code based handling
Similar to exception based handling under the exception block, we can define status code based handling under status block as follows.
In this case, the control comes to StatusPages if the api will be returning any http status which is defined under this status block. For instance, we’ve mentioned Internal Server and Bad Gateway errors. So whenever our apis will return with these codes, the execution control will come here and we’ll be able to respond with defined responses.
Alright! Enough of set up! Now it’s time to see all of the above in action.
Action
We’ll create 4 api end points, all of which will throw four different types of exception. Two of them will be uncaught exception and rest two will be based on status code.
exception/validation // exception based
exception/parsing // exception based
exception/internal-error // status based
exception/bad-gateway // status based
For these apis, we’ll set up a route as follows.
fun Application.configureExceptionRoutes() {
routing {
route("/exception") {
exceptionRoutes()
statusRoutes()
}
}
}
exceptionRoutes will contain the exception based routes namely validation and parsing.
statusRoutes will contain the status based routes namely internal-error and bad-gateway.
API: exception/validation
We simply define an end point and throws a Validation exception.
get("/validation") {
throw ValidationException("this is a validation exception")
}
On hitting this endpoint, we get the result as follows.
{
"message": "this is a validation exception",
"code": 400
}
Whoo! On to the point. Let’s test others too.
API: exception/parsing
We simply define an end point and throws a Parsing exception.
get("/parsing") {
throw ParsingException("this is a parsing exception")
}
On hitting this endpoint, we get the result as follows.
{
"message": "this is a parsing exception",
"code": 417
}
API: exception/internal-error
We simply define an end point and returns internal server error status.
get("/internal-error") {
call.respond(
HttpStatusCode.InternalServerError
)
}
On hitting this endpoint, we get the result as follows.
{
"message": "Oops! internal server error at our end",
"code": 500
}
API: exception/bad-gateway
We simply define an end point and returns internal server error status.
get("/bad-gateway") {
call.respond(
HttpStatusCode.BadGateway
)
}
On hitting this endpoint, we get the result as follows.
{
"message": "Oops! We got a bad gateway. Fixing it. Hold on!",
"code": 502
}
Advantages
Using StatusPages provides us with some advantages which are:
- We can provide a consistent implementation for our uncaught exception. This is also beneficial from code maintenance point of view.
- We can provide specific implementation for specific exception while still being consistent with rest of the exceptions.
- We can treat different status codes to provide consistent response to users.
This would be enough to get started with handling exceptions in our apis. Play around with different scenario and experience them in real. For reference you can checkout the complete code at the following. Hope it would be useful.