code-patterns

Enforce consistent implementation patterns across the codebase. Use when reviewing code, fixing inconsistencies, or implementing new features to ensure they follow established patterns.

$ 安裝

git clone https://github.com/majiayu000/claude-skill-registry /tmp/claude-skill-registry && cp -r /tmp/claude-skill-registry/skills/testing/code-patterns ~/.claude/skills/claude-skill-registry

// tip: Run this command in your terminal to install the skill


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:

// Error responses
{
  success: false,
  error: string,        // User-facing message
  message?: string,     // Technical details (optional, for debugging)
  statusCode?: number   // Optional
}

// Success responses
{
  success: true,
  data: T,             // The actual response data
  message?: string     // Optional success message
}

❌ INCONSISTENT PATTERNS TO AVOID:

// Don't use these varying formats:
{ error: 'message' }                           // Missing success field
{ error: 'msg', message: 'other' }             // Confusing dual messages
{ message: 'Error al...', error: 'Unknown' }   // Mixed languages
res.json(data)                                  // No wrapper at all

IMPLEMENTATION:

// In controllers
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';

// Define AuthRequest type if not already defined
interface AuthRequest extends Request {
  user?: {
    userId: string;
    email: string;
    role: string;
  };
}

// In controllers
export const myController = async (req: AuthRequest, res: Response) => {
  const userId = req.user?.userId;  // ✅ Type-safe access

  if (!userId) {
    return res.status(401).json({
      success: false,
      error: 'Authentication required'
    });
  }
  // ... rest of controller
};

❌ AVOID:

// Don't use type casting
const userId = (req as any).user?.userId;  // ❌ Loses type safety

MIDDLEWARE IMPORTS:

// Use the new modular auth system
import { authenticate } from '../auth/middleware/authenticate';
import { requireAdmin } from '../auth/middleware/authorize';

// ❌ Don't use legacy imports
import { authenticate, requireAdmin } from '../middleware/auth';

3. Input Validation

STANDARD - Manual validation until Zod is implemented:

// At the start of controller functions
export const createResource = async (req: AuthRequest, res: Response) => {
  const { field1, field2, field3 } = req.body;

  // Validate required fields
  if (!field1 || !field2) {
    return res.status(400).json({
      success: false,
      error: 'Missing required fields: field1, field2'
    });
  }

  // Validate enum values
  if (!['option1', 'option2'].includes(field1)) {
    return res.status(400).json({
      success: false,
      error: 'Invalid field1. Must be option1 or option2'
    });
  }

  // Validate types/ranges
  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'
    });
  }

  // Continue with logic...
};

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)
});

// Use in middleware or at controller start
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:

// ❌ AVOID
const params: any[] = [];
const data: any = result.rows[0];
visualData?: { type: string; data: any };

// ✅ USE PROPER TYPES
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:

// Define interfaces for query results
interface QueryResult {
  rows: SessionRow[];
  rowCount: number;
}

const result: QueryResult = await pool.query<SessionRow>(query, params);

5. Async/Await Pattern

STANDARD - Always use async/await:

// ✅ CORRECT
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'
    });
  }
};

// ❌ AVOID .then() chains
service.doSomething()
  .then(result => res.json(result))
  .catch(error => res.status(500).json({ error }));

FOR PARALLEL OPERATIONS:

// ✅ Use Promise.all for parallel queries
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:

// For important operations
console.log('[Controller:createResource] Starting operation', {
  userId,
  resourceType,
  timestamp: new Date().toISOString()
});

// For errors - always include context
console.error('[Controller:createResource] Operation failed', {
  error: error instanceof Error ? error.message : 'Unknown',
  userId,
  stack: error instanceof Error ? error.stack : undefined
});

// For debugging (can be removed in production)
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';  // Or define locally
import { serviceFunction } from '../services/myService';

/**
 * Controller description
 * @route POST /api/resource
 * @access Private
 */
export const createResource = async (
  req: AuthRequest,
  res: Response
): Promise<void> => {
  try {
    // 1. Extract and validate authentication
    const userId = req.user?.userId;
    if (!userId) {
      res.status(401).json({
        success: false,
        error: 'Authentication required'
      });
      return;
    }

    // 2. Extract request data
    const { field1, field2 } = req.body;
    const { queryParam } = req.query;

    // 3. Validate inputs
    if (!field1 || !field2) {
      res.status(400).json({
        success: false,
        error: 'Missing required fields: field1, field2'
      });
      return;
    }

    // 4. Call service layer (business logic)
    const result = await serviceFunction({
      field1,
      field2,
      userId
    });

    // 5. Return success response
    res.status(200).json({
      success: true,
      data: result
    });
  } catch (error) {
    console.error('[createResource] Error:', error);
    res.status(500).json({
      success: false,
      error: 'Failed to create resource',
      message: error instanceof Error ? error.message : 'Unknown 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:

// services/myService.ts
import { pool } from '../config/database';

interface CreateResourceInput {
  field1: string;
  field2: string;
  userId: string;
}

interface ResourceResult {
  id: string;
  field1: string;
  field2: string;
  createdAt: Date;
}

/**
 * Creates a new resource
 */
export async function createResource(
  input: CreateResourceInput
): Promise<ResourceResult> {
  const { field1, field2, userId } = input;

  // Business logic here
  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];
}

/**
 * Helper function for data transformation
 */
function transformData(raw: any): ResourceResult {
  return {
    id: raw.id,
    field1: raw.field1,
    field2: raw.field2,
    createdAt: new Date(raw.created_at)
  };
}

9. Database Query Building

FOR DYNAMIC QUERIES:

// ✅ RECOMMENDED PATTERN
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:

// 1. Import at top with other routes
import resourceRoutes from './routes/resourceRoutes';

// 2. Register with other app.use calls
app.use('/api/resources', resourceRoutes);

// 3. Keep alphabetical order for easy scanning
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);  // New route
app.use('/api/sessions', sessionRoutes);

Frontend Patterns

11. API Calls

USE THE CENTRALIZED API CLIENT:

// ✅ Use lib/api-client.ts
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');
      // Update state...
    } 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):

