Ktor: REST API for Mobile
In this tutorial, you’ll create a REST API server for mobile apps using the new Ktor framework from JetBrains. By Kevin D Moore.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Ktor: REST API for Mobile
30 mins
- Getting Started
- Starting a Project in IntelliJ
- Implementing APIs
- Defining Routes
- Setting up Postgres
- Setting up Database Dependencies
- Running the Server
- Adding a Data Layer
- Setting up Model Classes
- Working on the Database Classes
- Adding Your Repository
- Authenticating Your Users
- Configuring Application
- Building the Routes
- Adding the User Create Route
Authenticating Your Users
You need to authenticate users to keep your server secure. When you created your server, you chose the JWT authentication feature. But you’ll need to create a few functions to use it.
Start by creating a new file titled Auth.kt under auth and add the following:
import io.ktor.util.KtorExperimentalAPI
import io.ktor.util.hex
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
@KtorExperimentalAPI // 1
val hashKey = hex(System.getenv("SECRET_KEY")) // 2
@KtorExperimentalAPI
val hmacKey = SecretKeySpec(hashKey, "HmacSHA1") // 3
@KtorExperimentalAPI
fun hash(password: String): String { // 4
val hmac = Mac.getInstance("HmacSHA1")
hmac.init(hmacKey)
return hex(hmac.doFinal(password.toByteArray(Charsets.UTF_8)))
}
- Makes use of the
SECRET_KEY
Environment Variable defined in step 2. Use this value as the argument of thehex
function, which turns the HEX key into aByteArray
. Note the use of@KtorExperimentalAPI
to avoid warnings associated with the experimental status of thehex
function. - Defines Environment Variable.
- Creates a secret key using the given algorithm,
HmacSHA1
. -
hash
converts a password to a string hash.
Next, create a new class in auth named JwtService
and add the following:
import com.auth0.jwt.JWT
import com.auth0.jwt.JWTVerifier
import com.auth0.jwt.algorithms.Algorithm
import com.raywenderlich.models.User
import java.util.*
class JwtService {
private val issuer = "todoServer"
private val jwtSecret = System.getenv("JWT_SECRET") // 1
private val algorithm = Algorithm.HMAC512(jwtSecret)
// 2
val verifier: JWTVerifier = JWT
.require(algorithm)
.withIssuer(issuer)
.build()
// 3
fun generateToken(user: User): String = JWT.create()
.withSubject("Authentication")
.withIssuer(issuer)
.withClaim("id", user.userId)
.withExpiresAt(expiresAt())
.sign(algorithm)
private fun expiresAt() =
Date(System.currentTimeMillis() + 3_600_000 * 24) // 24 hours
}
The previous code requires the JWT_SECRET
Environment Variable in step 1 to create the JWTVerifier
in step 2. generateToken
, defined in step 3, generates a token that the API uses to authenticate the request. You’ll need this function later.
Next, create a file named MySession in the auth folder and add this code:
data class MySession(val userId: Int)
This stores the current userId
in a Session.
Configuring Application
In this section, you’ll add the required plumbing to configure the server. To do so, you’ll use some of the functions you created earlier.
Open Application.kt, remove MySession
and import the newer MySession
you just created.
Now, add the following after the install(Sessions)
section:
// 1
DatabaseFactory.init()
val db = TodoRepository()
// 2
val jwtService = JwtService()
val hashFunction = { s: String -> hash(s) }
Import these classes. The first part of this code initializes the data layer you defined earlier, while the second part handles authentication.
Next, add the following to the install(Authentication)
section:
jwt("jwt") { //1
verifier(jwtService.verifier) // 2
realm = "Todo Server"
validate { // 3
val payload = it.payload
val claim = payload.getClaim("id")
val claimString = claim.asInt()
val user = db.findUser(claimString) // 4
user
}
}
You define the JWT
name in step 1, which can be anything you want.
Step 2 specifies the verifier you created in the JwtService
class.
Step 3 creates a method that runs each time the app needs to authenticate a call.
Finally, step 4 tries to find the user in the database with the userId
from claimString
. If the userID exists, it verifies the user. Otherwise, it returns a null user and rejects the route.
Before you continue, run your server to make sure everything compiles and runs properly. You should see several Hikari debug statements in the output window.
Building the Routes
If you started your own project, here’s an extra step for you. At the bottom of Application.kt, add the following constant:
const val API_VERSION = "/v1"
This is a constant that you will use to prefix all the paths in your routes.
Adding the User Create Route
In Routes, create a new file named UserRoute.
Start by adding the following imports to the top of the file:
import com.raywenderlich.API_VERSION
import com.raywenderlich.auth.JwtService
import com.raywenderlich.auth.MySession
import com.raywenderlich.repository.Repository
import io.ktor.application.application
import io.ktor.application.call
import io.ktor.application.log
import io.ktor.http.HttpStatusCode
import io.ktor.http.Parameters
import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.locations.Location
import io.ktor.locations.post
import io.ktor.request.receive
import io.ktor.response.respond
import io.ktor.response.respondText
import io.ktor.routing.Route
import io.ktor.sessions.sessions
import io.ktor.sessions.set
These will avoid issues of choosing the wrong version of a class. Next, add some route constants:
const val USERS = "$API_VERSION/users"
const val USER_LOGIN = "$USERS/login"
const val USER_CREATE = "$USERS/create"
These define the strings needed for the routes, allowing you to create or log in a user.
Add the following route classes:
@KtorExperimentalLocationsAPI
@Location(USER_LOGIN)
class UserLoginRoute
@KtorExperimentalLocationsAPI
@Location(USER_CREATE)
class UserCreateRoute
Since Locations API is experimental, using @KtorExperimentalLocationsAPI
removes any compiler warnings. @Location(USER_LOGIN)
associates the string USER_LOGIN
with the UserLoginRoute
class.
Then add the user create route:
@KtorExperimentalLocationsAPI
// 1
fun Route.users(
db: Repository,
jwtService: JwtService,
hashFunction: (String) -> String
) {
post<UserCreateRoute> { // 2
val signupParameters = call.receive<Parameters>() // 3
val password = signupParameters["password"] // 4
?: return@post call.respond(
HttpStatusCode.Unauthorized, "Missing Fields")
val displayName = signupParameters["displayName"]
?: return@post call.respond(
HttpStatusCode.Unauthorized, "Missing Fields")
val email = signupParameters["email"]
?: return@post call.respond(
HttpStatusCode.Unauthorized, "Missing Fields")
val hash = hashFunction(password) // 5
try {
val newUser = db.addUser(email, displayName, hash) // 6
newUser?.userId?.let {
call.sessions.set(MySession(it))
call.respondText(
jwtService.generateToken(newUser),
status = HttpStatusCode.Created
)
}
} catch (e: Throwable) {
application.log.error("Failed to register user", e)
call.respond(HttpStatusCode.BadRequest, "Problems creating User")
}
}
}
Here’s how all that breaks down.
Adding the TODO Routes
Retrieving TODOs
Where to Go From Here?
Testing Routes
Adding the User Login Route
Test the Server
Handling TODOs
Testing the Server
Test the Server
But you’re not done yet. Add the following code to the routing
section in Application.kt:
Build and run the server again.
Perform a Build ▸ Build Project to ensure your project still builds without errors.
The Postman app is a terrific tool for testing APIs. Find it at getpostman.com/.
Once installed, open Postman and use a new tab to make a POST request to generate a new user. Use localhost:8080/v1/users/create
. In the Body tab, add these three variables: displayName, email and password.
Use any data you want in the Value column. Press Send and you’ll get a token back.
To save that token in Postman, set a global variable that you can use in other calls. To do so, go to the Tests tab and add:
This sets a variable named jwt_token that you can use in other calls.
Now that your server can generate jwt tokens, its time to add some routes to perform authentication.
Open UserRoutes and add the following after the UserCreateRoute
:
Here’s the breakdown.
Restart your server and open Postman. Use a new tab to create a POST
request to log in a user. Use localhost:8080/v1/users/login
.
In the Body tab, add two variables: email and password using the data from the create call in the Value column. Press Send and you’ll get a token back.
Perform another Build ▸ Build Project to ensure your project builds without errors.
Before adding the TODO routes, you need to adapt the repository. Open Repository and after findUserByEmail
add:
Open TodoRepository and add after rowToUser
:
Here’s what the code above does:
Now, create the routes for handling TODOs.
Create a new file named TodosRoute in the routes folder and add the following code:
This defines one route for TODOs. Since you want to create, delete or retrieve TODOs, you can use one route with three different REST
methods: POST
, DELETE
and GET
.
To do so, add the following code below the previous one:
This code is similar to the code you used to create a User:
Next, open Application.kt and add the TODO route after the users route in the routing
section:
Restart your server and open Postman. Since you restarted the server, you’ll have to log in again.
Create a new tab to add a new TODO. First, you’ll need to add the authorization token you got when you created the user.
Go to the Authorization tab and add the jwt_token variable in the token field:
Next, go to the Body tab. Create a TODO parameter, or key. The value field is the name of the TODO.
Set the done state by adding another key named done and setting its value to true.
Give a value for the TODO and press Send. You’ll see the JSON result in the body section below.
To cap things off, in the next section, you’ll create the routes to fetch TODOs.
Now that your users can save their TODOs, they need to be able to call them up again as well.
Open TodosRoute and add the following underneath the post TodoRoute
function:
Restart your server and open Postman. Log in so you have an updated token, then create a new tab to get your TODOs.
Add the authorization token you got when you created the user and press Send. You’ll see something like:
Congratulations! This was a loooong journey, but you’ve reached your destination. You’ve created a REST Server!
Download the final version of this project using the Download Materials button at the top or bottom of this tutorial.
If you want to learn more about Ktor, swing over to ktor.io/, or checkout the video course if you’re interested in learning how to dockerize and deploy your Ktor server to the cloud.
If you’re interested in learning about how to consume REST APIs from Android, checkout the video course on the site.
We hope you enjoyed this tutorial. If you have any questions, comments or musings, please join the forum discussion below.
- Defines an extension function to
Route
namedusers
that takes in aRepository
, aJWTService
and a hash function. - Generates a route for creating a new user.
- Uses the
call
parameter to get the parameters passed in with the request. - Looks for the password parameter and returns an error if it doesn’t exist.
- Produces a hash string from the password.
- Adds a new user to the database.
- Defines a
POST
for logging in a user. - Tries to find the user by their email address.
- Compares the hash created from the passed-in password with the existing hash.
- If the hashes don’t match, the method returns an error. Otherwise, it sets the current session for the user ID in step 4 and creates a token from the user to return in step 5.
- Defines
addTodo
, which takes a user ID, the TODO text and the done flag. - Defines the method to get all TODOs for a given user ID.
- Note how
getTodos
useseq
to find a user that matches the user ID. - Defines a helper function to convert an Exposed
ResultRow
to yourTODO
class. - Uses the
authenticate
extension function to tell the system you want to authenticate these routes. - Defines the new TODO route.
- Checks if the user has a session. If not, it returns an error.
- Adds the TODO to the database.
Next, open Application.kt and add the TODO route after the users route in the
routing
section:todos(db)
Testing the Server
Restart your server and open Postman. Since you restarted the server, you’ll have to log in again.
Create a new tab to add a new TODO. First, you’ll need to add the authorization token you got when you created the user.
Go to the Authorization tab and add the jwt_token variable in the token field:
Next, go to the Body tab. Create a TODO parameter, or key. The value field is the name of the TODO.
Set the done state by adding another key named done and setting its value to true.
Give a value for the TODO and press Send. You’ll see the JSON result in the body section below.
To cap things off, in the next section, you’ll create the routes to fetch TODOs.
Retrieving TODOs
Now that your users can save their TODOs, they need to be able to call them up again as well.
Open TodosRoute and add the following underneath the
post TodoRoute
function:get<TodoRoute> { val user = call.sessions.get<MySession>()?.let { db.findUser(it.userId) } if (user == null) { call.respond(HttpStatusCode.BadRequest, "Problems retrieving User") return@get } try { val todos = db.getTodos(user.userId) call.respond(todos) } catch (e: Throwable) { application.log.error("Failed to get Todos", e) call.respond(HttpStatusCode.BadRequest, "Problems getting Todos") } }
Test the Server
Restart your server and open Postman. Log in so you have an updated token, then create a new tab to get your TODOs.
Add the authorization token you got when you created the user and press Send. You’ll see something like:
Where to Go From Here?
Congratulations! This was a loooong journey, but you’ve reached your destination. You’ve created a REST Server!
Download the final version of this project using the Download Materials button at the top or bottom of this tutorial.
If you want to learn more about Ktor, swing over to ktor.io/, or checkout the video course if you’re interested in learning how to dockerize and deploy your Ktor server to the cloud.
If you’re interested in learning about how to consume REST APIs from Android, checkout the video course on the site.
We hope you enjoyed this tutorial. If you have any questions, comments or musings, please join the forum discussion below.
But you’re not done yet. Add the following code to the routing
section in Application.kt:
users(db, jwtService, hashFunction)
Build and run the server again.
Perform a Build ▸ Build Project to ensure your project still builds without errors.
Testing Routes
The Postman app is a terrific tool for testing APIs. Find it at getpostman.com/.
Once installed, open Postman and use a new tab to make a POST request to generate a new user. Use localhost:8080/v1/users/create
. In the Body tab, add these three variables: displayName, email and password.
Use any data you want in the Value column. Press Send and you’ll get a token back.
To save that token in Postman, set a global variable that you can use in other calls. To do so, go to the Tests tab and add:
var data = responseBody;
postman.clearGlobalVariable("jwt_token");
postman.setGlobalVariable("jwt_token", data);
This sets a variable named jwt_token that you can use in other calls.
Now that your server can generate jwt tokens, its time to add some routes to perform authentication.
Adding the User Login Route
Open UserRoutes and add the following after the UserCreateRoute
:
post<UserLoginRoute> { // 1
val signinParameters = call.receive<Parameters>()
val password = signinParameters["password"]
?: return@post call.respond(
HttpStatusCode.Unauthorized, "Missing Fields")
val email = signinParameters["email"]
?: return@post call.respond(
HttpStatusCode.Unauthorized, "Missing Fields")
val hash = hashFunction(password)
try {
val currentUser = db.findUserByEmail(email) // 2
currentUser?.userId?.let {
if (currentUser.passwordHash == hash) { // 3
call.sessions.set(MySession(it)) // 4
call.respondText(jwtService.generateToken(currentUser)) // 5
} else {
call.respond(
HttpStatusCode.BadRequest, "Problems retrieving User") // 6
}
}
} catch (e: Throwable) {
application.log.error("Failed to register user", e)
call.respond(HttpStatusCode.BadRequest, "Problems retrieving User")
}
}
Here’s the breakdown.
Test the Server
Restart your server and open Postman. Use a new tab to create a POST
request to log in a user. Use localhost:8080/v1/users/login
.
In the Body tab, add two variables: email and password using the data from the create call in the Value column. Press Send and you’ll get a token back.
Perform another Build ▸ Build Project to ensure your project builds without errors.
Adding the TODO Routes
Before adding the TODO routes, you need to adapt the repository. Open Repository and after findUserByEmail
add:
suspend fun addTodo(userId: Int, todo: String, done: Boolean): Todo?
suspend fun getTodos(userId: Int): List<Todo>
Open TodoRepository and add after rowToUser
:
// 1
override suspend fun addTodo(userId: Int, todo: String, done: Boolean): Todo? {
var statement : InsertStatement<Number>? = null
dbQuery {
statement = Todos.insert {
it[Todos.userId] = userId
it[Todos.todo] = todo
it[Todos.done] = done
}
}
return rowToTodo(statement?.resultedValues?.get(0))
}
// 2
override suspend fun getTodos(userId: Int): List<Todo> {
return dbQuery {
Todos.select {
Todos.userId.eq((userId)) // 3
}.mapNotNull { rowToTodo(it) }
}
}
// 4
private fun rowToTodo(row: ResultRow?): Todo? {
if (row == null) {
return null
}
return Todo(
id = row[Todos.id],
userId = row[Todos.userId],
todo = row[Todos.todo],
done = row[Todos.done]
)
}
Here’s what the code above does:
Handling TODOs
Now, create the routes for handling TODOs.
Create a new file named TodosRoute in the routes folder and add the following code:
import com.raywenderlich.API_VERSION
import io.ktor.application.application
import io.ktor.application.call
import io.ktor.application.log
import io.ktor.auth.authenticate
import io.ktor.http.HttpStatusCode
import io.ktor.http.Parameters
import io.ktor.locations.*
import io.ktor.request.receive
import io.ktor.response.respond
import io.ktor.routing.Route
import io.ktor.sessions.get
import io.ktor.sessions.sessions
import com.raywenderlich.auth.MySession
import com.raywenderlich.repository.Repository
const val TODOS = "$API_VERSION/todos"
@KtorExperimentalLocationsAPI
@Location(TODOS)
class TodoRoute
This defines one route for TODOs. Since you want to create, delete or retrieve TODOs, you can use one route with three different REST
methods: POST
, DELETE
and GET
.
To do so, add the following code below the previous one:
@KtorExperimentalLocationsAPI
fun Route.todos(db: Repository) {
authenticate("jwt") { // 1
post<TodoRoute> { // 2
val todosParameters = call.receive<Parameters>()
val todo = todosParameters["todo"]
?: return@post call.respond(
HttpStatusCode.BadRequest, "Missing Todo")
val done = todosParameters["done"] ?: "false"
// 3
val user = call.sessions.get<MySession>()?.let {
db.findUser(it.userId)
}
if (user == null) {
call.respond(
HttpStatusCode.BadRequest, "Problems retrieving User")
return@post
}
try {
// 4
val currentTodo = db.addTodo(
user.userId, todo, done.toBoolean())
currentTodo?.id?.let {
call.respond(HttpStatusCode.OK, currentTodo)
}
} catch (e: Throwable) {
application.log.error("Failed to add todo", e)
call.respond(HttpStatusCode.BadRequest, "Problems Saving Todo")
}
}
}
}
This code is similar to the code you used to create a User:
- Defines a
POST
for logging in a user. - Tries to find the user by their email address.
- Compares the hash created from the passed-in password with the existing hash.
- If the hashes don’t match, the method returns an error. Otherwise, it sets the current session for the user ID in step 4 and creates a token from the user to return in step 5.
- Defines
addTodo
, which takes a user ID, the TODO text and the done flag. - Defines the method to get all TODOs for a given user ID.
- Note how
getTodos
useseq
to find a user that matches the user ID. - Defines a helper function to convert an Exposed
ResultRow
to yourTODO
class.
- Uses the
authenticate
extension function to tell the system you want to authenticate these routes. - Defines the new TODO route.
- Checks if the user has a session. If not, it returns an error.
- Adds the TODO to the database.
Next, open Application.kt and add the TODO route after the users route in the
routing
section:todos(db)
Testing the Server
Restart your server and open Postman. Since you restarted the server, you’ll have to log in again.
Create a new tab to add a new TODO. First, you’ll need to add the authorization token you got when you created the user.
Go to the Authorization tab and add the jwt_token variable in the token field:
Next, go to the Body tab. Create a TODO parameter, or key. The value field is the name of the TODO.
Set the done state by adding another key named done and setting its value to true.
Give a value for the TODO and press Send. You’ll see the JSON result in the body section below.
To cap things off, in the next section, you’ll create the routes to fetch TODOs.
Retrieving TODOs
Now that your users can save their TODOs, they need to be able to call them up again as well.
Open TodosRoute and add the following underneath the
post TodoRoute
function:get<TodoRoute> { val user = call.sessions.get<MySession>()?.let { db.findUser(it.userId) } if (user == null) { call.respond(HttpStatusCode.BadRequest, "Problems retrieving User") return@get } try { val todos = db.getTodos(user.userId) call.respond(todos) } catch (e: Throwable) { application.log.error("Failed to get Todos", e) call.respond(HttpStatusCode.BadRequest, "Problems getting Todos") } }
Test the Server
Restart your server and open Postman. Log in so you have an updated token, then create a new tab to get your TODOs.
Add the authorization token you got when you created the user and press Send. You’ll see something like:
Where to Go From Here?
Congratulations! This was a loooong journey, but you’ve reached your destination. You’ve created a REST Server!
Download the final version of this project using the Download Materials button at the top or bottom of this tutorial.
If you want to learn more about Ktor, swing over to ktor.io/, or checkout the video course if you’re interested in learning how to dockerize and deploy your Ktor server to the cloud.
If you’re interested in learning about how to consume REST APIs from Android, checkout the video course on the site.
We hope you enjoyed this tutorial. If you have any questions, comments or musings, please join the forum discussion below.
users(db, jwtService, hashFunction)
var data = responseBody;
postman.clearGlobalVariable("jwt_token");
postman.setGlobalVariable("jwt_token", data);
post<UserLoginRoute> { // 1
val signinParameters = call.receive<Parameters>()
val password = signinParameters["password"]
?: return@post call.respond(
HttpStatusCode.Unauthorized, "Missing Fields")
val email = signinParameters["email"]
?: return@post call.respond(
HttpStatusCode.Unauthorized, "Missing Fields")
val hash = hashFunction(password)
try {
val currentUser = db.findUserByEmail(email) // 2
currentUser?.userId?.let {
if (currentUser.passwordHash == hash) { // 3
call.sessions.set(MySession(it)) // 4
call.respondText(jwtService.generateToken(currentUser)) // 5
} else {
call.respond(
HttpStatusCode.BadRequest, "Problems retrieving User") // 6
}
}
} catch (e: Throwable) {
application.log.error("Failed to register user", e)
call.respond(HttpStatusCode.BadRequest, "Problems retrieving User")
}
}
suspend fun addTodo(userId: Int, todo: String, done: Boolean): Todo?
suspend fun getTodos(userId: Int): List<Todo>
// 1
override suspend fun addTodo(userId: Int, todo: String, done: Boolean): Todo? {
var statement : InsertStatement<Number>? = null
dbQuery {
statement = Todos.insert {
it[Todos.userId] = userId
it[Todos.todo] = todo
it[Todos.done] = done
}
}
return rowToTodo(statement?.resultedValues?.get(0))
}
// 2
override suspend fun getTodos(userId: Int): List<Todo> {
return dbQuery {
Todos.select {
Todos.userId.eq((userId)) // 3
}.mapNotNull { rowToTodo(it) }
}
}
// 4
private fun rowToTodo(row: ResultRow?): Todo? {
if (row == null) {
return null
}
return Todo(
id = row[Todos.id],
userId = row[Todos.userId],
todo = row[Todos.todo],
done = row[Todos.done]
)
}
import com.raywenderlich.API_VERSION
import io.ktor.application.application
import io.ktor.application.call
import io.ktor.application.log
import io.ktor.auth.authenticate
import io.ktor.http.HttpStatusCode
import io.ktor.http.Parameters
import io.ktor.locations.*
import io.ktor.request.receive
import io.ktor.response.respond
import io.ktor.routing.Route
import io.ktor.sessions.get
import io.ktor.sessions.sessions
import com.raywenderlich.auth.MySession
import com.raywenderlich.repository.Repository
const val TODOS = "$API_VERSION/todos"
@KtorExperimentalLocationsAPI
@Location(TODOS)
class TodoRoute
@KtorExperimentalLocationsAPI
fun Route.todos(db: Repository) {
authenticate("jwt") { // 1
post<TodoRoute> { // 2
val todosParameters = call.receive<Parameters>()
val todo = todosParameters["todo"]
?: return@post call.respond(
HttpStatusCode.BadRequest, "Missing Todo")
val done = todosParameters["done"] ?: "false"
// 3
val user = call.sessions.get<MySession>()?.let {
db.findUser(it.userId)
}
if (user == null) {
call.respond(
HttpStatusCode.BadRequest, "Problems retrieving User")
return@post
}
try {
// 4
val currentTodo = db.addTodo(
user.userId, todo, done.toBoolean())
currentTodo?.id?.let {
call.respond(HttpStatusCode.OK, currentTodo)
}
} catch (e: Throwable) {
application.log.error("Failed to add todo", e)
call.respond(HttpStatusCode.BadRequest, "Problems Saving Todo")
}
}
}
}
todos(db)
get<TodoRoute> {
val user = call.sessions.get<MySession>()?.let { db.findUser(it.userId) }
if (user == null) {
call.respond(HttpStatusCode.BadRequest, "Problems retrieving User")
return@get
}
try {
val todos = db.getTodos(user.userId)
call.respond(todos)
} catch (e: Throwable) {
application.log.error("Failed to get Todos", e)
call.respond(HttpStatusCode.BadRequest, "Problems getting Todos")
}
}