Nommage
kebab-case pour fichiers et dossiers, conventions par contexte
Règle d'or : tous les fichiers et dossiers en
kebab-case. Tout. Sans exception (ou alors documentée).
Pourquoi kebab-case
| Critère | snake_case | camelCase | PascalCase | kebab-case |
|---|---|---|---|---|
| URLs (Next.js) | sale | sale | sale | natif |
Cohérence avec les routes Next.js (/about-us) | non | non | non | oui |
| Cohérence avec les CSS classes | non | non | non | oui |
| Compatible filesystem case-insensitive (macOS) | oui | sale | sale | oui |
| Lisibilité | oui | moyen | moyen | oui |
| Standard Node/Bun ecosystem | non | rare | non | majoritaire |
Le kebab-case gagne sur tous les fronts pour les noms de fichiers et de dossiers. À l'intérieur du code, on utilise les conventions JavaScript classiques (camelCase pour les variables, PascalCase pour les types/classes/composants).
Règles par type d'objet
Fichiers et dossiers
Toujours kebab-case.
✅ apps/server/src/modules/members/members.routes.ts
✅ apps/web/src/components/member-card.tsx
✅ packages/db/src/schema/room-capacity.ts
✅ apps/server/src/lib/error-format.ts
❌ apps/server/src/modules/Members/Members.routes.ts
❌ apps/web/src/components/MemberCard.tsx
❌ packages/db/src/schema/RoomCapacity.ts
❌ apps/server/src/lib/errorFormat.tsY compris pour les composants React. Le composant React lui-même reste en PascalCase (export function MemberCard()), mais le fichier qui le contient est en kebab-case (member-card.tsx).
Variables, fonctions, paramètres
camelCase.
const memberCount = 42;
function calculateBudget(request: Request) { /* ... */ }
const onMemberClick = () => { /* ... */ };Types, interfaces, classes, composants React
PascalCase.
type MemberFilters = { /* ... */ };
interface RequestPayload { /* ... */ }
class CommissionCalculator { /* ... */ }
export function MemberCard() { /* ... */ }Constantes globales
UPPER_SNAKE_CASE uniquement pour les vraies constantes (valeurs immuables, partagées, semantiquement constantes). Pas pour des const locaux.
const MAX_RESULTS_PER_PAGE = 50;
const ANTHROPIC_BASE_URL = 'https://api.anthropic.com';
// Non, c'est une const locale, pas une "constante" sémantique
const userInput = req.body; // ✅ camelCase, OK
const USER_INPUT = req.body; // ❌Enums
PascalCase pour le nom de l'enum, PascalCase ou UPPER_SNAKE_CASE pour les valeurs (à choisir, restons cohérents, on choisit UPPER_SNAKE_CASE).
enum RequestStatus {
NEW = 'NEW',
QUALIFYING = 'QUALIFYING',
WON = 'WON',
}Préférer cependant les union types (type Status = 'new' | 'qualifying' | 'won') aux enums TS, ils sont plus simples, plus compatibles avec les schémas Zod/TypeBox, et n'introduisent pas de bytecode runtime.
Tables et colonnes Drizzle
snake_case pour les noms côté base, camelCase côté TS via le mapping Drizzle.
export const requestAvailabilityCheck = pgTable('request_availability_check', {
id: uuid('id').defaultRandom().primaryKey(),
resultId: uuid('result_id').notNull(),
tokenHash: text('token_hash').notNull(),
// …
});C'est la convention SQL standard, et Drizzle la mappe automatiquement en camelCase TS.
Variables d'environnement
UPPER_SNAKE_CASE.
DATABASE_URL=
ANTHROPIC_API_KEY=
META_WHATSAPP_TOKEN=Conventions de fichiers par contexte
Modules métier (Elysia)
modules/[feature]/
├── [feature].routes.ts # Elysia routes (HTTP)
├── [feature].service.ts # Logique métier
├── [feature].repository.ts # Accès Drizzle
├── [feature].schema.ts # Schémas TypeBox locaux
└── index.ts # Re-export public uniquementComposants React
components/
├── member-card/
│ ├── member-card.tsx # Composant principal
│ ├── member-card.test.tsx # Tests colocalisés
│ ├── member-card.types.ts # Types locaux si > 5 lignes
│ └── index.ts # Re-exportQuand un composant n'a qu'un fichier, on évite le sous-dossier :
components/
├── badge.tsx # OK, fichier unique
└── member-card/ # Sous-dossier seulement si plusieurs fichiers
├── member-card.tsx
└── ...Schémas Drizzle
packages/db/src/schema/
├── members.ts # Table member + relations
├── rooms.ts # Tables room et room_capacity
├── requests.ts
├── auth.ts
└── index.ts # Re-export toutUn fichier par "domaine de table", pas un fichier par table (sauf si la table est très grosse).
Tests
Tests colocalisés avec le code, suffixe .test.ts ou .test.tsx.
members.service.ts
members.service.test.tsPas de dossier __tests__/ séparé. Plus simple à naviguer.
Hooks React
use- en préfixe (convention React), kebab-case.
hooks/
├── use-member.ts
├── use-debounce.ts
└── use-magic-token.tsÀ l'intérieur, le hook lui-même est en camelCase (useMember).
Index files
Chaque dossier de module exporte son contrat public via index.ts. Les fichiers internes ne sont jamais importés directement depuis l'extérieur.
// Public, peut être importé depuis n'importe où
export { membersRoutes } from './members.routes';
export type { Member, MemberFilters } from './members.types';// ✅ Bon, passe par l'index
import { type Member } from '@/modules/members';
// ❌ Mauvais, importe un fichier interne
import { type Member } from '@/modules/members/members.types';Cette règle est lintable via eslint-plugin-boundaries ou les paths de tsconfig, à activer dès que possible.
Acronymes
Les acronymes restent lowercase dans les noms en camelCase, et PascalCase dans les noms PascalCase.
// camelCase
const apiKey = '...'; // ✅
const apiURL = '...'; // ❌ (URL devrait être Url ici)
const userId = 42; // ✅
const userID = 42; // ❌
// PascalCase
type ApiKey = string; // ✅
type ApiResponse = { /* */ }; // ✅
type APIResponse = { /* */ }; // ❌Cas particuliers
Fichiers de config
Les fichiers de config gardent leur nom natif imposé par l'outil :
biome.json # Biome impose ce nom
next.config.ts # Next.js impose
drizzle.config.ts # Drizzle impose
tsconfig.json
package.jsonComposants UI shadcn
Les composants shadcn générés sont déjà en kebab-case (button.tsx, dropdown-menu.tsx). On garde la convention shadcn telle quelle.
Exceptions documentées
Liste des exceptions tolérées dans le repo (à maintenir à jour) :
| Fichier / dossier | Pourquoi |
|---|---|
README.md, CHANGELOG.md, CONTRIBUTING.md | Convention universelle GitHub |
Dockerfile, Makefile | Imposé par l'outil |
LICENSE | Convention universelle |
app/ (Next.js App Router) | Imposé par Next.js |
_app.tsx, _document.tsx | Imposé par Next.js (rare en App Router) |
Aucune autre exception ne doit exister sans commentaire CONVENTION-EXCEPTION (cf. index conventions).