Architecture
Once by the book.
Once across a whole system.
Move from a diagram to the consequences of a decision. See where responsibilities live in a UI and on a server, how the two talk, how the system fails, and how you would know a change was worth making.
Moving in. 48 of 48 topics have lessons; they're moving over from the old site.
Moving in 48 of 48 topics have lessons, which are moving over from the old site. Planned topics show where the catalog is going.
Client and server
The model every web application starts from, and the choices about how much the client knows.
Client–server architecture
The browser asks and the server decides: where truth lives, what to trust, and the network in between
TypeScriptGoBackend for frontend
Give one UI a server layer that reshapes the APIs behind it
TypeScriptGoREST, RPC, or GraphQL
Choose the shape of the contract between client and server
TypeScriptGoPolling, server-sent events, or WebSockets
Keep a screen current while the server’s data changes
TypeScriptGoLocal-first and sync
Let the client own a replica that works offline and reconciles with the server
TypeScriptGoFailure and evidence
Expect each part to fail in specific ways, decide what success means, and measure before and after a change.
Thinking in failure modes
For each dependency, ask what happens when it is slow, down, wrong, duplicated, or half-done
TypeScriptGoContaining failure
Keep one failing dependency from taking the rest down: timeouts, circuit breakers, bulkheads, and degraded modes
TypeScriptGoDefining success
Decide what working means before you change anything: SLIs, SLOs, and error budgets
TypeScriptGoObservability across a request
Follow one request through logs, metrics, and traces across every service it touches
TypeScriptGoBaseline before you change
Measure before and after a refactor, so you know whether it was worth doing
TypeScriptGoFitness functions
Turn an architecture’s promises into checks that run on every change
TypeScriptGoDecompose a system
Find the lines a system should split along, and recognize when a line is in the wrong place.
Decomposing a system
Split by what changes together and hide what is likely to change, rather than by technical layer
TypeScriptGoBounded contexts
Let a word like “customer” mean different things to billing and support, on purpose
TypeScriptGoWho writes this data?
Find boundaries by asking which part of the system is allowed to change each record
TypeScriptGoConway’s law and team boundaries
Systems end up shaped like the teams that build them, so draw both on purpose
TypeScriptGoSigns a boundary is wrong
Every change touches two modules, calls between them are chatty, and services must deploy together
TypeScriptGoHow big should a module or service be?
Weigh fine-grained pieces against coarse ones by change, ownership, and the cost of every call between them
TypeScriptGoOrganize an application
Where responsibilities live inside one application, on a server or in a UI, and which parts may know about which.
Layered architecture
Split routes, services, and data access into layers, and notice when the service layer becomes a junk drawer
TypeScriptGoHexagonal / ports & adapters
Keep application behavior independent of the frameworks, databases, and APIs around it
TypeScriptGoVertical slices
Group code by feature, so one change stays in one folder
TypeScriptGoPlugin architecture
Keep a small core that others extend, like Vite plugins or editor extensions
TypeScriptGoPipes and filters
Pass data through independent steps, like a build transform chain or a Unix pipe
TypeScriptGoFrontend applications
How a UI splits state, data, and rendering, and who owns each.
State ownership in a component tree
Decide which component owns each piece of state, and how it reaches the ones that read it
TypeScriptGoUnidirectional data flow
Send every change through one path: action, store, render
TypeScriptGoServer state in the client
Treat fetched data as a cache the server owns, with its own staleness rules
TypeScriptGoServer, static, or client rendering
Choose where and when each route’s HTML is produced
TypeScriptGoMicro-frontends
Split one product UI across teams and deployments, and see what the browser pays for it
TypeScriptGoFrom modular monolith to services
Build one deployment so a module could become a service later, and know when that move has earned its cost.
Modular monolith
One deployment built from modules with an owner, a public API, and boundaries something enforces
TypeScriptGoModule contracts
Expose operations and plain data, never internals, so a module’s API could survive becoming a network call
TypeScriptGoCommunication between modules
Choose direct calls or in-process events, and write each one as if it could fail or arrive late
TypeScriptGoModule-owned data in one database
Give each module its own tables and no joins across them, while still sharing one database
TypeScriptGoConsistency without a shared transaction
Plan for the day two modules can no longer commit together
TypeScriptGoExtracting a service
Find the seam, run old and new side by side, hand over the data, and keep a way back
TypeScriptGoModular monolith vs. services
Decide when one deployment is enough and when a capability has earned its own service
TypeScriptGoEvents, queues, and workflows
Work that happens later, elsewhere, or across several systems.
Event-driven architecture
Let parts of a system react to things that have already happened
TypeScriptGoWork queues and background jobs
Move slow work out of the request and run it reliably later
TypeScriptGoReceiving webhooks
Accept events from another company’s system: verify, deduplicate, and respond quickly
TypeScriptGoTransactional outbox
Save a change and the message announcing it in the same transaction
TypeScriptGoSagas and compensation
Run a multi-step workflow across services, and undo completed steps when a later one fails
TypeScriptGoData
How a backend records change, answers questions, and keeps customers apart.
CQRS
Give reads and writes different models where their needs diverge
TypeScriptGoEvent sourcing
Store changes as events, then build the views that read them
TypeScriptGoChange data capture
Stream a database’s committed changes to search indexes, caches, and other systems
TypeScriptGoMulti-tenant data isolation
Separate customers by row, schema, or database
TypeScriptGoDeploy and evolve
Choose where the code runs, and move a live system to a new shape without stopping it.
Strangler fig migration
Replace an old system one route at a time while both keep running
TypeScriptGoLong-running servers vs. functions
Choose between a process that stays up and code that runs per request
TypeScriptGoExpand and contract
Change a database schema or API without breaking the version still running
TypeScriptGoBranch by abstraction
Swap out a dependency behind a seam while still shipping from main
TypeScriptGo