Skip to content

SOLID and design patterns

SOLID and design patterns in TypeScript, Node, NestJS and React, and how to tell when a pattern is over-engineering.

38 lessons

SOLID in practice

  1. 1SOLID at a glance, with LSP and ISP in TypeScriptUse the five principles as heuristics; the compiler checks shapes, so a subtype's broken promise and a fat interface's coupling are yours to see.
  2. 2Single responsibility as one reason to changeGroup code by who asks for its changes: each team's requests land in one place, and in no other team's code.
  3. 3Open/closed through the strategy patternPut each variant behind one interface and let a registry pick it: new variants arrive beside the core; a switch stays while small.
  4. 4Dependency inversion and NestJS dependency injectionDecide who owns the interface, then let Nest wire it by token; keep providers singleton, because request scope spreads up the chain.
  5. 5Composition over inheritance, and the fragile base classInherit and you depend on how the base calls itself; compose small parts, inject what varies, and extend only where the class is checked.

Patterns in your stack

  1. 6Singletons through the module cache"One instance" means one per module cache: count the caches before you count the instances.
  2. 7Wrappers: adapter, decorator, proxy, facadeAsk what a wrapper does to the interface: changes it, keeps it and adds, keeps it and guards, or simplifies; @ changes the class.
  3. 8Observer and pub/sub in Node and the frontendAn emitter calls its listeners now, in order, inside the caller's stack; a broker hands the message on and returns.
  4. 9The repository pattern over an ORMName each repository method for the question it answers, and let only the service begin and end the transaction its repositories share.

Judgment

  1. 10When a pattern is over-engineeringKeep a layer only for what it buys today; build nothing on a guess, extract at the third copy, and inline a wrong abstraction back.
  2. 11From code smell to pattern, and backName the smell by what the next change costs; move, delete or split code first, and add a pattern only when the smell keeps returning.

GoF creational and structural

  1. 12Factory, abstract factory and NestJS useFactoryPut the "which class?" decision in one function; build a family from one choice; pick per call only what really changes per call.
  2. 13A builder, and why TypeScript mostly uses options objectsName your arguments with an options object; keep a builder for objects assembled in parts, and for tests.
  3. 14An adapter behind a port you ownEverything the vendor defines stops in the adapter; count the files that use the SDK, and aim for one.
  4. 15Decorator metadata: why NestJS keeps legacy decoratorsNest injects from a list the compiler writes; when a dependency is undefined, ask which compiler built the file.
  5. 16The JavaScript Proxy: a stand-in that is not its targetA proxy is another object: every operation passes its traps, and nothing private comes with it.
  6. 17A facade: one task-shaped call over a subsystemOne method per task, the order and the wiring inside it; undo what a failed task started; keep the rules in the parts.
  7. 18Composite: treating a leaf and a group the same wayA leaf answers for itself, a container for everything inside it, and the caller never asks which one it holds.

GoF behavioral

  1. 19Command: requests as objects for undo, redo and queued jobsStore the request, not the call: what is stored can be undone, redone, logged and queued.
  2. 20Explicit state machines and guarded transitionsOne table decides which moves exist; the guarded write decides who makes each one.
  3. 21Template method, framework hooks, and the step passed as a functionThe base owns the order and calls your step; in your own code, hand the step in as a function.
  4. 22A chain of handlers, one next() at a timeOrder is behaviour: a handler sees only what ran before it, and runs only if everything before it called next().
  5. 23Iterators and generators: values made only when pulledNothing is made until something pulls; whoever stops pulling must say so, and a generator cleans up in finally.
  6. 24The rest of the GoF catalogue at a glanceKnow the handful in your stack, name the rest by the problem each solves, and use one only where that problem hurts.

React patterns

  1. 25Compound components: parts that share a Root's stateThe Root owns the state and the parts read it from context; cloneElement reaches only what the Root was handed.
  2. 26Render props, higher-order components and custom hooksHooks put shared logic inside your component; higher-order and render-prop components put it around yours.
  3. 27Controlled and uncontrolled component APIsvalue means the parent owns it, defaultValue means the component does, and the owner never changes while it lives.
  4. 28Container and presentational split, and its modern formContainers load and decide; presentational components render props and report events.
  5. 29The provider pattern: context plus a guarded hookContext carries the value, the hook guards the read, and the provider's prop is where tests swap the implementation.
  6. 30Headless components: behaviour and ARIA without markupThe library owns state, keys and ARIA; you own every element, and your props go through its getter.
  7. 31Reducers and one status for UI stateOne status holds only the screens you drew; a reducer judges each event against it.

Enterprise and backend patterns

  1. 32One unit of work per business operationCollect the whole operation's changes, then write them in one transaction; anything that commits on its own is outside the unit.
  2. 33DTOs and mappers at the boundariesName every field that leaves; a type is not a filter, and a removal list is out of date at the next migration.
  3. 34Command-query separation and CQRS-lightAsk without changing, change without asking: queries read the tables straight, and commands go through the model that keeps the rules.
  4. 35A specification is one rule with two readersWrite the rule once, as an object two readers can interpret; keep it a function until a second reader needs it.
  5. 36An anti-corruption layer translates a whole foreign modelTranslate what the foreign record means in your model, and nothing else; keep every rule where every path meets it.
  6. 37Clean architecture layers in a NestJS featureEvery way in calls the use case; put the rules and the transaction where every way in passes.
  7. 38Composing resilience policies: order is behaviourWrite the stack outside in; each layer sees only what the layer inside it hands back.