ADR-0001: Backend Technology Stack

  • Status: Accepted
  • Date: 2026-09-15
  • Deciders: Backend engineering team

Context

We are building the Conduit (RealWorld) backend: a medium-sized JSON API exposing user authentication, profiles, articles, comments, favorites, and tags. The service must be maintainable, strongly typed, testable in isolation via a ports-and-adapters architecture, and deployable to a standard container platform against a relational database. We need a stack that offers first-class type safety across the entire request lifecycle, a mature HTTP layer, and a database access layer that generates types from the schema so the domain and persistence layers stay in sync. The team’s existing expertise, the size of the surrounding ecosystem, and long-term support windows are all decision factors, because the codebase is expected to live for years and be maintained by rotating contributors.

Decision

We adopt the following stack:

  • TypeScript 5 — end-to-end static typing, strict mode enabled, catching contract violations at compile time rather than runtime.
  • Node 20 (LTS) — long-term support runtime with stable performance and broad library compatibility.
  • Express 4 — minimal, battle-tested HTTP framework with a vast middleware ecosystem; the driving-adapter layer stays thin and delegates to services.
  • Prisma 5 — type-safe ORM/query builder that generates a typed client from a declarative schema, powering our repository adapters and migrations.
  • PostgreSQL 16 — robust relational database with strong constraint support for the many-to-many relationships (favorites, follows, tags) in the domain.

Alternatives Considered

  • Fastify instead of Express — faster benchmarks and built-in schema validation, but a smaller middleware ecosystem and less team familiarity. Rejected to reduce onboarding risk.
  • NestJS — batteries-included structure, but heavyweight for this scope and imposes its own DI opinions that conflict with our hand-rolled composition root.
  • TypeORM / Sequelize instead of Prisma — weaker type generation and migration ergonomics. Prisma’s generated client aligns with our type-safety goal.
  • MySQL / SQLite — SQLite is unsuitable for concurrent production writes; PostgreSQL’s richer constraint and indexing support fits the relational model best.

Consequences

  • Positive: compile-time safety from HTTP boundary to database row; generated Prisma types remove a class of drift bugs; large hiring pool; simple containerized deployment.
  • Negative: Prisma’s generated client requires a build step (prisma generate) in CI and locally; Express requires explicit typing discipline for middleware.
  • Neutral: we own the dependency-injection composition root rather than delegating it to a framework, which is by design for the hexagonal architecture.