DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Reactive Kafka With Spring Boot
  • Refining Your JavaScript Code: 10 Critical Mistakes to Sidestep
  • React Server Components (RSC): The Future of React
  • Building a Twilio Softphone With JavaScript, HTML, and Flask

Trending

  • From Zero to Production: Best Practices for Scaling LLMs in the Enterprise
  • Beyond ChatGPT, AI Reasoning 2.0: Engineering AI Models With Human-Like Reasoning
  • How to Practice TDD With Kotlin
  • DGS GraphQL and Spring Boot
  1. DZone
  2. Coding
  3. Languages
  4. Going Full-Stack With Kotlin/JS and Spring Boot

Going Full-Stack With Kotlin/JS and Spring Boot

In this article, explore Kotlin/JS for creating a web application that communicates with a Spring Boot backend which is also written in Kotlin.

By 
Kyriakos Mandalas user avatar
Kyriakos Mandalas
DZone Core CORE ·
Dec. 18, 23 · Tutorial
Likes (10)
Comment
Save
Tweet
Share
7.6K Views

Join the DZone community and get the full member experience.

Join For Free

In the dynamic world of web development, Single Page Applications (SPAs) and frameworks like React, Angular, and Vue.js have emerged as the preferred approach for delivering seamless user experiences. With the evolution of the Kotlin language and its recent multiplatform capabilities, new options exist that are worthwhile to evaluate.

In this article, we will explore Kotlin/JS for creating a web application that communicates with a Spring Boot backend which is also written in Kotlin. In order to keep it as simple as possible, we will not bring in any other framework. 

Advantages of Kotlin/JS for SPA Development

As described in the official documentation, Kotlin/JS provides the ability to transpile Kotlin code, the Kotlin standard library, and any compatible dependencies to JavaScript (ES5). With Kotlin/JS we can manipulate the DOM and create dynamic HTML by taking advantage of Kotlin's conciseness and expressiveness, coupled with its compatibility with JavaScript. And of course, we do have the much needed type-safety, which reduces the likelihood of runtime errors.

This enables developers to write client-side code with reduced boilerplate and fewer errors. Additionally, Kotlin/JS seamlessly integrates with popular JavaScript libraries (and frameworks), thus leveraging the extensive ecosystem of existing tools and resources.  And, last but not least: this makes it easier for a backend developer to be involved with the frontend part as it looks more familiar. Moderate knowledge of "vanilla" JavaScript, the DOM, and HTML is of course needed; but especially when we are dealing with non-intensive apps (admin panels, back-office sites, etc.), one can get engaged rather smoothly.

Sample Project

The complete source code for this showcase is available on GitHub. The backend utilizes Spring Security for protecting a simple RESTful API with basic CRUD operations. We won't expand more on this since we want to keep the spotlight on the frontend part which demonstrates the following:

  • Log in with username/password
  • Cookie-based session
  • Page layout with multiple tabs and top navigation bar (based on Bootstrap)
  • Client-side routing (based on Navigo)
  • Table with pagination, sorting, and filtering populated with data fetched from the backend (based on DataTables)
  • Basic form with input fields including (dependent) drop-down lists (based on Bootstrap)
  • Modals and loading masks (based on Bootstrap and spin.js)
  • Usage of sessionStorage and localStorage
  • Usage of Ktor HttpClient for making HTTP calls to the backend

An architectural overview is provided in the diagram below:

Architectural overviewStarting Point

The easiest way to start exploring is by creating a new Kotlin Multiplatform project from IntelliJ. The project's template must be "Full-Stack Web Application":

Project Template: Full-Stack Web Application

This will create the following project structure:

  • springMain: This is the module containing the server-side implementation.
  • springTest: For the Spring Boot tests
  • commonMain: This module contains "shared" code between the frontend and the backend; e.g., DTOs
  • commonTest: For the unit tests of the "common" module
  • jsMain: This is the frontend module responsible for our SPA.
  • jsTest: For the Kotlin/JS tests

The sample project on GitHub is based on this particular skeleton. Once you clone the project you may start the backend by executing:

 
$ ./gradlew bootRun


This will spin up the SpringBoot app, listening on port: 8090. In order to start the frontend, execute:

 
$ ./gradlew jsBrowserDevelopmentRun -t


This will open up a browser window automatically navigating to http://localhost:8080 and presenting the user login page. For convenience, a couple of users are provisioned on the server (have a look at dev.kmandalas.demo.config.SecurityConfig for details).

Once logged in, the user views a group of tabs with the main tab presenting a table (data grid) with items fetched from the server. The user can interact with the table (paging, sorting, filtering, data export) and add a new item (product) by pressing the "Add product" button. In this case, a form is presented within a modal with typical input fields and dependent drop-down lists with data fetched from the server. In fact, there is some caching applied on this part in order to reduce network calls. 

