Code Standards
SALLY is a TypeScript-only project. These conventions keep the codebase consistent across the NestJS backend, Next.js frontend, and shared packages.
General Rules
- TypeScript everywhere — No
.jsfiles. Every source file uses.tsor.tsx. - No
console.login production code — Use the project logger (Pino) on the backend. Remove anyconsole.logcalls before submitting a PR. - Prefer named exports — Use
export functionorexport constinstead ofexport default(page components in the App Router are the one exception). - Keep functions small and focused — If a function exceeds 40-50 lines, consider splitting it.
- Import order — Organize imports in this sequence:
- External packages (
@nestjs/common,react,next/link) - Internal packages and aliases (
@sally/shared,@/lib/utils) - Relative imports (
./dto,../components)
- External packages (
Backend Conventions (NestJS)
File Naming
Use kebab-case for all files:
drivers.controller.ts
drivers.service.ts
drivers.module.ts
create-driver.dto.ts
driver.entity.tsClass Naming
Use PascalCase for classes:
export class DriversController {}
export class DriversService {}
export class CreateDriverDto {}Module Pattern
Every domain feature follows the NestJS module pattern with three core files:
src/domains/fleet/drivers/
drivers.module.ts // Module definition
drivers.controller.ts // HTTP route handlers
drivers.service.ts // Business logic
dto/
create-driver.dto.ts
update-driver.dto.tsDTO Validation
Use class-validator decorators for all request DTOs:
import { IsString, IsNotEmpty, IsOptional } from 'class-validator';
export class CreateDriverDto {
@IsString()
@IsNotEmpty()
firstName: string;
@IsString()
@IsNotEmpty()
lastName: string;
@IsOptional()
@IsString()
licenseNumber?: string;
}Swagger Documentation
Decorate every controller and endpoint with Swagger metadata:
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
@ApiTags('Drivers')
@Controller('drivers')
export class DriversController {
@Get()
@ApiOperation({ summary: 'List all drivers' })
@ApiResponse({ status: 200, description: 'Drivers retrieved successfully' })
findAll() {
return this.driversService.findAll();
}
}API Routes
All routes live under the /api/v1/ prefix. Use plural nouns for resource names:
GET /api/v1/drivers
POST /api/v1/drivers
GET /api/v1/drivers/:id
PATCH /api/v1/drivers/:id
DELETE /api/v1/drivers/:idDatabase Access
Use Prisma for all database operations. Avoid raw SQL unless there is a specific performance reason that Prisma cannot address:
// Correct
const driver = await this.prisma.driver.findUnique({
where: { id },
include: { vehicles: true },
});
// Avoid unless necessary
const result = await this.prisma.$queryRaw`SELECT ...`;Frontend Conventions (Next.js)
File Naming
- Component files:
kebab-case.tsxorPascalCase.tsx(both accepted, be consistent within a directory) - Hook files:
use-feature-name.ts(e.g.,use-drivers.ts,use-alerts.ts) - Page files:
page.tsxinside the App Router directory structure - Utility files:
kebab-case.ts
Component Structure
// Named export (preferred)
export function DriverCard({ driver }: DriverCardProps) {
return (
<Card>
<CardHeader>
<CardTitle>{driver.name}</CardTitle>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground">{driver.status}</p>
</CardContent>
</Card>
);
}Shadcn/ui Components (Mandatory)
You must use Shadcn/ui components for all interactive elements. Never use plain HTML elements where a Shadcn component exists:
| Instead of | Use |
|---|---|
<button> | <Button> from @/shared/components/ui/button |
<input> | <Input> from @/shared/components/ui/input |
<select> | <Select> from @/shared/components/ui/select |
<textarea> | <Textarea> from @/shared/components/ui/textarea |
<label> | <Label> from @/shared/components/ui/label |
<table> | <Table> from @/shared/components/ui/table |
| Custom modal | <Dialog> from @/shared/components/ui/dialog |
| Custom tooltip | <Tooltip> from @/shared/components/ui/tooltip |
If a component you need is not yet installed, add it:
pnpm dlx shadcn@latest add [component-name]Dark Theme Support (Mandatory)
Every UI component must work in both light and dark themes. Use semantic color tokens instead of hardcoded colors:
| Category | Do not use | Use instead |
|---|---|---|
| Backgrounds | bg-white, bg-gray-50 | bg-background, bg-card |
| Text | text-gray-900, text-gray-600 | text-foreground, text-muted-foreground |
| Borders | border-gray-200 | border-border |
| Hover states | hover:bg-gray-100 | hover:bg-gray-100 dark:hover:bg-gray-800 |
When you must use a specific shade, always include the dark variant:
// Correct
<div className="bg-gray-50 dark:bg-gray-900">
// Incorrect -- missing dark variant
<div className="bg-gray-50">Responsive Design (Mandatory)
All components must be mobile-first and responsive. Test at these breakpoints:
- 375px — Mobile
- 768px — Tablet
- 1440px — Desktop
Use progressive sizing and spacing:
<div className="px-4 md:px-6 lg:px-8">
<h1 className="text-lg md:text-xl lg:text-2xl">Title</h1>
</div>Color Palette
The only permitted colors in the UI are black, white, and gray shades. The exceptions are status indicators (red, yellow, green, blue) which must include dark theme variants.
Linting and Formatting
The project uses ESLint and Prettier. Before committing, make sure your code passes both:
# Run from the monorepo root
pnpm lint
pnpm formatYour PR will fail CI checks if linting or formatting errors are present.