client- Next.js frontend (active)nestjs- NestJS backend (active)server- Old Koa.js backend. Still used for legacy endpoints and TypeORM entities
New features should be implemented in nestjs. If changes need to be made to functionality in server/, migrate it to nestjs first (exception: small critical hotfixes). Only allowed to modify TypeORM entities in server
| Alias | Resolves To | Used In |
|---|---|---|
@entities/* |
server/src/models/* |
nestjs |
@client/* |
client/src/* |
client |
| Type | Pattern | Example |
|---|---|---|
| Component | PascalCase.tsx | CourseCard.tsx |
| Hook | camelCase.ts | useExpelledStats.ts |
| Service | kebab-case.ts | courses.service.ts |
| DTO | kebab-case.dto.ts | create-course.dto.ts |
| Test | *.test.ts(x) | auth.service.test.ts |
| Module | kebab-case.module.ts | courses.module.ts |
| Entity | camelCase.ts | course.ts |
| CSS Module | *.module.css | Card.module.css |
- Prefer
unknownoveranyfor truly unknown types - Use utility types (
Pick,Omit,Partial) instead of manual type construction - Specify return types for public functions explicitly
- Use
readonlymodifier for data that should not be mutated - Treat function arguments as immutable
- Use
constby default,letonly when reassignment is needed - Prefix unused variables with underscore:
_unused - Return early to reduce nesting depth
- Use
async/awaitover.then()chains - Use
Promise.all()for parallel async operations - Order imports: external packages, internal aliases, relative imports
- Use path aliases instead of deep relative imports
modules/<Feature>/
components/ # Feature-specific UI
hooks/ # Feature-specific hooks
pages/ # Page components
services/ # API wrappers
types.ts # Feature types
index.ts # Barrel exports
- Use functional components with hooks exclusively
- Export as named exports (not default)
- One component per file as primary export
- Use
React.memo()for components with expensive renders - Keep components pure when possible
- Define props with
type, notinterface
- Keep
pages/*.tsxas thin wrappers only - Compose providers and render single module page component
- Delegate all logic to module components
- Minimize
useEffect- use only when truly necessary - Derive computed values instead of storing in state
- Lift state up rather than prop drilling
- Use context for cross-cutting concerns
- Use hooks from
react-useorahooksbefore writing custom - Use
useRequestfromahooksfor API calls - Keep custom hooks focused and simple
- Initialize clients at module level (singleton pattern)
- Use OpenAPI client for
/api/v2/*, axios services for legacy/api/*
- Use
Form.useForm<T>()with typed form values - Use
Form.Itemwithrulesfor validation - Use
theme.useToken()for theme colors - Use
clsxfor conditional class composition
- Use CSS modules exclusively:
*.module.css - Co-locate styles with components
- Do not use styled-jsx (deprecated)
nestjs/src/<domain>/
<domain>.module.ts # Module definition
<domain>.controller.ts # HTTP endpoints
<domain>.service.ts # Business logic
dto/
index.ts # Barrel exports
<entity>.dto.ts # Response DTO
create-<entity>.dto.ts # Input DTO with validation
update-<entity>.dto.ts # Partial input (extends create)
- Add
@ApiTags('domain')to controller class - Add
@ApiOperation({ operationId: 'verbNoun' })to every endpoint - Add
@ApiOkResponse({ type: DtoClass })for type safety - Use
ParseIntPipefor numeric params - Transform entities to DTOs before returning
- Keep controllers thin - delegate business logic to services
@UseGuards(DefaultGuard)- authenticated users@UseGuards(DefaultGuard, RoleGuard)+@RequiredRoles([...])- role-based- Use
Role.Admin,CourseRole.Managerenums, not strings
- Inject repositories:
@InjectRepository(Entity) - Use
findOneOrFail/findOneByOrFailwhen entity must exist - Keep services focused on single domain
- Response DTO: constructor takes entity, maps to properties
- Input DTO: validation decorators (
@IsNotEmpty,@IsString, etc.) - Update DTO:
extends PartialType(CreateDto) - All properties need
@ApiProperty()for OpenAPI
- Include context in error messages:
Entity with id ${id} not found
All entities live in server/src/models/. NestJS imports via @entities/* alias.
- Keep both relation property and
fieldIdcolumn
- Register migrations in
server/src/migrations/index.ts - Export all entities from
server/src/models/index.ts
- Unit tests:
{source}.test.ts(x)next to source file - E2E tests:
client/specs/*.spec.ts(Playwright) - Never use separate
__tests__directories for new code
describeas noun/situation:describe('AuthService')itshould describe behavior:it('should return null when not found')- Group related tests with nested
describeblocks - Extract shared setup to
beforeEach - Extract shared mock data to reusable constants
- Each test must be independent and not rely on others
- Reset mocks and state in
beforeEach - Ensure tests are deterministic (no random values)
- Assert full object shapes over field-by-field checks
- Use
expect.objectContaining()for partial matching - Test both success and error paths
- Test edge cases: empty arrays, null values, boundaries
- Use
await expect().rejects.toThrow()for error cases - In React: use
findBy*for first async query, thengetBy*for rest - Keep only ONE assertion inside
waitForcallback
- Use
Test.createTestingModule()for setup - Mock repositories via
getRepositoryToken(Entity) - Mock services with
jest.fn()methods
- Use
@testing-library/reactutilities - Query by role/text over test IDs
- Test user behavior, not implementation
// Correct
const mockData = { id: 1, name: 'Test' } as User;
// Avoid
const mockData = { ... } as unknown as User;