API (NestJS)
General
For new requirements, always check the documentation first. There are already established techniques or patterns for many common problems.
NestJS is agnostic in many ways, and one of them is communication. It provides wrappers (or allows us to create our own) for third-party libraries.
See: What is the NestJS Runtime - Trilon Consulting
Logging
- Only log warnings and errors by default.
- Additional logs (for debugging/profiling) should be toggleable via flags/settings.
Recommendation: Use the built-in NestJS Logger.
Services vs. Controller/Resolver
- Controller/Resolver: Represent the HTTP or GraphQL layer.
- Service: Represents the business logic layer.
Service Best Practices
- Services should be decoupled from the HTTP/GraphQL layer (e.g., avoid using
InputTypes
directly).- This allows them to be reused (e.g., in console jobs) without mocking types.
- Prefer passing the entire entity rather than just the ID.
- Services should be easily testable. (Using MikroORM with NestJS framework)
- Use Dependency Injection (DI) to control access to services and enforce clear module boundaries/interfaces.
Service FAQs
Is a service always required?
No — don’t create pass-through methods just for the sake of it.
Yes — if business logic is reused or the HTTP layer becomes messy.Can I create utility files?
Yes, as long as they are stateless helper functions.
Do not consume other services or repositories within them.noteGive them specific names — avoid generic
utils
dump folders.
Access Control Lists (ACL) / Authorization Checks
- Place ACL logic in a dedicated ACL service (e.g.,
products.acl.service.ts
) or inline in the controller/resolver for specific permission checks. - Do not perform ACL checks in shared services: Console jobs, for instance, have no authenticated user and may lead to double-checking permissions.
- ACLs should be covered by unit tests.
Avoid Using process.env
Directly
Directly accessing environment variables in the code has downsides:
- Not testable (no dependency injection)
- No central place to manage environment variables
- No validation
Exception: It's technically necessary in some cases, such as in app entry points (main.ts
, bootstrap.ts
).
Dependency Injection
Constructor-based injection is preferred over property-based injection (see: Documentation | NestJS - A progressive Node.js framework).
Use property injection only when technically necessary.
ORM / Database
Repository
Prefer repository.create
over new Entity()
since create
expects a complete data object.
Migrations
Down migrations are not required – using throw new Error("Unsupported")
is sufficient.
We do not use rollbacks; issues are resolved with a new deployment.
Data Types
- Always use
uuidv4
as the primary key whenever possible.- Prevent listing or guessing of IDs (e.g., when they are part of a URL).
- Can be generated "offline" (without a connection to the database), making the ID available in the code before insertion.
- Are globally unique, which can sometimes be helpful.
- Use
columnType: "uuid"
for UUID columns.
- Always use
columnType: text
for strings. Explanation: See Don't Do This - PostgreSQL wiki. - Prefer
jsonb
overjson
. Explanation: See 8.14. JSON Types - For timestamps, always use
columnType: "timestamp with time zone"
.
DB Defaults
Avoid using DB defaults as they are not available before inserting/updating. This also falsifies the types of the entity.
@Property({ default: false })
@Field()
visible: boolean;
@Property()
@Field()
visible: boolean;
(if GQL Field → provide default value for InputType)
@Property()
visible: boolean = false;
(if not a GQL Field)
If you are using the Upsert-Pattern, you may use DB defaults additionally
@Field()
@Property({
columnType: "timestamp with time zone",
onUpdate: () => new Date(),
defaultRaw: "now()", // fallback for upsert
})
updatedAt: Date = new Date();
GraphQL
Enum Keys = Values
Enum keys and values must be identical.
enum Category {
MAIN = "main"
SECONDARY = "secondary"
}
enum Category {
Main = "Main"
Secondary = "Secondary"
}
Reason: Admin and Site only know the GraphQL schema, where the enum keys are used. Therefore, queries and mutations also send the keys. If the values differ from the keys, this causes API errors because the wrong values are used for the enum.
Regarding naming conventions, see also TypeScript | Enums.
TypeScript number vs GraphQL Int/Float
TypeScript has only one number type, but GraphQL distinguishes between Int and Float, so the type must be specified manually, otherwise, Float is the less strict default.
class Product {
@Field(() => Int)
position: number;
@Field(() => Float)
price: number;
}