Läkare (Familjeläkaren) // Läkare.se · Swedish telemedicine platform
A Swedish telemedicine platform rebuilt on a modern TypeScript stack, with BankID and EHR integration from scratch
Läkare is a Swedish telemedicine platform connecting patients with doctors for remote care: drop-in consultations, prescription renewals, medical certificates, and specialist referrals. It serves two audiences from one system — patients requesting care through a web platform and a native mobile app, and doctors/clinic staff triaging, claiming, and resolving those requests through a shared real-time queue, chat, and clinical documentation workflow.
Operating inside the Swedish healthcare system means integrating with two pieces of infrastructure that don’t exist in most other markets: BankID, Sweden’s national electronic ID scheme used for authentication instead of passwords or OAuth for the vast majority of users, and Webdoc, a third-party EHR that clinics use as their system of record — every doctor-signed consultation gets written into Webdoc as a real clinical journal entry, not just stored in Läkare’s own database.
The product surface
The backend is organized as a modular monolith with an explicit hexagonal composition root rather than a traditional MVC layering — a small app/ composition root wires the server and router, a routers/ layer aggregates around 14 domain routers into one typed router, and modules/ holds one folder per bounded context.
Backend rewrite
A full backend rewrite: Spring Boot/MySQL → Bun/Hono/oRPC/Drizzle/PostgreSQL, replacing the traditional MVC layering with a ports-and-adapters monolith.
BankID from scratch
A custom Better Auth plugin implementing Sweden’s BankID protocol from scratch — mutual-TLS relying-party client, QR code generation, encrypted server-side transaction state, per-browser/per-device deep-link handling.
Webdoc EHR integration
A production integration with Webdoc, including a compare-and-swap idempotency mechanism that makes a multi-step external booking→visit→sign workflow safely resumable across retries and crashes.
Real-time queue & chat
A real-time doctor queue and chat system built on raw Bun WebSockets, with no third-party realtime service, shared across web and mobile.
Signing-journal workflow
Doctors author, autosave, and sign clinical records, with the sign/resign business rules extracted into a single framework-free, unit-tested package shared by both frontends.
Checkarna
A family of roughly 15 self-triage “fast-track” health checks, each defined almost entirely as config against two shared engines rather than as one-off features.



Decisions, not just dependencies.
The backend is a modular monolith with an explicit hexagonal composition root: a small app/ composition root wires the server and router, a routers/ layer aggregates around 14 domain routers into one typed router, modules/ holds one folder per bounded context, platform/ holds infrastructure adapters, and shared/ holds cross-module pure helpers. The composition root instantiates every infrastructure adapter exactly once as a port-typed singleton, then wires each application service with only the ports it actually needs — there’s no DI framework; dependency injection happens through plain closures, which keeps every service’s true dependency surface visible and trivially mockable in tests.
What is shared across every frontend generation, uniformly, is the backend’s oRPC router type — not a runtime package, a TypeScript type — imported by both the web app and the mobile app and paired with a platform-appropriate transport: browser cookies for web, a manually-threaded secure-store cookie for React Native. The mobile app is Expo Router-based, with authenticated screens split behind role guards into a patient-facing tab navigator and a doctor/admin-only stack — all inside the same consumer app binary, gated by role rather than shipped as a separate build.
What was hard, and how we shipped it.
- 01
A modular, ports-and-adapters TypeScript backend
A backend migrated from a 9,300-line Java monolith to a modular, ports-and-adapters TypeScript backend built for the iteration speed the product actually needed.
- 02
A from-scratch BankID implementation
Handling the full range of device, browser, and app-lifecycle edge cases Sweden’s national ID scheme requires — mutual TLS, encrypted transaction state, live rotating QR codes, and both same-device and cross-device flows.
- 03
A resumable, idempotent EHR integration
A Webdoc integration that survives crashes and retries without duplicate clinical records, while honestly documenting the one residual gap that can’t be closed client-side.
- 04
A real-time queue and chat layer
Built on native Bun WebSockets with zero third-party realtime dependency, tuned specifically for mobile network conditions.
- 05
One shared source of truth for clinical rules
Sign/resign readiness, status transitions, and ownership live in one framework-free, tested package across three codebases, so doctors get identical validation messages regardless of platform.
- 06
A config-driven self-triage product line
Checkarna, where adding a new health check is a data file and one registration line, not a new feature build.
Looking at a project that sits at this kind of seam?
Bring us the architecture, the constraints, and the ship date. We will bring the rest.

