Пређи на главни садржај

Full-Stack Kotlin Frontend

When building the Borrowd web app, I wanted to avoid context-switching between a backend language (like Java) and a frontend ecosystem (like TypeScript). Instead, I chose to build a Full-Stack Kotlin architecture. This lets me use pure Kotlin for database queries, API routing, business logic, React components, and CSS-in-JS styles.

The UI is a responsive, map-centric web application built with Kotlin/JS, React, and MapLibre GL JS, tightly integrated with the Ktor backend through a shared data model.

Map-Centric UX

Just like modern ride-sharing or micro-mobility apps, the user experience centers around an interactive map canvas. The frontend queries dynamic item listings from the backend based on visible map bounds and renders markers seamlessly in WebGL using MapLibre.

Why Full-Stack Kotlin?​

Building the entire stack in one language provides massive productivity wins:

  1. End-to-End Type Safety: A field renamed in the :shared module immediately triggers compiler errors in both the Ktor backend controller and the React frontend component. There is no guessing if the API matches the client.
  2. Type-Safe React Builder DSLs: Instead of JSX templates, Kotlin React uses type-safe HTML/CSS builders. The compiler catches missing props, typos, and invalid child elements before the code even runs.
  3. Zero-Bundle Bloat: Component logic is structured cleanly into feature modules matching the backend structure (items/, borrows/, profile/), ensuring high maintainability.

Shared Multiplatform DTOs​

In traditional web apps, developers manually duplicate API DTOs in TypeScript and Java/Kotlin, leading to drift, field mismatches, and runtime serialization bugs. I eliminated this friction entirely using a root :shared Kotlin Multiplatform module.

This module contains:

  • Domain DTOs: ItemDto, UserDto, BorrowRequestDto.
  • State Enums: BorrowStatus, ItemCondition.
  • API Endpoint Contracts: Strongly typed route paths.

Here is an example of a shared DTO that compiles to JVM Bytecode for Ktor and JS definitions for the React frontend:

@Serializable
data class ItemDto(
val id: String,
val title: String,
val condition: ItemCondition,
val ownerId: String
)

enum class ItemCondition {
NEW, GOOD, FAIR, POOR
}

Type-Safe Theming​

I built theming directly into the frontend architecture from day one using kotlin-emotion (Emotion CSS-in-JS wrappers for Kotlin).

Instead of relying on fragile CSS strings, I define my design tokens (color palettes, surface fills, border radii, elevation shadows, and typography scales) as strongly typed Kotlin constants:

object Tokens {
val colorPrimary = Color("#4F46E5")
val radiusLg = 8.px
}
Dynamic Theme Switching

Theme switching is built into the root component tree context. Toggling dark mode seamlessly updates emotion theme tokens across all UI components without page reloads, and kotlin-emotion ensures that only the exact CSS rules required by rendered components are injected.