POST
Ktor vs Axios for Kotlin/JS remote calls
For the vast majority of Kotlin/JS applications, you need to do remote calls. You can perform your calls from Kotlin/JS using the fetch API, but using a third party library simplifies the code. I describe in this post the pros and cons of two natural solutions.
Should you use Ktor-client and Axios for your Kotlin/JS calls?
Well, it depends if you care about the distribution size of your app.
You can see the code on my todokotlin project.
Ktor-client, the obvious choice BUT…
Ktor client is a subproject of Ktor, a successful project with 5k stars on GitHub. You can deploy this HTTP client library in Android, JVM, iOs, and JavaScript platforms. The main contributors are Jetbrains collaborators.
It uses Coroutines to deal with asynchronous code.
The code is clean and straightforward.
is ActionRemoveTodo -> {
GlobalScope.launch {
client.delete<HttpResponse>("todo/${action.toDo.UUID}")
.displayEventualRemoteCallError(store)
}
next.next(store, action)
}
So, is Ktor-client the silver bullet for remote calls?
Well, not for the Moment. Ktor-client is composed of few libraries and has a dependency on Coroutines, which also has some dependencies.
In the end, the total JS files imported when you use Ktor-client represent few megabytes. The total JS files of TodoKotlin weigh 5.8 Mo.
How can we reduce this?
A few tools and mechanisms can be applied.
Kotlin-DCE (Dead Code Elimination)
This is a Kotlin tool that can be applied in the build process through a Gradle plugin. It takes the JS files from the project and its dependencies and tries to remove the unused code.
Webpack + Uglify
Webpack is the state of the art way of distributing JavaScript applications. By default, it assembles multiple JavaScript files in a single one. Applying the Uglify plugin in Webpack configuration, we also replace all extended functions and variables name by shorter ones.
GZIP
At runtime, we can activate on the server the compression of the content to reduce the bandwidth.
Results of todokotlin optimization with Ktor-client
Applying Kotlin-DCE to our files shrinks the files from 5.8 Mo to 2.3 Mo.
The compression of the resulting files is efficient and reduces the total weight to approximatively 325 Ko.
Unfortunately, the last optimization (Webpack + Uglify) fails due to dependencies. I didn’t investigate further this issue and stopped here my optimization.
ERROR in ./node_modules/crypto-browserify/index.js
Module not found: Error: Can't resolve 'randomfill' in '/Users/gaetan/dev/d2v/experiments/todokotlin/node_modules/crypto-browserify'
@ ./node_modules/crypto-browserify/index.js 68:9-30
@ ./data/public/ktor-ktor-utils.js
@ ./data/public/ktor-ktor-http.js
@ ./data/public/todokotlin.js
ERROR in ./data/public/kotlinx-io.js
Module not found: Error: Can't resolve 'text-encoding' in '/Users/gaetan/dev/d2v/experiments/todokotlin/data/public'
@ ./data/public/kotlinx-io.js 5687:21-45 5698:21-45
@ ./data/public/ktor-ktor-http.js
@ ./data/public/todokotlin.js
error Command failed with exit code 2.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
> Task :bundle FAILED
FAILURE: Build failed with an exception.
So our best optimization results in 325 Ko of JS files loaded on the browser.
Let’s see what we can achieve with Axios.
Axios, the JavaScript way
Axios is a trendy library in the JavaScript world. It has More than 66k stars on GitHub, and big frameworks like VueJs recommend consuming APIs through it. It is relatively small, 53 Ko, 13 Ko minified, with no dependencies (it uses the fetch API).
As it is a pure JavaScript library, you need to define external declarations to use it. For the current test, I just copy a definition from an existing project on GitHub.
Axios uses Promises to manage asynchronicity.
You need to use then
and catch
lambdas to manage responses.
Axios.delete("todo/${action.toDo.UUID}")
.catch { remoteCallError(store) }
With a little extension on Promise we can simplify the access to the AxiosError and its property:
Axios.delete("todo/${action.toDo.UUID}")
.catchAxiosError { err ->
console.log("HTTP Status code :: ${err.response?.status}")
remoteCallError(store)
}
Axios API is relatively simple but, to manage different use cases, you must configure the requests. As this is a JavaScript library, you need to use dynamic objects. Creating an ad-hoc Axios DSL library could improve this point.
val config: dynamic = jsObject()
val headers = jsObject()
headers["Content-Type"] = "text/plain;charset=UTF-8"
config.headers = headers
Axios.post<String>("todo/", data = action.text, config = config)
.then { e: AxiosResponse<String> ->
val json = e.data
val todos = Json.parse(TodoAppState.serializer(), json).todos
store.dispatch(ActionUpdateTodos(todos))
}
.catch { remoteCallError(store) }
Despite the not typed code, we can perform our remote calls in a quite compact way.
The Most exciting part of using Axios is the distribution of our code. Axios having no dependencies, the total weight of our resulting JS files is now much lighter, 3.3 Mo for the plain JS files.
Applying Kotlin-DCE plugin produces 1.2 Mo of JS files.
Even better, the Webpack with Uglify plugin now succeeds and produces a single file of 392 Ko. Applying compression on this file results in a transfer of only 86 Ko.
Conclusion
From a coder point of view, Ktor-client is more intuitive and benefits from an excellent typed Kotlin API. But, it suffers from the inadequate current Kotlin/JS tools, which impose a lot of bandwidth and useless browser parsing of unused code.
On the opposite, Axios provides a lower coding experience with a less typed API. But the distribution of the application is much more efficient and results in optimized JS files.
I hope that we’ll have some excellent distribution tools for Kotlin soon. To be efficient, Kotlin-DCE should process Kotlin code and not the resulting JavaScript. We will, then, be able to compete with the JS ecosystem.