Skip to content

ADR-0002: Technology stack

  • Status: Accepted
  • Date: 2026-04-22

Context

kafkito is a single-binary Kafka management UI (see ADR-0001). We need a stack that:

  • Produces a small, fast, statically linked binary.
  • Provides first-class, actively maintained Kafka client support.
  • Enables a protobuf-first API layer suitable for both backend and web/mobile clients.
  • Ships a modern, maintainable SPA frontend that we can embed via //go:embed.
  • Is Cloud Foundry / Kubernetes / container-friendly out of the box (see ADR-0003).

Decision

Backend

Concern Choice Rationale
Language Go 1.26+ Single-binary, static linking, strong stdlib, mature Kafka client ecosystem.
HTTP router go-chi/chi v5 Idiomatic, stdlib-compatible, excellent middleware story.
RPC layer ~~Connect-RPC with buf (protobuf-first)~~ superseded, see Amendments 2026-09-25 and ADR-0005 HTTP/1.1, HTTP/2, gRPC-compatible, generated TypeScript client, API contract versioning.
Kafka client twmb/franz-go + kadm + kmsg + sr Best-of-breed pure-Go Kafka client; supports KIPs promptly; wire-protocol-first.
Scripting dop251/goja Sandboxed JS engine for message-filter DSL.
Config knadh/koanf YAML + env + VCAP_SERVICES layering.
Logging log/slog (stdlib) with optional go.uber.org/zap handler later if perf demands it Structured JSON logs to stdout, Cloud Foundry loggregator-friendly.
Observability OpenTelemetry + Prometheus exporter Industry standard.

Frontend

Concern Choice Rationale
Runtime Bun Fast install/build, first-class TypeScript, matches modern React tooling.
Framework React 19 Mainstream, large talent pool, stable ecosystem.
Build Vite Fast dev server, lean production bundles.
Routing TanStack Router Type-safe, data-loading built in, no server-side rendering required.
UI kit ~~shadcn/ui~~ (superseded, see Amendments 2026-09-26) + Tailwind CSS Copy-in component library (no vendor lock-in), accessible defaults.
State / data TanStack Query + ~~Connect-RPC generated client~~ (superseded, see Amendments 2026-09-25 and ADR-0005) Declarative data-fetching; protobuf types end-to-end.

Distribution

  • Single Go binary with the SPA embedded via //go:embed.
  • Distroless multi-arch container image (gcr.io/distroless/static-debian12, linux/amd64 + linux/arm64).

Consequences

Positive

  • One deployment artifact. No JVM, no sidecar node_modules.
  • Protobuf-first API ensures end-to-end type safety and easy contract evolution.

Negative

  • Bun is less mainstream in some enterprise environments than Node.js. We mitigate by producing plain static assets — the runtime is only a build-time dependency.
  • Connect-RPC is newer than plain REST + OpenAPI. Team members need to learn buf and the Connect generator.

Alternatives considered

  • Echo / Gin instead of Chi — rejected, Chi has the cleanest middleware story and is stdlib-compatible.
  • Sarama or IBM/sarama instead of franz-go — rejected, franz-go is more modern, has better performance and KIP coverage.
  • Next.js or Remix instead of Vite SPA — rejected, we do not need SSR; a SPA simplifies the //go:embed distribution model.
  • gRPC-Web / grpc-gateway instead of Connect-RPC — rejected, Connect-RPC offers a simpler protocol with identical tooling benefits.

Amendments

2026-09-25

  • Logging is log/slog only. go.uber.org/zap is not used and no zap handler is planned.
  • Observability: OpenTelemetry and Prometheus are not implemented and are not planned until there is a consumer for them. Production runs on SAP BTP Cloud Foundry, where logs are viewed via cf logs.
  • RPC layer: superseded by ADR-0005. Connect-RPC and buf are removed; the HTTP contract is the OpenAPI 3.1 spec at api/openapi.yaml, and the frontend uses TanStack Query with TypeScript types generated from that spec (this also replaces the "Connect-RPC generated client" in the State / data row).
  • Go package layout: application packages live under internal/ (no public Go API); pkg/ is reserved for code intended for external import.

2026-09-26

  • UI kit: shadcn/ui is not used. The UI is built from kafkito's own primitives in frontend/src/components/ui/, styled with Tailwind CSS v4 and the design tokens in frontend/src/index.css (this replaces the "shadcn/ui" part of the UI kit row). Radix UI headless primitives (@radix-ui/react-*, no shadcn) may be added for behaviour that is hard to get right by hand, such as focus management and collision-aware positioning; each one is wrapped in components/ui/, and kafkito paints the whole visual layer. Toasts use sonner, icons lucide-react. The rules and the component inventory are in DESIGN_GUIDELINES.md (§ 1 and § 6).
  • Kafka client: the sr package is not used; Schema Registry calls go through a small HTTP client in internal/kafka/schema_registry.go.