MeshiTrack/docs/instructions/conventions.md

9.2 KiB

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

// .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

// 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 exceptionsNotFoundException, ConflictException, etc.
  5. Use custom decorators for request context@CurrentUser(), @CurrentHousehold()

Web (Next.js)

  1. App Router conventionspage.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.

// 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

{
  "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

## 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

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)