Loading
Document technical decisions with ADRs so future engineers understand not just what was built, but why.
Six months from now, someone will look at your architecture and ask "why did we do it this way?" If the answer lives in a Slack thread, a meeting that was not recorded, or someone's head, it is effectively lost. Architecture Decision Records capture the context, reasoning, and tradeoffs behind significant technical decisions so they survive team turnover, memory fade, and organizational growth.
Not every decision needs an ADR. Writing one for "we chose camelCase for variable names" wastes everyone's time. Writing one for "we chose PostgreSQL over MongoDB" prevents the same debate from recurring every quarter.
Write an ADR when:
Skip an ADR when:
A good rule of thumb: if you spent more than an hour discussing the decision in a meeting, it deserves an ADR.
Michael Nygard's original ADR format has five sections. It has endured because it is minimal and sufficient.
Each section has a purpose:
ADRs are not write-once documents. They form an evolving record of your architecture's history.
File naming: Number them sequentially. adr-0001-use-nextjs.md, adr-0002-use-supabase.md. The number provides a chronological ordering. Never reuse numbers.
Storage: Keep them in the repository, typically in docs/adr/ or docs/decisions/. They should live next to the code they describe. They go through the same review process as code — pull requests with team review.
Superseding decisions: When a decision is reversed, do not delete or edit the original ADR. Create a new one that references it:
Update the original ADR's status to Superseded by ADR-0023. The history of why REST was originally chosen and why it was later replaced tells a more complete story than either decision alone.
Index file: Maintain a simple table of contents:
An ADR practice only works if the team actually uses it. These patterns help:
Write ADRs during the decision, not after. Writing the context and rationale while the discussion is fresh takes 20 minutes. Reconstructing it three months later takes hours and produces a worse result.
Review ADRs in pull requests. An ADR is a proposal when in "Proposed" status. Team members review the reasoning, suggest alternatives that were missed, and challenge assumptions. This is more productive than debating in a meeting because the arguments are written down and can be referenced later.
Reference ADRs in code. When someone encounters a surprising architectural choice, a comment pointing to the ADR saves them from re-investigating from scratch:
Review ADRs during onboarding. New team members should read the ADR log as part of their first week. It answers the "why" questions they would otherwise ask in every code review for the next three months.
The return on investment of ADRs is not immediate — it compounds. Each ADR saves 30 minutes of re-discussion the first time someone asks "why." Over a year, across a growing team, that compounds into weeks of saved time and significantly better decisions because the reasoning behind prior choices is accessible to everyone making new ones.
# ADR-0012: Use PostgreSQL for Primary Data Store
## Status
Accepted (2025-03-15)
## Context
We need a primary database for the DURA learning platform. The data model
includes structured curriculum content (courses, lessons, phases), user
progress tracking, and a follow graph between users. Query patterns are
predominantly reads with complex joins — a user's dashboard requires
joining progress records across courses, lessons, and phases.
We evaluated three options:
- **PostgreSQL** — Relational, strong consistency, mature JSON support,
excellent with complex joins
- **MongoDB** — Document store, flexible schema, horizontal scaling,
weaker join support
- **PlanetScale (MySQL)** — Managed MySQL, branching workflows,
good developer experience
Current scale: ~10K users, ~500K progress records. Projected 12-month
scale: ~100K users, ~10M progress records.
## Decision
We will use PostgreSQL via Supabase.
## Rationale
1. Our data is inherently relational — courses contain lessons, users
have progress per lesson, the follow graph is a many-to-many
relationship. PostgreSQL handles these joins natively.
2. Supabase provides managed Postgres with built-in auth, row-level
security, and real-time subscriptions — reducing infrastructure we
need to build and maintain.
3. At our projected scale (10M records), a single well-indexed Postgres
instance handles the load comfortably. We do not need MongoDB's
horizontal sharding for at least 2-3 years.
4. The team has deep PostgreSQL experience. MongoDB would require a
learning curve with no compensating benefit at our scale.
We rejected MongoDB because document stores optimize for denormalized
data. Our data model is normalized with many relationships — forcing it
into documents would mean either data duplication or application-level
joins, both of which are worse than native relational joins.
We rejected PlanetScale because Supabase offers a more complete platform
(auth, storage, real-time) and our team prefers PostgreSQL's feature set
over MySQL's.
## Consequences
- All application data uses PostgreSQL via the Supabase client SDK
- Schema changes require migrations (managed via Supabase CLI)
- We accept Supabase as a platform dependency — migrating away means
replacing auth, storage, and real-time in addition to the database
- If we exceed Supabase's scaling limits, we can migrate to
self-managed Postgres since Supabase uses standard PostgreSQL
- Team members need basic PostgreSQL and SQL knowledge# ADR-0023: Migrate from REST to GraphQL for Client API
## Status
Accepted (2025-09-10)
Supersedes: ADR-0008
## Context
ADR-0008 chose REST for the client API when we had 5 endpoints. We now
have 47 endpoints and clients frequently need to combine data from 3-4
endpoints in a single view, leading to waterfall requests and
over-fetching...# Architecture Decision Records
| ID | Title | Status | Date |
| ---- | ------------------------ | ------------------ | ---------- |
| 0001 | Use Next.js 15 | Accepted | 2025-01-10 |
| 0002 | Use Supabase for backend | Accepted | 2025-01-10 |
| 0008 | REST for client API | Superseded by 0023 | 2025-02-15 |
| 0023 | Migrate to GraphQL | Accepted | 2025-09-10 |// Authentication is handled client-side with Supabase Auth.
// Server-side auth uses the Supabase SSR package for cookie-based sessions.
// See: docs/adr/adr-0003-client-side-auth.md