# Coding Conventions & Style Guide — MeshiTrack > Cross-cutting conventions that apply to all packages in the monorepo. ## General Rules ### No emojis Never use emoji characters anywhere in this codebase: not in source code, UI text, console output, log messages, comments, or documentation. Use plain text instead. ### Language: TypeScript everywhere - All packages use TypeScript with `strict: true` - No `.js` files in source (except config files: `jest.config.js`, `next.config.js`) - All files use `.ts` or `.tsx` extension ### Formatting: Prettier ```json // .prettierrc { "semi": true, "singleQuote": true, "trailingComma": "all", "printWidth": 100, "tabWidth": 2, "arrowParens": "always", "endOfLine": "lf" } ``` ### Linting: ESLint Use a flat config (`eslint.config.js`) with TypeScript support: - `@typescript-eslint/recommended` - `@typescript-eslint/no-explicit-any` → error - `@typescript-eslint/no-unused-vars` → error (with `_` prefix exception) - `import/order` → enforce consistent import ordering ### Import ordering ```typescript // 1. Node built-ins import { readFile } from 'fs/promises'; // 2. External packages import { Injectable } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; // 3. Monorepo packages import type { Product } from '@meshitrack/shared'; import { ProductCategory } from '@meshitrack/shared'; // 4. Internal (relative) imports import { ProductsRepository } from './products.repository'; import type { ProductQueryDto } from './dto/product-query.dto'; ``` Separate each group with a blank line. ## Naming Conventions ### Files | Kind | Pattern | Example | | ----------------- | -------------------------------------- | --------------------------- | | Module | `kebab-case.module.ts` | `products.module.ts` | | Controller | `kebab-case.controller.ts` | `products.controller.ts` | | Service | `kebab-case.service.ts` | `products.service.ts` | | Repository | `kebab-case.repository.ts` | `products.repository.ts` | | Schema (Mongoose) | `kebab-case.schema.ts` | `product.schema.ts` | | DTO | `kebab-case.dto.ts` | `create-product.dto.ts` | | Guard | `kebab-case.guard.ts` | `keycloak-auth.guard.ts` | | Decorator | `kebab-case.decorator.ts` | `current-user.decorator.ts` | | Interface | `kebab-case.interface.ts` | `llm-provider.interface.ts` | | React component | `PascalCase.tsx` | `ProductCard.tsx` | | React hook | `use-kebab-case.ts` | `use-products.ts` | | Test file | `*.spec.ts` (API) / `*.test.tsx` (Web) | `products.service.spec.ts` | | Zod schema | `kebab-case.schemas.ts` | `product.schemas.ts` | | Enum file | `kebab-case.enums.ts` | `product.enums.ts` | ### Code | Kind | Style | Example | | ------------------------------------- | -------------------------------- | -------------------------------------- | | Class | PascalCase | `ProductsService`, `KeycloakAuthGuard` | | Interface | PascalCase + I prefix (optional) | `ILlmProvider` or `LlmProvider` | | Type alias | PascalCase | `ProductQuery`, `CreateProductInput` | | Enum | PascalCase | `ProductCategory` | | Enum member | UPPER_SNAKE | `ProductCategory.DAIRY` | | Function | camelCase | `calculateNutrition()` | | Variable | camelCase | `servingSize`, `totalCalories` | | Constant | UPPER_SNAKE | `LLM_PROVIDER`, `MAX_RETRY_COUNT` | | Private field (backing getter/setter) | `_camelCase` | `_accessToken`, `_householdId` | | React component | PascalCase | `ProductCard`, `NutritionBadge` | | React hook | camelCase | `useProducts`, `usePantryItems` | | CSS class (Tailwind) | kebab-case | via Tailwind utilities | ## Code Organization Rules ### API (NestJS) 1. **One module per feature** — self-contained folder with controller, service, repository, schemas, DTOs, tests 2. **No cross-module imports of internal files** — only import from exported module API 3. **Services contain business logic** — controllers are thin, repositories handle data access 4. **Throw NestJS exceptions** — `NotFoundException`, `ConflictException`, etc. 5. **Use custom decorators for request context** — `@CurrentUser()`, `@CurrentHousehold()` ### Web (Next.js) 1. **App Router conventions** — `page.tsx`, `layout.tsx`, `loading.tsx`, `error.tsx` 2. **Server Components by default** — only add `'use client'` when interactive 3. **Feature components** in `components/features/` — organized by domain (products, pantry, etc.) 4. **UI components** in `components/ui/` — generic, reusable, no domain logic 5. **Hooks** in `hooks/` — custom hooks for data fetching, state management 6. **Services** in `services/` — API client wrappers ### Shared 1. **Only pure TypeScript** — no Node.js, no browser, no framework code 2. **Types, enums, Zod schemas, pure utils** — nothing else 3. **Barrel exports** — every directory has `index.ts` 4. **Use `type` modifier for type-only exports/imports** ## Getters and Setters Use TypeScript `get`/`set` accessors when a property simply exposes or lightly wraps a private backing field. Use a plain method only when the operation is async, takes multiple parameters, or has meaningful side effects beyond assignment. ```typescript // Good:Simple exposure of a private field — use accessor class ApiClient { private _accessToken: string | null = null; set accessToken(token: string) { this._accessToken = token; } get accessToken(): string | null { return this._accessToken; } } // Good:Side effects / async / multiple params — use a method class TokenManager { async setTokenFromCode(code: string, redirectUri: string) { const token = await exchangeCode(code, redirectUri); this._accessToken = token; } } ``` Backing fields use the `_camelCase` prefix to avoid name collisions with the accessor. ## Error Messages ### API error response format ```json { "statusCode": 404, "error": "Not Found", "message": "Product abc123 not found in household xyz", "timestamp": "2026-01-15T10:30:00.000Z", "path": "/api/v1/products/abc123" } ``` ### Rules - Messages are **end-user readable** — no stack traces, no internal IDs in production - Include **resource type and identifier** in not-found messages - Log full error details server-side with a correlation ID - Never leak database field names or query details ## Git Conventions ### Branch naming ``` feature/MESH-001-product-crud fix/MESH-042-barcode-duplicate chore/update-dependencies docs/phase-2-recipe-notes ``` ### Commit messages (Conventional Commits) ``` feat(products): add barcode lookup via Open Food Facts fix(pantry): correct freshness calculation for opened items docs: update phase-2 recipe instructions chore(deps): bump @nestjs/core to 10.4.0 test(api): add integration tests for product search refactor(api): extract repository pattern from service ``` ### PR template ```markdown ## What Brief description of the change. ## Why Link to issue or explanation of the need. ## How Key implementation decisions. ## Testing - [ ] Unit tests added/updated - [ ] Integration tests (if DB changes) - [ ] Manual testing done ## Checklist - [ ] Types updated in `packages/shared` - [ ] Zod schemas match new fields - [ ] API docs (Swagger decorators) updated - [ ] No `any` types introduced ``` ## Logging ### Use NestJS Logger ```typescript import { Logger } from '@nestjs/common'; @Injectable() export class ProductsService { private readonly logger = new Logger(ProductsService.name); async create(householdId: string, userId: string, dto: CreateProductDto) { this.logger.log(`Creating product "${dto.name}" for household ${householdId}`); // ... this.logger.debug(`Product created with ID ${product.id}`); return product; } } ``` ### Log levels - `error` — unexpected failures, unhandled exceptions - `warn` — recoverable issues, deprecated usage, slow queries - `log` — significant operations (create, delete, status transitions) - `debug` — detailed flow information (only shown in dev) - `verbose` — very detailed (disabled by default) ### Never log: - Passwords, tokens, API keys - Full request bodies with sensitive user data - PII (unless specifically needed and compliant with privacy policy)