| name | code-patterns |
| description | Enforce consistent implementation patterns across the codebase. Use when reviewing code, fixing inconsistencies, or implementing new features to ensure they follow established patterns. |
Code Patterns & Standards
This skill helps maintain consistency across the codebase by enforcing standardized patterns and identifying code that doesn't follow best practices.
When to Use This Skill
Invoke this skill when:
- Implementing new features or endpoints
- Reviewing or refactoring existing code
- Encountering inconsistent patterns
- User asks to "standardize" or "make consistent"
- User asks about "best practices" or "how we do X here"
Critical Patterns (MUST Follow)
1. Error Response Format
STANDARD FORMAT - Use this everywhere:
{
success: false,
error: string,
message?: string,
statusCode?: number
}
{
success: true,
data: T,
message?: string
}
❌ INCONSISTENT PATTERNS TO AVOID:
{ error: 'message' }
{ error: 'msg', message: 'other' }
{ message: 'Error al...', error: 'Unknown' }
res.json(data)
IMPLEMENTATION:
try {
const result = await someService();
return res.status(200).json({
success: true,
data: result
});
} catch (error) {
console.error('Error in operation:', error);
return res.status(500).json({
success: false,
error: 'User-friendly error message',
message: error instanceof Error ? error.message : 'Unknown error'
});
}
2. Authentication Pattern
STANDARD - Use AuthRequest type:
import { Request, Response } from 'express';
interface AuthRequest extends Request {
user?: {
userId: string;
email: string;
role: string;
};
}
export const myController = async (req: AuthRequest, res: Response) => {
const userId = req.user?.userId;
if (!userId) {
return res.status(401).json({
success: false,
error: 'Authentication required'
});
}
};
❌ AVOID:
const userId = (req as any).user?.userId;
MIDDLEWARE IMPORTS:
import { authenticate } from '../auth/middleware/authenticate';
import { requireAdmin } from '../auth/middleware/authorize';
import { authenticate, requireAdmin } from '../middleware/auth';
3. Input Validation
STANDARD - Manual validation until Zod is implemented:
export const createResource = async (req: AuthRequest, res: Response) => {
const { field1, field2, field3 } = req.body;
if (!field1 || !field2) {
return res.status(400).json({
success: false,
error: 'Missing required fields: field1, field2'
});
}
if (!['option1', 'option2'].includes(field1)) {
return res.status(400).json({
success: false,
error: 'Invalid field1. Must be option1 or option2'
});
}
if (typeof field3 !== 'number' || field3 < 1 || field3 > 100) {
return res.status(400).json({
success: false,
error: 'field3 must be a number between 1 and 100'
});
}
};
FUTURE - When adding Zod:
import { z } from 'zod';
const createResourceSchema = z.object({
field1: z.enum(['option1', 'option2']),
field2: z.string().min(1),
field3: z.number().min(1).max(100)
});
const validation = createResourceSchema.safeParse(req.body);
if (!validation.success) {
return res.status(400).json({
success: false,
error: 'Validation failed',
message: validation.error.message
});
}
4. TypeScript Type Safety
STANDARD - Avoid 'any', use proper types:
const params: any[] = [];
const data: any = result.rows[0];
visualData?: { type: string; data: any };
const params: (string | number | boolean)[] = [];
interface SessionRow {
id: string;
name: string;
description: string;
status: string;
created_at: Date;
}
const data: SessionRow = result.rows[0];
interface VisualData {
type: 'graph' | 'geometry' | 'table' | 'diagram';
data: GraphData | GeometryData | TableData | DiagramData;
}
FOR DATABASE QUERIES:
interface QueryResult {
rows: SessionRow[];
rowCount: number;
}
const result: QueryResult = await pool.query<SessionRow>(query, params);
5. Async/Await Pattern
STANDARD - Always use async/await:
export const myController = async (req: AuthRequest, res: Response) => {
try {
const result = await service.doSomething();
return res.json({ success: true, data: result });
} catch (error) {
return res.status(500).json({
success: false,
error: 'Operation failed'
});
}
};
service.doSomething()
.then(result => res.json(result))
.catch(error => res.status(500).json({ error }));
FOR PARALLEL OPERATIONS:
const [users, attempts, sessions] = await Promise.all([
pool.query('SELECT COUNT(*) FROM users'),
pool.query('SELECT COUNT(*) FROM attempts'),
pool.query('SELECT COUNT(*) FROM sessions')
]);
Important Patterns (Highly Recommended)
6. Logging Standards
CURRENT MIXED STATE:
- Some files use emoji-rich logging:
console.log('🔐 Auth successful')
- Some use simple logging:
console.error('Error:', error)
- Some have minimal logging
RECOMMENDATION - Use structured logging:
console.log('[Controller:createResource] Starting operation', {
userId,
resourceType,
timestamp: new Date().toISOString()
});
console.error('[Controller:createResource] Operation failed', {
error: error instanceof Error ? error.message : 'Unknown',
userId,
stack: error instanceof Error ? error.stack : undefined
});
console.debug('[Service:processData] Processing', { dataSize, filters });
AVOID:
- Excessive emoji logging in production code (fine for scripts/seeds)
- Logging sensitive data (passwords, tokens)
- Empty catch blocks without logging
7. Controller Structure
STANDARD PATTERN:
import { Response } from 'express';
import { AuthRequest } from '../types';
import { serviceFunction } from '../services/myService';
export const createResource = async (
req: AuthRequest,
res: Response
): Promise<void> => {
try {
const userId = req.user?.userId;
if (!userId) {
res.status(401).json({
success: false,
error: 'Authentication required'
});
return;
}
const { field1, field2 } = req.body;
const { queryParam } = req.query;
if (!field1 || !field2) {
res.status(400).json({
: ,
:
});
;
}
result = ({
field1,
field2,
userId
});
res.().({
: ,
: result
});
} (error) {
.(, error);
res.().({
: ,
: ,
: error ? error. :
});
}
};
KEY PRINCIPLES:
- Controllers handle HTTP concerns (request/response)
- Services handle business logic
- Always use try-catch
- Log errors with context
- Return consistent response format
- Validate inputs early
- Check authentication first
8. Service Layer Pattern
WHEN TO CREATE A SERVICE:
- Business logic is complex
- Logic is reused across multiple controllers
- External API calls
- Complex database operations
- Data transformations
STRUCTURE:
import { pool } from '../config/database';
interface CreateResourceInput {
field1: string;
field2: string;
userId: string;
}
interface ResourceResult {
id: string;
field1: string;
field2: string;
createdAt: Date;
}
export async function createResource(
input: CreateResourceInput
): Promise<ResourceResult> {
const { field1, field2, userId } = input;
const query = `
INSERT INTO resources (field1, field2, user_id)
VALUES ($1, $2, $3)
RETURNING *
`;
const result = await pool.query<ResourceResult>(
query,
[field1, field2, userId]
);
return result.rows[0];
}
function transformData(raw: any): {
{
: raw.,
: raw.,
: raw.,
: (raw.)
};
}
9. Database Query Building
FOR DYNAMIC QUERIES:
let query = 'SELECT * FROM resources WHERE 1=1';
const params: (string | number)[] = [];
let paramCount = 1;
if (userId) {
query += ` AND user_id = $${paramCount}`;
params.push(userId);
paramCount++;
}
if (status) {
query += ` AND status = $${paramCount}`;
params.push(status);
paramCount++;
}
if (search) {
query += ` AND (name ILIKE $${paramCount} OR description ILIKE $${paramCount})`;
params.push(`%${search}%`);
paramCount++;
}
query += ' ORDER BY created_at DESC';
const result = await pool.query(query, params);
ALWAYS:
- Use parameterized queries (prevent SQL injection)
- Type your params array
- Increment paramCount properly
- Add ORDER BY for predictable results
10. Route Registration Pattern
IN index.ts:
import resourceRoutes from './routes/resourceRoutes';
app.use('/api/resources', resourceRoutes);
app.use('/api/admin', adminRoutes);
app.use('/api/ai', aiRoutes);
app.use('/api/analytics', analyticsRoutes);
app.use('/api/auth', authRoutes);
app.use('/api/resources', resourceRoutes);
app.use('/api/sessions', sessionRoutes);
Frontend Patterns
11. API Calls
USE THE CENTRALIZED API CLIENT:
import { apiClient } from '@/lib/api-client';
const fetchData = async () => {
try {
const response = await apiClient.get('/endpoint');
if (response.success) {
setData(response.data);
}
} catch (error) {
handleError(error);
}
};
BENEFITS:
- Automatic token refresh
- Consistent error handling
- Base URL management
- Request/response interceptors
12. Error Handling (Frontend)
STANDARD PATTERN:
import { toast } from 'react-hot-toast';
const handleOperation = async () => {
try {
const response = await apiClient.post('/endpoint', data);
if (response.success) {
toast.success('Operation successful');
} else {
toast.error(response.error || 'Operation failed');
}
} catch (error) {
console.error('Operation error:', error);
const message = error instanceof Error
? error.message
: 'An unexpected error occurred';
toast.error(message);
} finally {
setLoading(false);
}
};
13. Presentational Components + Hooks Pattern (Frontend)
STANDARD - Separate logic from presentation:
This pattern ensures components remain maintainable by extracting business logic into custom hooks while keeping components focused on rendering.
When to Apply This Pattern
MUST extract hooks when:
- Component has 3+
useState calls
- Component has complex
useEffect logic (API calls, subscriptions, timers)
- Component handles form state with validation
- Same logic could be reused across components
- Component exceeds 80 lines of non-JSX code
Can keep inline when:
- Simple presentational component with 1-2 state variables
- State is purely UI-related (hover, toggle, modal open)
- No API calls or side effects
File Structure Pattern
components/
FeatureName/
FeatureName.tsx # Presentational component
useFeatureName.ts # Business logic hook
FeatureName.types.ts # Shared types (optional)
index.ts # Barrel export (optional)
Or for simpler cases:
components/
FeatureName.tsx # Presentational component
hooks/
useFeatureName.ts # Business logic hook
Example: Complex Component Refactored
BEFORE - Mixed concerns (BAD):
export default function LiveSession({ sessionId }: Props) {
const [session, setSession] = useState<LiveSession | null>(null);
const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
const [selectedAnswer, setSelectedAnswer] = useState<number | null>(null);
const [myAnswers, setMyAnswers] = useState<(number | null)[]>([]);
const [isLoading, setIsLoading] = useState(true);
const refreshSession = async () => {
};
useEffect(() => {
refreshSession();
const interval = setInterval(refreshSession, 2000);
return () => clearInterval(interval);
}, [sessionId]);
}
AFTER - Separated concerns (GOOD):
interface UseLiveSessionReturn {
session: LiveSession | null;
currentQuestionIndex: number;
selectedAnswer: number | null;
isLoading: boolean;
handleAnswerSelect: (index: number) => Promise<void>;
handleNext: () => void;
handlePrevious: () => void;
}
export function useLiveSession(sessionId: string): UseLiveSessionReturn {
const [session, setSession] = useState<LiveSession | null>(null);
const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
const [selectedAnswer, setSelectedAnswer] = useState<number | null>(null);
const [isLoading, setIsLoading] = useState(true);
return {
session,
currentQuestionIndex,
selectedAnswer,
isLoading,
handleAnswerSelect,
handleNext,
handlePrevious,
};
}
() {
{
session,
currentQuestionIndex,
selectedAnswer,
isLoading,
handleAnswerSelect,
handleNext,
handlePrevious,
} = (sessionId);
(isLoading) ;
(!session) ;
(
);
}
Hook Design Guidelines
1. Hook Interface Design:
interface UseQuizReturn {
questions: Question[];
currentIndex: number;
isSubmitted: boolean;
selectAnswer: (index: number) => void;
submitQuiz: () => Promise<void>;
resetQuiz: () => void;
score: number;
progress: number;
}
export function useQuiz(config: QuizConfig): UseQuizReturn { ... }
2. Separate Concerns Within Hooks:
export function useQuiz(config: QuizConfig) {
const state = useQuizState(config);
const progress = useQuizProgress(state);
const navigation = useQuizNavigation(state.questions.length);
return {
...state,
...progress,
...navigation,
};
}
3. Handle Loading and Error States:
export function useDataFetch<T>(url: string) {
const [data, setData] = useState<T | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
return { data, isLoading, error, refetch };
}
Naming Conventions
| Type | Convention | Example |
|---|
| Hook file | use{Feature}.ts | useQuizState.ts |
| Hook function | use{Feature} | useQuizState() |
| Component file | {Feature}.tsx | QuizCard.tsx |
| Props interface | {Component}Props | QuizCardProps |
| Hook return type | Use{Feature}Return | UseQuizStateReturn |
Existing Good Examples in This Codebase
Reference these files for the pattern:
hooks/useQuizState.ts - State management hook
hooks/useQuizProgress.ts - Progress tracking hook
hooks/useQuizNavigation.ts - Navigation hook
components/quiz/ZenQuiz.tsx - Component using multiple hooks
components/ui/Card.tsx - Simple presentational component
ESLint Will Warn You
The following warnings suggest extracting a hook:
max-lines-per-function exceeded (component too long)
complexity exceeded (too many branches)
max-statements exceeded (too much logic)
14. Refactor When Touched Policy
When modifying existing components that don't follow the presentational + hooks pattern:
Substantial edits (MUST refactor):
- Adding new features that require understanding the component
- Fixing bugs that require understanding the component logic
- Any edit touching >10 lines of code
Minor edits (NO refactor required):
- Typo fixes
- Style/CSS changes
- Single-line fixes
Migration Priorities
When refactoring existing code, address in this order:
High Priority (Fix First):
- Error Response Format - Critical for frontend reliability
- Success Response Format - Ensure all endpoints use
{ success, data }
- Authentication Pattern - Migrate to AuthRequest type
- Input Validation - Add missing validations
Medium Priority (Fix When Touching Code):
- TypeScript 'any' - Replace with proper types
- Logging - Add structured logging to new/modified code
- Auth Imports - Use new modular auth imports
- Presentational + Hooks - Extract hooks from complex components (see Refactor When Touched Policy)
Low Priority (Nice to Have):
- Form Validation - Consider React Hook Form + Zod for complex forms
- Query Builder - Consider library for complex dynamic queries
Code Review Checklist
When reviewing or implementing code, check:
Frontend Components:
Examples of Pattern Violations
Violation: Inconsistent Error Format
res.status(500).json({ error: 'Failed' });
res.status(500).json({
success: false,
error: 'Failed to process request'
});
Violation: Type Casting
const userId = (req as any).user?.userId;
interface AuthRequest extends Request {
user?: { userId: string };
}
const userId = req.user?.userId;
Violation: Missing Validation
export const create = async (req: Request, res: Response) => {
const { name } = req.body;
await createResource(name);
res.json({ success: true });
};
export const create = async (req: AuthRequest, res: Response) => {
const { name } = req.body;
if (!name || typeof name !== 'string') {
return res.status(400).json({
success: false,
error: 'Name is required and must be a string'
});
}
const result = await createResource(name);
res.json({ success: true, data: result });
};
Quick Reference
New Controller Template:
import { Response } from 'express';
import { AuthRequest } from '../types';
export const myController = async (req: AuthRequest, res: Response) => {
try {
const userId = req.user?.userId;
if (!userId) {
return res.status(401).json({ success: false, error: 'Auth required' });
}
const { field } = req.body;
if (!field) {
return res.status(400).json({ success: false, error: 'Missing field' });
}
const result = await service(field, userId);
return res.status(200).json({ success: true, data: result });
} catch (error) {
console.(, error);
res.().({
: ,
: ,
: error ? error. :
});
}
};
Use this skill proactively to catch inconsistencies and maintain code quality!