Backend Technologies

The fleet is polyglot by fit (ADR-008): Go where proxying, resilience and event throughput dominate; TypeScript where auth tooling, fast-iterating rules and query-shaped work dominate. Both sides share the same hexagonal layout (domain / application / adapters / config), so the languages differ but the architecture reads identically.

Go side

api-gateway, passport-service, fridge-service, measurement-pipeline.

Go

What: Statically typed, compiled language with goroutine-based concurrency.

Why: Go compiles to a single static binary, which gives ~15 MB Alpine images and instant container starts — exactly what a 7-service fleet on a laptop (and later Kubernetes) needs. Its explicit error handling and lack of framework magic suit the services where correctness under failure is the point: the gateway (proxying), the passport ACL (circuit breakers toward flaky external sources), the fridge fold (event-sourced state) and the measurement pipeline (Kafka throughput).

chi

What: A router on top of net/http — handlers are plain stdlib handlers.

Why: The gateway needs route groups (the endpoint-ownership table) and middleware (request IDs, logging, panic recovery, later auth and rate limiting); chi provides both without replacing the standard library's request model. Anything written for net/http — including httputil.ReverseProxy, which powers the gateway — plugs in unchanged.

pgx + sqlc

What: pgx is the canonical Postgres driver for Go; sqlc compiles hand-written SQL files into type-safe Go functions at build time.

Why: Repositories stay explicit: the SQL that defines an aggregate's persistence is visible in queries/, and the compiler catches a renamed column before runtime. No ORM sits between the domain model and the schema — important for the append-only fridge event table and the tenant-scoped queries that ADR-001 requires to be auditable. Migrations run per service schema via golang-migrate.

franz-go

What: A pure-Go Kafka client.

Why: Part III of the course is explicitly about topic design, partitioning keys, consumer groups and delivery semantics. franz-go keeps those concepts in our code instead of hiding them behind a framework, performs well, and avoids CGo — keeping the static-binary Docker story intact (unlike confluent-kafka-go, which wraps a C library).

testify

What: Assertion helpers on top of the stdlib testing package.

Why: The stdlib runner stays in charge (no custom test framework); testify just removes assertion boilerplate.

TypeScript side

identity-service, personalization-service, brand-analytics-service.

TypeScript / Node.js

What: Typed JavaScript on the Node 24 LTS runtime.

Why: These three services are people-and-rules shaped: identity sits next to Keycloak and the consent ledger, personalization iterates on versioned screening policies, brand-analytics serves dashboard queries. TS iterates fastest there, and it is the same language as the SvelteKit SPA — verdict and profile types stay close to the code that renders them. Running both Go and TS also makes the microservice independence claim real rather than asserted.

Fastify

What: A fast, low-overhead Node web framework with first-class JSON Schema validation of requests and responses.

Why: Fastify validates routes with the same technology our event contracts use (JSON Schema), so one mental model covers both HTTP and Kafka payloads. It is plugin-based without imposing a DI framework, which keeps the hexagonal layout ours — the domain folder never imports Fastify. Chosen over NestJS for exactly that reason: less framework in the architecture.

Kysely

What: A type-safe SQL query builder over the pg driver.

Why: The TS analogue of the sqlc decision: you still think in SQL, but the TypeScript compiler checks tables, columns and result types. No schema DSL competing with the SQL migrations, no ORM entity lifecycle — repositories stay as explicit as on the Go side.

@confluentinc/kafka-javascript

What: Confluent's Node client, binding the battle-tested librdkafka engine with a KafkaJS-compatible API.

Why: The historically popular kafkajs is effectively unmaintained; Confluent's client is actively developed and production-grade. The native module costs a little image complexity, which is acceptable for getting reliable consumer groups in Node.

tsx + Vitest

What: tsx runs TypeScript directly with watch mode for development; Vitest is the test runner.

Why: Zero-config dev loop (npm run dev) and a fast, TS-native test runner that shares Vite's transform pipeline with the frontend toolchain.