Principles for writing domain models in server-side TypeScript. Rather than class-based OOP, adopt a functional approach that makes maximum use of TypeScript's type system.
1. Domain modeling with types
Express state with a Discriminated Union, andkind unify as the discriminant.type(interface ), Companion Object, Branded Types,Readonly<>, use function property notation, and adopt a one-concept-one-file structure.
Validation library detection: of the project package.json の dependencies / devDependencies check:
zod→ validation-libraries/zod.mdvalibot→ validation-libraries/valibot.mdarktype→ validation-libraries/arktype.md
Details: domain-modeling.md
2. State transitions via functions
Express state transitions with pure functions. The function's argument type constrains the valid source state, and the return type makes the destination state explicit. Invalid transitions become compile errors.assertNever check exhaustiveness with.
Details: state-modeling.md
3. Error Handling — Railway Oriented Programming
Instead of throwing exceptions, treat errors as values with the Result type. Define error types as Discriminated Unions so that callers can handle them exhaustively.
Library detection: of the project package.json の dependencies / devDependencies check:
neverthrow→ result-libraries/neverthrow.mdbyethrow→ result-libraries/byethrow.mdfp-ts→ result-libraries/fp-ts.mdoption-t→ result-libraries/option-t.md
Details: error-handling.md
4. Defending the boundaries
Perform runtime validation on external input (API requests, DB results, file reads) using validation library schemas. Within the domain layer, trust the types. Type assertions (as) are not used. For PII fields, apply the Sensitive<T> wrapper.
Details: boundary-defense.md
5. Declarative style
Write array transformations declaratively with filter / map / reduce using the Companion Object's predicate functions. Model domain events as immutable records.
Details: declarative-style.md
6. Test Data
Test data is as const satisfies Type define with, preserving the literal type of the discriminant and preventing widening.
Details: test-data.md
About applying the principles
These are recommendations, not strict rules. You may decide according to context, but when deviating from the principles, explicitly state the reason in a comment.
Typical valid reasons for deviation:
- When an external library requires class inheritance
- When performance requirements make the cost of creating immutable data a problem
- When a different pattern has been adopted by team agreement