// ❌ Component with mixed logic and presentation
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 () => {
    // 20+ lines of API logic...
  };

  useEffect(() => {
    refreshSession();
    const interval = setInterval(refreshSession, 2000);
    return () => clearInterval(interval);
  }, [sessionId]);

  // 100+ lines of JSX...
}

AFTER - Separated concerns (GOOD):

// ✅ hooks/useLiveSession.ts - All business logic
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);

  // All effects, API calls, and handlers here...

  return {
    session,
    currentQuestionIndex,
    selectedAnswer,
    isLoading,
    handleAnswerSelect,
    handleNext,
    handlePrevious,
  };
}

// ✅ components/LiveSession.tsx - Pure presentation
export default function LiveSession({ sessionId, onExit }: LiveSessionProps) {
  const {
    session,
    currentQuestionIndex,
    selectedAnswer,
    isLoading,
    handleAnswerSelect,
    handleNext,
    handlePrevious,
  } = useLiveSession(sessionId);

  if (isLoading) return <LoadingSpinner />;
  if (!session) return <ErrorState />;

  return (
    <div className="...">
      {/* Pure JSX, no logic */}
    </div>
  );
}

Hook Design Guidelines

1. Hook Interface Design:

// ✅ GOOD - Clear, typed return object
interface UseQuizReturn {
  // State
  questions: Question[];
  currentIndex: number;
  isSubmitted: boolean;

  // Actions
  selectAnswer: (index: number) => void;
  submitQuiz: () => Promise<void>;
  resetQuiz: () => void;

  // Computed values
  score: number;
  progress: number;
}

export function useQuiz(config: QuizConfig): UseQuizReturn { ... }

2. Separate Concerns Within Hooks:

// ✅ GOOD - Compose smaller hooks
export function useQuiz(config: QuizConfig) {
  const state = useQuizState(config);      // State management
  const progress = useQuizProgress(state); // Progress tracking
  const navigation = useQuizNavigation(state.questions.length);

  return {
    ...state,
    ...progress,
    ...navigation,
  };
}

3. Handle Loading and Error States:

// ✅ GOOD - Include all UI 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);

  // ... fetch logic

  return { data, isLoading, error, refetch };
}

Naming Conventions

TypeConventionExample
Hook fileuse{Feature}.tsuseQuizState.ts
Hook functionuse{Feature}useQuizState()
Component file{Feature}.tsxQuizCard.tsx
Props interface{Component}PropsQuizCardProps
Hook return typeUse{Feature}ReturnUseQuizStateReturn

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):

  1. Error Response Format - Critical for frontend reliability
  2. Success Response Format - Ensure all endpoints use { success, data }
  3. Authentication Pattern - Migrate to AuthRequest type
  4. Input Validation - Add missing validations

Medium Priority (Fix When Touching Code):

  1. TypeScript 'any' - Replace with proper types
  2. Logging - Add structured logging to new/modified code
  3. Auth Imports - Use new modular auth imports
  4. Presentational + Hooks - Extract hooks from complex components (see Refactor When Touched Policy)

Low Priority (Nice to Have):

  1. Form Validation - Consider React Hook Form + Zod for complex forms
  2. Query Builder - Consider library for complex dynamic queries

Code Review Checklist

When reviewing or implementing code, check:

  • Error responses use { success: false, error: string } format
  • Success responses use { success: true, data: T } format
  • Controllers use AuthRequest type
  • Auth imports from new modular system
  • All required fields are validated
  • No use of any type (use proper types)
  • Uses async/await (not .then chains)
  • Try-catch blocks around all async operations
  • Errors are logged with context
  • Parameterized queries (no SQL injection risk)
  • Service layer for complex business logic
  • Consistent with existing patterns in the codebase

Frontend Components:

  • Complex components have logic extracted to hooks
  • Hooks return typed interfaces
  • Component focused on rendering (no inline API calls)
  • No ESLint warnings for complexity/length

Examples of Pattern Violations

Violation: Inconsistent Error Format

// ❌ WRONG
res.status(500).json({ error: 'Failed' });

// ✅ CORRECT
res.status(500).json({
  success: false,
  error: 'Failed to process request'
});

Violation: Type Casting

// ❌ WRONG
const userId = (req as any).user?.userId;

// ✅ CORRECT
interface AuthRequest extends Request {
  user?: { userId: string };
}
const userId = req.user?.userId;

Violation: Missing Validation

// ❌ WRONG
export const create = async (req: Request, res: Response) => {
  const { name } = req.body;
  await createResource(name);  // No validation!
  res.json({ success: true });
};

// ✅ CORRECT
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('[myController] Error:', error);
    return res.status(500).json({
      success: false,
      error: 'Operation failed',
      message: error instanceof Error ? error.message : 'Unknown'
    });
  }
};

Use this skill proactively to catch inconsistencies and maintain code quality!