Kotlin + TypeScript RPC — A code-first RPC framework that generates type-safe TypeScript clients from Kotlin interfaces.
Define your API as Kotlin interfaces → run the generator → get a fully typed TypeScript client with support for queries, mutations, and real-time subscriptions over WebSockets.
- Code-first — Define RPC interfaces in Kotlin, generate TypeScript automatically
- Type-safe — Full type safety on both ends (Kotlin ↔ TypeScript)
- HTTP + WebSocket — Queries/mutations over HTTP POST, subscriptions over WebSocket
- Sealed classes → Discriminated unions — Kotlin sealed hierarchies become TypeScript unions with type guards
- Subscriptions — Kotlin
Flow→ TypeScriptAsyncIterablewith automatic WebSocket management - Auth-agnostic — Generic
@RpcContextannotation lets you inject any request context (JWT, sessions, etc.)
┌─────────────────────────────────────────────────────┐
│ Your Project │
│ │
│ ┌─────────────┐ ┌──────────┐ ┌────────────┐ │
│ │ rpc-protocol│◄───│ your │───►│ rpc-gen │ │
│ │ (framework) │ │ schema │ │ (codegen) │ │
│ └──────┬──────┘ └──────────┘ └─────┬──────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────┐ ┌──────────────┐ │
│ │ Ktor Server │ │ rpc.ts │ │
│ │ POST /rpc │◄────────────────►│ (generated) │ │
│ │ WS /rpc/ws │ HTTP / WS │ │ │
│ └─────────────┘ └──────────────┘ │
│ + client-runtime │
└─────────────────────────────────────────────────────┘
kts-rpc/
├── rpc-protocol/ # Core: Ktor HTTP & WebSocket RPC handlers
├── rpc-gen/ # Core: TypeScript code generator
├── client-runtime/ # Core: TypeScript client (runtime.ts, rpc-proxy.ts, websocket-client.ts)
├── examples/
│ ├── schema/ # Example: Kotlin RPC interfaces & DTOs
│ ├── ktor-server/ # Example: Ktor backend with JWT auth
│ └── frontend/ # Example: Vite + TypeScript frontend
└── doc/ # Documentation
Using JitPack (recommended):
// settings.gradle.kts
repositories {
maven("https://jitpack.io")
}
// your-schema/build.gradle.kts
dependencies {
implementation("com.github.planerist.ktsrpc:rpc-protocol:v0.1.4")
implementation("com.github.planerist.ktsrpc:rpc-gen:v0.1.4")
}Or copy modules — clone this repo and copy rpc-protocol/ and rpc-gen/ into your Gradle project.
// your-schema/src/.../MyServiceRpc.kt
@Serializable
data class Greeting(val message: String)
interface GreeterServiceRpc : Rpc {
suspend fun greet(name: String): Greeting
suspend fun greetMe(@RpcContext ctx: MyContext): Greeting
}Add the generator task to your schema module's build.gradle.kts:
dependencies {
implementation(project(":rpc-protocol"))
implementation(project(":rpc-gen"))
}
tasks.register<JavaExec>("generateTsRpc") {
dependsOn("assemble")
classpath = sourceSets["main"].runtimeClasspath
mainClass.set("net.planerist.ktsrpc.gen.RpcExporter")
args = listOf(
"--output", "../frontend/src/api/rpc.ts",
"com.example.GreeterServiceRpc",
"com.example.TodoServiceRpc",
)
}Run: ./gradlew your-schema:generateTsRpc
See examples/ktor-server/src/.../RpcConfig.kt for the full example. Key setup:
val rpcCallHandler = RpcCallHandler<MyContext>(
contextAnnotation = RpcContext::class,
jsonSerializer = json,
rpcImpls = mapOf(GreeterServiceRpc::class to GreeterService())
)
routing {
post("/rpc") { /* handle HTTP RPC */ }
webSocket("/rpc/ws") { webSocketHandler.handle(this) }
}Copy the 3 files from client-runtime/ into your frontend project, then:
import { WebSocketsRpcProxy } from "./api/rpc-proxy";
import { Rpc } from "./api/rpc"; // generated
const rpc = new Rpc(new WebSocketsRpcProxy("/rpc", "/rpc/ws"));
const greeting = await rpc.greet("World");
// Subscriptions
for await (const todos of rpc.subscribeTodos()) {
renderTodos(todos);
}# Backend
./gradlew examples:ktor-server:run
# Frontend (separate terminal)
cd examples/frontend
npm install
npm run devOpen http://localhost:5173 — login, try the greeter, add todos with live updates, join the chat room.
| Kotlin | TypeScript |
|---|---|
String, Int, Long, Double, Boolean |
string, number, number, number, boolean |
List<T>, Set<T> |
readonly T[] |
Map<K, V> |
Record<K, V> |
T? (nullable) |
T | null |
@Serializable data class |
interface |
@Serializable enum class |
type (string union) |
sealed interface/class |
Discriminated union + type guards |
Flow<T> (return only) |
AsyncIterable<T> |
See RELEASING.md for how to publish a new version.
Apache License 2.0 — see LICENSE.