Backend Technologies¶
The fleet is polyglot by fit: Go where proxying, resilience and event throughput dominate; TypeScript where auth tooling, fast-iterating rules and query-shaped work dominate. Both sides implement ports and adapters, but their physical layouts differ by accepted design: TypeScript uses the layered service layout, three Go services use the single core package layout, and Measurement and the gateway are documented exceptions.
Go side¶
api-gateway, passport-service, fridge-service, shopping-list-service, measurement-pipeline.
Go¶
What: Statically typed, compiled language with goroutine-based concurrency.
Why: Go compiles to a self-contained binary and supports small container images, which helps an eight-service fleet run on a laptop and in Kubernetes. Exact image size and startup time are build measurements rather than language guarantees. Its explicit error handling suits the services where correctness under failure is the point: the gateway (proxying), the passport ACL (circuit breakers toward flaky external sources), the fridge and shopping-list folds (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, auth, CORS, and later 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 for auditable queries that require Brand scope explicitly. Migrations run per service schema via embedded goose migrations.
franz-go¶
What: A pure-Go Kafka client.
Why: Topic design, partitioning keys, consumer groups and delivery semantics are core to how the event backbone behaves. 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 reduces 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, and Brand Analytics implements dashboard-shaped ingest and query use cases. TS iterates fastest there, and it is the same language as the SvelteKit SPA. Cross-service types still come from generated contracts; same-language services do not share domain or application types.
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. It was chosen over NestJS to preserve that boundary: less framework in the architecture.
Kysely¶
What: A type-safe SQL query builder over the pg driver.
Why: Kysely keeps queries explicit while the TypeScript compiler checks tables, columns and result types. Unlike sqlc, it builds queries in application code rather than generating code from SQL. No schema DSL competes 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 native librdkafka engine with a KafkaJS-compatible API.
Why: The repository selected Confluent's librdkafka-backed client for consumer-group and broker compatibility requirements. Maintenance status changes over time and must be verified from the upstream projects when dependencies are reviewed. The native module adds image complexity.
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.