What you'll learn
Architecture controls dependency direction and the cost of change. Build use cases around domain language, express infrastructure as ports and adapters, and wire everything once in a composition root.
By the end of this lesson, you'll be able to:
- Define layer responsibilities
- Apply dependency inversion
- Use explicit dependency injection
- Choose modular-monolith boundaries
Core mental model
Node.js becomes easier when you separate the JavaScript language from the runtime and the operating-system capabilities it exposes. Use this table as a decision guide.
| Concept | What it means | Decision rule |
|---|---|---|
| Use case | Application behavior coordinating domain and ports | Name it after user intent, not framework mechanics |
| Port | Interface required by inner code | Define it where it is consumed |
| Composition root | The single place constructing the graph | Keep new and configuration outside business logic |
Professional workflow
Build and verify Node.js programs from the terminal in small, observable steps.
- Define the application architecture boundary: inputs, outputs, invariants, ownership, and expected failures.
- Design the data or message contract before choosing implementation details.
- Implement the smallest correct path with dependencies passed explicitly.
- Add validation, failure translation, cleanup, and concurrency behavior.
- Verify the boundary with realistic data and at least one adversarial case.
- Measure or observe the behavior before optimizing or extracting abstractions.
Keep the feedback loop short
Guided code lab
Wire ports to adapters
The use case remains independent of Express, PostgreSQL, Redis, and the system clock.
const taskRepository = createPostgresTaskRepository({ pool });
const publishEvents = createQueuePublisher({ queue });
const createTask = makeCreateTask({
tasks: taskRepository,
publishEvents,
clock: systemClock,
ids: uuidIds,
});
const app = createHttpApp({ createTask });Production practice
Contract
Dependencies point inward: transports translate, use cases coordinate, domain rules decide, and adapters perform I/O.
Verification
Unit-test use cases with fakes, contract-test every adapter, and add architecture checks for forbidden imports or cycles.
Operations
Keep modules independently observable and configurable, define ownership, and document cross-module events and failure modes.
Common failure mode
Independent workshop
Refactor the API into a modular monolith with task and identity modules.
Your finished workshop must include:
- Module map
- Use-case services
- Ports beside consumers
- Composition root
- Adapter contract tests
- Dependency rule
Definition of done
Recap & quick check
Key takeaways
- Architecture manages change
- Dependencies point inward
- DI is explicit construction
- Ports express needs
- Start with a modular monolith
Quick check
1. Where should a port be defined?
2. What belongs in the composition root?
3. Why prefer a modular monolith first?
Next: Performance, Memory & Profiling