NestJS Code Style
Tools
- ESLint for linting, Prettier for formatting.
- Run linting before committing (
npm run lintor the project equivalent).
Principles
- Prefer simple, explicit, maintainable solutions over unnecessary abstractions.
- Prefer the simplest structure that satisfies the requirement.
Naming conventions
Symbols
- Functions and variables:
camelCase. - Classes, components, types, interfaces, enums:
PascalCase. - Descriptive names in English; avoid unnecessary abbreviations.
Directories
- Lowercase
kebab-case; directories represent architectural responsibility when applicable:constants/,controllers/,decorators/,dto/,entities/,filters/,guards/,jobs/,repositories/,services/,strategies/,types/,utils/.
Files
- Lowercase
kebab-casewith the most specific responsibility suffix:.controller.ts,.service.ts,.repository.ts,.guard.ts,.filter.ts,.decorator.ts,.dto.ts,.entity.ts,.strategy.ts,.processor.ts,.scheduler.ts,.constant.ts,.util.ts,.module.ts,.spec.ts, andindex.tsfor barrels. - Examples:
user-profile.controller.ts,orders.service.ts,create-user.dto.ts,jwt-auth.strategy.ts,app.module.ts. - File names must remain meaningful without opening the file; do not encode implementation details or arbitrary abbreviations.
General conventions
- Functions have a clear, well-defined responsibility.
- Review existing components and modules before implementing new functionality; follow established patterns.
- Do not assume a library is available: check
package.jsonfirst, and do not add dependencies without a clear reason. - No emojis inside source-code files.
Comments
Readability first: names carry intent. Before adding a comment, make the code express the idea itself — extract to a function with a descriptive name (
hasExpiredLicense, not// verifica si expiro). The comment is the last recourse, only when the why cannot be carried by the code.JSDoc/TypeDoc are always English: they document the public contract, not the file's prose, so surrounding Spanish inline comments do not change the language of API docs. In a mixed file, JSDoc stays English.
Do not mirror doc blocks from Spanish codebases; write the contract in English from scratch.
Avoid:
/** Obtiene el usuario por id y devuelve su perfil. */ getUserProfile(id: string): Promise<UserProfile>Keep:
/** Returns the user profile for the given id. */ getUserProfile(id: string): Promise<UserProfile>Inline
//comments stay Spanish and only explain the why (domain rationale, ordering traps); never restate the what line by line.
Imports
- Use relative imports within a module (
../module,../../feature/user) so each file is self-contained and moves with its module. - Use the project's path alias (
@/,@app/) for anything that crosses a service or module boundary, where a relative path would be deep and fragile (../../../shared).
TypeScript types
- Place
type,interface, andenumdefinitions inside the appropriatetypes/directories. - Do not colocate shared types with the classes that consume them when a
types/location exists.
Enums
- When a value exists across multiple sources (TypeScript, database, seeds, contracts), keep all representations synchronized: update all sources in the same change.
Constants
- Group related constants together using a class with static members or an
as constobject with a derived type. - Avoid unrelated standalone exports when constants belong to the same domain.
Repositories
- Repository methods express business intent, not the generic ORM API:
prefer
findResumable,applyTerminaloverfindOneWhere.... - Repositories hide persistence implementation details from the application and domain layers.
Separate the call from its interpretation
An awaited (or otherwise meaningful) call that feeds a validation, a default, or a derivation should first be assigned to a variable named after what it returns; the validation/assignment happens on that variable in a separate statement. Do not fuse the call into
??,||,===, or an inline argument.Avoid:
const isNewUser = (await this.redisService.sadd(usersKey, String(params.userId))) === 1; const userId = (await this.redisService.get(usersKey)) ?? 1;Keep:
const insertCount = await this.redisService.sadd(usersKey, String(params.userId)); const isNewUser = insertCount === 1; const existing = await this.redisService.get(usersKey); const userId = existing ?? 1;The raw result stays available for inspection and several derivations (log + decision, branching), and the intent reads from the derived names. A
??/||default is only honest once it sits on an assigned raw result; a default applied to a coded count (SADD returning 1 or 0) usually masks the wrong thing, since that call never returnsnull.Do not stretch this into ceremony: a call already stored in its own variable before a simple check needs no extra indirection. The rule targets calls embedded in a compound expression.