Skip to main content

Code Style

CORTEX follows consistent coding conventions across all packages.

TypeScript

General Rules

  • Use TypeScript strict mode
  • Prefer const over let
  • Use explicit return types for functions
  • Avoid any — use unknown if type is truly unknown

Naming Conventions

TypeConventionExample
ClassesPascalCaseUserService
InterfacesPascalCaseCreateUserDto
FunctionscamelCasegetUserById
VariablescamelCasecurrentUser
ConstantsSCREAMING_SNAKEMAX_RETRIES
EnumsPascalCaseUserStatus
Enum valuesSCREAMING_SNAKEACTIVE
Fileskebab-caseuser-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

  1. External modules (node_modules)
  2. Internal modules (@nestjs, @prisma)
  3. Absolute imports from project
  4. 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';