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
- 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.
- 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.
- 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.
- 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.
- 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
- 6Singletons through the module cache"One instance" means one per module cache: count the caches before you count the instances.
- 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.
- 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.
- 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
- 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.
- 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
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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
- 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.
- 20Explicit state machines and guarded transitionsOne table decides which moves exist; the guarded write decides who makes each one.
- 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.
- 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().
- 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.
- 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
- 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.
- 26Render props, higher-order components and custom hooksHooks put shared logic inside your component; higher-order and render-prop components put it around yours.
- 27Controlled and uncontrolled component APIsvalue means the parent owns it, defaultValue means the component does, and the owner never changes while it lives.
- 28Container and presentational split, and its modern formContainers load and decide; presentational components render props and report events.
- 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.
- 30Headless components: behaviour and ARIA without markupThe library owns state, keys and ARIA; you own every element, and your props go through its getter.
- 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
- 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.
- 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.
- 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.
- 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.
- 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.
- 37Clean architecture layers in a NestJS featureEvery way in calls the use case; put the rules and the transaction where every way in passes.
- 38Composing resilience policies: order is behaviourWrite the stack outside in; each layer sees only what the layer inside it hands back.