Code Style
CORTEX follows consistent coding conventions across all packages.
TypeScript
General Rules
- Use TypeScript strict mode
- Prefer
constoverlet - Use explicit return types for functions
- Avoid
any— useunknownif type is truly unknown
Naming Conventions
| Type | Convention | Example |
|---|---|---|
| Classes | PascalCase | UserService |
| Interfaces | PascalCase | CreateUserDto |
| Functions | camelCase | getUserById |
| Variables | camelCase | currentUser |
| Constants | SCREAMING_SNAKE | MAX_RETRIES |
| Enums | PascalCase | UserStatus |
| Enum values | SCREAMING_SNAKE | ACTIVE |
| Files | kebab-case | user-service.ts |
Interface vs Type
// Use interface for objects
interface User {
id: string;
email: string;
}
// Use type for unions/intersections
type Status = 'active' | 'inactive';
type UserWithRoles = User & { roles: Role[] };
Async/Await
// Good
async function getUser(id: string): Promise<User> {
const user = await this.prisma.user.findUnique({ where: { id } });
if (!user) throw new NotFoundException();
return user;
}
// Avoid
function getUser(id: string): Promise<User> {
return this.prisma.user.findUnique({ where: { id } })
.then(user => {
if (!user) throw new NotFoundException();
return user;
});
}
NestJS Conventions
Module Structure
modules/user/
├── user.module.ts # Module definition
├── user.controller.ts # HTTP layer
├── user.service.ts # Business logic
├── dto/ # Data transfer objects
│ ├── create-user.dto.ts
│ ├── update-user.dto.ts
│ └── index.ts
├── entities/ # Type definitions
└── user.service.spec.ts # Tests
Controller Pattern
@Controller('users')
export class UserController {
constructor(private readonly userService: UserService) {}
@Get()
findAll(@Query() query: PaginationDto) {
return this.userService.findAll(query);
}
@Get(':id')
findOne(@Param('id', ParseUUIDPipe) id: string) {
return this.userService.findById(id);
}
@Post()
create(@Body() dto: CreateUserDto) {
return this.userService.create(dto);
}
}
Service Pattern
@Injectable()
export class UserService {
constructor(private readonly prisma: PrismaService) {}
async findById(id: string): Promise<User> {
const user = await this.prisma.user.findUnique({
where: { id },
});
if (!user) {
throw new NotFoundException(`User ${id} not found`);
}
return user;
}
}
DTO Pattern
export class CreateUserDto {
@IsEmail()
@Transform(({ value }) => value.toLowerCase())
email: string;
@IsString()
@MinLength(8)
password: string;
@IsString()
@MaxLength(100)
firstName: string;
@IsString()
@MaxLength(100)
lastName: string;
}
Formatting
Prettier Configuration
{
"semi": true,
"singleQuote": true,
"trailingComma": "all",
"printWidth": 100,
"tabWidth": 2
}
ESLint Rules
Key rules enforced:
- No unused variables
- Explicit function return types
- No floating promises
- Consistent type imports
Error Handling
Throwing Errors
// Use NestJS exceptions
throw new NotFoundException('User not found');
throw new BadRequestException('Invalid email format');
throw new ConflictException('Email already exists');
throw new ForbiddenException('Insufficient permissions');
Custom Exceptions
export class AccountLockedException extends ForbiddenException {
constructor() {
super({
type: 'account-locked',
message: 'Account is temporarily locked',
});
}
}
Comments
When to Comment
// Good: Explains WHY, not WHAT
// Using 5 retries because the external service has occasional timeouts
const MAX_RETRIES = 5;
// Bad: States the obvious
// Loop through users
for (const user of users) { ... }
JSDoc for Public APIs
/**
* Creates a new user in the specified tenant.
*
* @param dto - User creation data
* @returns The created user
* @throws ConflictException if email already exists
*/
async create(dto: CreateUserDto): Promise<User> {
// ...
}
Testing
Test Structure
describe('UserService', () => {
// Setup
let service: UserService;
beforeEach(async () => {
// Initialize
});
// Group related tests
describe('create', () => {
it('should create a user', async () => {
// Arrange
const dto = { email: 'test@example.com' };
// Act
const result = await service.create(dto);
// Assert
expect(result.email).toBe(dto.email);
});
it('should throw on duplicate email', async () => {
// Test error case
});
});
});
Imports
Import Order
- External modules (node_modules)
- Internal modules (@nestjs, @prisma)
- Absolute imports from project
- Relative imports
// External
import { Injectable } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
// Internal packages
import { User } from '@cortex/shared-types';
// Relative
import { CreateUserDto } from './dto';