Serverless Kotlin on Google Cloud Run
Learn how to build a serverless API using Ktor then dockerize and deploy it to Google Cloud Run. By Kshitij Chauhan.
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
Serverless Kotlin on Google Cloud Run
25 mins
- Getting Started
- Defining Serverless
- Understanding Cloud Run
- Using Docker
- Getting Started with Ktor
- Creating the HTTP Server
- Starting the Server
- Detecting the Client’s IP Address
- Fetching Locations Using IP Addresses
- Adding Kotlinx Serialization and Ktor Client
- Using Ktor Client
- Fetching Location Data
- Containerizing the Application
- Pushing Images to Artifact Registry
- Deploying Image to Cloud Run
- Consuming the API
- Defining a Service Interface
- Using the Service Interface
- Integrating with ViewModel
- Where to Go From Here?
Starting the Server
In the Application.kt file, add a main method that starts the server:
val server = ...
fun main() {
server.start(wait = true)
}
The wait parameter tells the application to block until the server terminates.
At this point, you have everything you need to get a basic server up and running. To start the server, use the green icon next to main IntelliJ:
If everything went well, you’ll see logs indicating your server is running!
To test your server, use the curl command line utility. Enter the following command in the terminal:
curl -X GET "http://0.0.0.0:8080/"
You’ll see the correct response: “Hello, world!”.
➜ ~ curl -X GET "http://0.0.0.0:8080/" Hello, world!
curl: (7) Failed to connect to 0.0.0.0 port 8080 after 0 ms: Address not available, replace your curl request with curl -X GET "http://0.0.0.0:8080/".You could update the networking configuration in order to continue using 0.0.0.0 but that’s outside the scope of this tutorial. In subsequent requests, you’ll have to keep using localhost instead of 0.0.0.0.
Detecting the Client’s IP Address
In the routing function, add a route that returns the client’s IP address back to them. To get the client’s IP address, use the origin property of the request object associated with a call.
import io.ktor.server.plugins.*
// Add this in the `routing` block:
get("/ip") {
val ip = call.request.origin.remoteHost
call.respond(ip)
}
This adds an HTTP GET route on the “/ip” path. On each request, the handler extracts the client’s IP address using call.request.origin.remoteHost and returns it in the response.
Restart the server, and try this new route using curl again:
➜ ~ curl -X GET "http://0.0.0.0:8080/ip" localhost% ➜ ~
The server responds with localhost, which just means the client and server are on the same machine.
Fetching Locations Using IP Addresses
To fetch a client’s location from their IP address, you need a geolocation database. There are many free third-party services that let you query geolocation databases. IP-API is an example.
IP-API provides a JSON API to query the geolocation data for an IP address. To interact with it from your server, you’ll need to make HTTP requests to it using an HTTP client. For this tutorial, you’ll use the Ktor client.
Additionally, you’ll need the ability to parse JSON responses from IP-API. Parsing and marshalling JSON data is a part of data serialization. Kotlin has an excellent first-party library, kotlinx.serialization, to help with it.
The process of detecting the client’s location will look like this:
Adding Kotlinx Serialization and Ktor Client
The kotlinx.serialization library requires a compiler plugin as well as a support library.
Add the compiler plugin inside the plugins of the build.gradle.kts file:
plugins {
// ...
kotlin("plugin.serialization") version "1.6.10"
}
Then add these dependencies to interop with it using Ktor:
dependencies {
// ...
implementation("io.ktor:ktor-client-core:$ktorVersion")
implementation("io.ktor:ktor-client-cio:$ktorVersion")
implementation("io.ktor:ktor-client-serialization:$ktorVersion")
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
}
Here’s a description of these artifacts:
-
io.ktor:ktor-client-coreprovides core Ktor client APIs. -
io.ktor:ktor-client-cioprovides a Coroutines-based Ktor client engine. -
io.ktor:ktor-client-serialization,io.ktor:ktor-serialization-kotlinx-jsonandio.ktor:ktor-client-content-negotiationprovide APIs to serialize request/response data in JSON format using thekotlinx.serializationlibrary.
Using Ktor Client
So far you’ve used Ktor as an application server. Now you’ll use the other side of Ktor: an HTTP client.
First, create a data class to model the responses of IP-API. Create a file named IpToLocation.kt, and add the following code to it:
package com.yourcompany.android.serverlesskt import kotlinx.serialization.Serializable @Serializable data class LocationResponse( val country: String, val regionName: String, val city: String, val query: String )
Then, create a function that sends an HTTP request to IP-API with the client’s IP address. In the same file, add the following code:
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.request.*
/**
* Specifies which fields to expect in the
* response from the API
*
* More info: https://ip-api.com/docs/api:json
*/
private const val FIELDS = "country,regionName,city,query"
/**
* Prefix URL for all requests made to the IP to location API
*/
private const val BASE_URL = "http://ip-api.com/json"
/**
* Fetches the [LocationResponse] for the given IP address
* from the IP to Location API
*
* @param ip The IP address to fetch the location for
* @param client The HTTP client to make the request from
*/
suspend fun getLocation(ip: String, client: HttpClient): LocationResponse {
// 1
val url = buildString {
append(BASE_URL)
if (ip != "localhost" && ip != "_gateway") {
append("/$ip")
}
}
// 2
val response = client.get(url) {
parameter("fields", FIELDS)
}
// 3
return response.body()
}
getLocation fetches the location data for an IP address using IP-API. It uses an HttpClient supplied to it to make the HTTP request.
First, it constructs the URL to send the request to. Second, it adds FIELDS as a query parameter to the URL. This parameter tells IP-API which fields you want in the response (learn more here). Finally, it sends an HTTP GET request to the constructed URL and returns the response.
Fetching Location Data
To use getLocation, you must create an instance of the Ktor HTTP client. In the Application.kt file, add the following code above main:
import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation as ClientContentNegotiation
import io.ktor.serialization.kotlinx.json.*
val client = HttpClient(CIO) {
install(ClientContentNegotiation) {
json()
}
}
This not only creates an HttpClient, but it also adds a Ktor feature ContentNegotiation (aliased as ClientContentNegotiation to avoid import collision with the server feature of the same name) for JSON serialization/deserialization.
Then, add a route to your server to fetch the location data. In the routing block, add the following route:
get("/location") {
val ip = call.request.origin.remoteHost
val location = getLocation(ip, client)
call.respond(location)
}
Note this route responds with an object of type LocationResponse, which should be deserialized to the JSON format before sending it to the client. To tell Ktor how to deal with this, install the server-side ContentNegotiation plugin.
First, add the following dependency in the build.gradle.kts file:
implementation("io.ktor:ktor-server-content-negotiation:$ktorVersion")
In the Application.kt file, modify the configuration block for embeddedServer by adding the following code:
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation as ServerContentNegotiation
val server = embeddedServer(Netty, port=8080) {
install(ServerContentNegotiation) {
json()
}
// ...
}
Finally, restart the server and use curl to send a request to the “/location” route. You’ll see a response like this:
➜ ~ curl -X GET "http://0.0.0.0:8080/location"
{
"country":"<country>",
"regionName":"<state>",
"city":"<city>,
"query":"<ip>"
}
➜ ~
That’s it for your back-end API! So far you’ve built three API routes:
-
/: Returns “Hello, world!”. -
/ip: Returns the client’s IP address. -
/location: Fetches the client’s IP geolocation data and returns it.
The next step is to containerize the application to deploy it on Cloud Run.