Finally, from the top navigation bar, the user can toggle the theme (this setting is preserved in the browser's local storage) and perform logout. In the next section, we will explore some low-level details for selected parts of the frontend module.

The jsMain Module

Let's start by having a look at the structure of the module:

Module structure

The naming of the Kotlin files should give an idea about the responsibility of each class. The "entrypoint" is of course the Main.kt class:

Kotlin
 
import home.Layout
import kotlinx.browser.window
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch

fun main() {
    MainScope().launch {
        window.onload = {
            Layout.init()
            val router = Router()
            router.start()
        }
    }
}


Once the "index.html" file is loaded, we initialize the Layout and our client-side Router.

Now, the "index.html" imports the JavaScript source files of the things we use (Bootstrap, Navigo, Datatables, etc.) and their corresponding CSS files. And of course, it imports the "transpiled" JavaScript file of our Kotlin/JS application. Apart from this, the HTML body part consists of some static parts like the "Top Navbar," and most importantly, our root HTML div tag. Under this tag, we will perform the DOM manipulations needed for our simple SPA.

By importing the kotlinx.browser package in our Kotlin classes and singletons, we have access to top-level objects such as the document and window. The standard library provides typesafe wrappers for the functionality exposed by these objects (wherever possible) as described in the Browser and DOM API.

So this is what we do at most parts of the module by writing Kotlin and not JavaScript or using jQuery, and at the same time having type-safety without using, e.g., TypeScript. So for example we can create content like this:

Kotlin
 
private fun buildTable(products: List<Product>): HTMLTableElement {
  val table = document.createElement("table") as HTMLTableElement
    table.className = "table table-striped table-hover"
    // Header
    val thead = table.createTHead()
    val headerRow = thead.insertRow()
    headerRow.appendChild(document.createElement("th").apply { textContent = "ID" })
    headerRow.appendChild(document.createElement("th").apply { textContent = "Name" })
    headerRow.appendChild(document.createElement("th").apply { textContent = "Category" })
    headerRow.appendChild(document.createElement("th").apply { textContent = "Price" })
    // Body
    val tbody = table.createTBody()
    for (product in products) {
    val row = tbody.insertRow()
      row.appendChild(document.createElement("td").apply { textContent = product.id.toString() })
      row.appendChild(document.createElement("td").apply { textContent = product.name })
      row.appendChild(document.createElement("td").apply { textContent = product.category.name })
      row.appendChild(document.createElement("td").apply { textContent = product.price.toString() })
  }
  document.getElementById("root")?.appendChild(table)
  return table
}


Alternatively, we can use the Typesafe HTML DSL of the kotlinx.html library which looks pretty cool. Or we can load HTML content as "templates" and further process them. Seems that many possibilities exist for this task. 

Moving on, we can attach event-listeners thus dynamic behavior to our UI elements like this:

Kotlin
 
categoryDropdown?.addEventListener("change", {
  val selectedCategory = categoryDropdown.value
    // Fetch sub-categories based on the selected category
    mainScope.launch {
    populateSubCategories(selectedCategory)
  }
})


Before talking about some "exceptions to the rule", it's worth mentioning that we use the Ktor HTTP client (see ProductApi) for making the REST calls to the backend. We could use the ported Fetch API for this task but going with the client looks way better. Of course, we need to add the ktor-client as a dependency to the build.gradle.kts file:

Kotlin
 
val jsMain by getting {
  dependsOn(commonMain)
    dependencies {
    implementation("io.ktor:ktor-client-core:$ktorVersion")
      implementation("io.ktor:ktor-client-js:$ktorVersion")
      implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
	  //... 	
  }
}


The client includes the JSESSIONID browser cookie received from the server upon successful authentication of the HTTP requests. If this is omitted, we will get back HTTP 401/403 errors from the server. These are also handled and displayed within Bootstrap modals. Also, a very convenient thing regarding the client-server communication is the sharing of common data classes (Product.kt and Category.kt, in our case) between the jsMain and springMain modules. 

Exception 1: Use Dependencies From npm

For client-side routing, we selected the Navigo JavaScript library. This library is not part of Kotlin/JS, but we can import it in Gradle using the npm function:

Kotlin
 
val jsMain by getting {
  dependsOn(commonMain)
    dependencies {
    //...
    implementation(npm("navigo", "8.11.1"))
  }
}


However, because JavaScript modules are dynamically typed and Kotlin is statically typed, in order to manipulate Navigo from Kotlin we have to provide an "adapter." This is what we do within the Router.kt class:

Kotlin
 
@JsModule("navigo")
@JsNonModule
external class Navigo(root: String, resolveOptions: ResolveOptions = definedExternally) {
    fun on(route: String, handler: () -> Unit)
    fun resolve()
    fun navigate(s: String)
}


With this in place, the Navigo JavaScript module can be used just like a regular Kotlin class.

Exception 2: Use JavaScript Code From Kotlin

It is possible to invoke JavaScript functions from Kotlin code using the js() function. Here are some examples from our example project:

Kotlin
 
// From ProductTable.kt:
private fun initializeDataTable() {
  js("new DataTable('#$PRODUCTS_TABLE_ID', $DATATABLE_OPTIONS)")
}

// From ModalUtil.kt:
val modalElement = document.getElementById(modal.id) as? HTMLDivElement
   modalElement?.let {
   js("new bootstrap.Modal(it).show()")
}


However, this should be used with caution since this way we are outside Kotlin's type system. 

Takeaways

In general, the best framework to choose depends on several factors with one of the most important ones being, "The one that the developer team is more familiar with." On the other hand, according to Thoughtworks Technology radar, the SPA by default approach is under question; meaning, that we should not blindly accept the complexity of SPAs and their frameworks by default even when the business needs don't justify it. 

In this article, we provided an introduction to Kotlin multiplatform with Kotlin/JS which brings new things to the table. Taking into consideration the latest additions in the ecosystem - namely Kotlin Wasm and Compose Multiplatform - it becomes evident that these advancements offer not only a fresh perspective but also robust solutions for streamlined development.

Kotlin (programming language) Spring Boot JavaScript Web development

Opinions expressed by DZone contributors are their own.

Related

  • Reactive Kafka With Spring Boot
  • Refining Your JavaScript Code: 10 Critical Mistakes to Sidestep
  • React Server Components (RSC): The Future of React
  • Building a Twilio Softphone With JavaScript, HTML, and Flask

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!