5.8 KiB
05 - Code Conventions
Language and style conventions for all TypeScript/TSX code in YouTube Studio Flow. These apply to both the frontend and backend unless noted otherwise. For CSS-specific rules, see 02 - CSS Conventions.
Comments
Default to writing no comments.
Only add a comment when the why is non-obvious: a hidden constraint, a subtle invariant, a browser quirk, a workaround for a specific external bug, or behavior that would surprise a competent reader encountering it for the first time. If removing the comment wouldn't cause confusion, don't write it.
Never write:
- Comments that describe what the code does (the code already does that)
- Multi-paragraph docstrings on functions or classes
- Multi-line comment blocks
- Cross-reference notes ("added for issue #123", "used by the sync feature")
- Section dividers (
// --- helpers ---)
Acceptable:
// BullMQ silently drops jobs if Redis evicts keys — must use noeviction policy
const client = new Redis({ maxmemoryPolicy: 'noeviction' });
// Single-pass replacement avoids re-substituting inside already-replaced values
const result = template.replace(pattern, (match) => tokens[match] ?? match);
No premature abstraction
Add abstractions exactly when they are needed, not before. Three similar lines of code is better than a helper function introduced speculatively for hypothetical future use.
If a pattern appears twice, note it. If it appears three times with meaningful variation, consider abstracting. If the abstraction would be more complex than the repetition, don't.
Error handling scope
Only validate and handle errors at system boundaries:
- User input (request bodies, form submissions)
- External API calls (YouTube API, Google OAuth)
- Queue job payloads at the processor entry point
Trust internal code. Trust Prisma's type guarantees. Do not add defensive try/catch around internal service calls for errors that cannot happen under normal operation.
No backwards-compat shims
When changing or removing behavior, change or remove it. Do not leave:
- Unused variables prefixed with
_to signal "formerly used" - Re-exports of deleted types for "compatibility"
// removedcomments where code used to be- Feature flags gating old vs. new behavior
TypeScript
- Use strict TypeScript throughout.
"strict": trueis set in bothtsconfig.jsonfiles. - Avoid
any. The only acceptable uses are at Prisma enum boundaries where the type system cannot express a legitimate constraint, and when interfacing with genuinely untyped external data (e.g. raw OAuth token payloads). - Use
as anysparingly. If you reach for it, consider whether a type assertion (as SpecificType) or a type guard is more appropriate. - Prefer explicit return types on exported functions and service methods. Inference is acceptable for small private helpers.
- Use
typefor object shapes and union types. Useinterfacefor contracts intended to be extended or implemented.
// Object shape — use type
type VideoFilters = {
search?: string;
status?: PrivacyStatus;
page: number;
};
// Extendable contract — use interface
interface RenderInput {
videoId: string;
blockOrder: string[];
blockOverrides: Record<string, BlockOverride>;
}
Naming conventions
| Context | Convention | Examples |
|---|---|---|
| React components | PascalCase | VideoTable, BlockEditor |
| CSS module classes | camelCase | styles.clickableRow, styles.headerCell |
| TypeScript interfaces and types | PascalCase | VideoFilters, RenderInput |
| Frontend API functions | camelCase verb + noun | fetchVideos, updateVideo, createBlock |
| Backend service methods | camelCase verb + noun | findAll, findOne, create, update, remove |
| Queue job name strings | kebab-case | 'lint', 'youtube-sync', 'bulk-metadata' |
| TanStack Query keys | array of strings | ['videos'], ['video', id], ['blocks'] |
| Zustand store files | camelCase with use prefix |
useAuthStore.ts, useUIStore.ts |
Import order
Organize imports in this order within any TypeScript or TSX file. An empty line between each group:
- React imports
- Next.js imports (
next/navigation,next/image, etc.) - Third-party libraries (
@tanstack/react-query,lucide-react, etc.) - Internal aliases (
@/components/...,@/lib/...,@/store/...) - Relative imports (
../utils,./helpers) - Style imports (CSS modules, always last)
import { useState, useRef, forwardRef } from 'react';
import { useRouter } from 'next/navigation';
import { useQuery, useMutation } from '@tanstack/react-query';
import { Save, X } from 'lucide-react';
import Modal from '@/components/shared/Modal';
import { updateVideo } from '@/lib/api';
import { useAuthStore } from '@/store/useAuthStore';
import { formatDate } from '../utils/date';
import styles from './VideoEditor.module.css';
import f from '@/components/shared/FormField.module.css';
No dead code
Remove unused imports as you work. Remove unused variables. Remove unreachable branches. Delete unused files rather than leaving them in place.
Do not leave console.log statements in committed code. Use the logger (nestjs/common Logger on the backend) for intentional output.
Validation
Backend: Use class-validator decorators on DTO classes. Every request body that reaches a controller must go through a validated DTO.
export class CreateBlockDto {
@IsString()
@IsNotEmpty()
name: string;
@IsEnum(BlockType)
type: BlockType;
@IsString()
@IsOptional()
content?: string;
}
Frontend: Validate at the form submission boundary only. Inside a component, trust the types from @/lib/api.ts. Do not add runtime type checks on data returned by the API.