Skip to content

Instantly share code, notes, and snippets.

@mubbi
Created January 1, 2026 12:14
Show Gist options
  • Select an option

  • Save mubbi/cd3cd57749777dee5c6bebba58161061 to your computer and use it in GitHub Desktop.

Select an option

Save mubbi/cd3cd57749777dee5c6bebba58161061 to your computer and use it in GitHub Desktop.
Developers Guide for MERN

Developer Standards Guide

Tech Stack: Node.js 20+, React 18+, Next.js 14+, Express.js 4+, MongoDB 7+, TypeScript 5+

This document defines mandatory standards and best practices for building scalable, maintainable, secure, and high‑performance applications using the MERN stack (MongoDB, Express.js, React, Node.js) with Next.js and TypeScript. All developers must follow these guidelines consistently.


1. General Engineering Principles

1.1 Core Principles

  • SOLID principles must be followed
  • YAGNI (You Aren't Gonna Need It) - Don't add functionality until it's necessary
  • Separation of Concerns (SoC)
  • Single Responsibility for components, functions, and modules
  • Fail fast, validate early
  • Explicit over implicit
  • Readable code > clever code
  • KISS (Keep It Simple, Stupid) - Prefer simple solutions over complex ones
  • DRY (Don't Repeat Yourself) - Avoid code duplication
  • Composition over Inheritance - Especially in React

1.2 Code Quality Expectations

  • Code must be:
    • Readable
    • Testable
    • Extensible
    • Documented
    • Type-safe (TypeScript)
  • No business logic in components or API routes
  • No direct database queries outside repositories/services
  • Consistent code style across the codebase

2. TypeScript Standards

2.1 TypeScript Version

  • Minimum TypeScript 5.0+
  • Use strict mode in tsconfig.json:
{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictPropertyInitialization": true,
    "noImplicitThis": true,
    "alwaysStrict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true
  }
}

2.2 Type Safety (Mandatory)

  • Always use:
    • Explicit type annotations for function parameters and return types
    • Interfaces for object shapes
    • Type aliases for unions and complex types
    • Enums for fixed sets of values
    • Generics for reusable code
    • Utility types (Partial, Pick, Omit, Record, etc.)
    • Discriminated unions for type narrowing
// βœ… Good - Explicit types
interface User {
  id: string;
  email: string;
  name: string;
  role: UserRole;
  createdAt: Date;
}

enum UserRole {
  ADMIN = 'admin',
  USER = 'user',
  MODERATOR = 'moderator',
}

type UserCreateInput = Omit<User, 'id' | 'createdAt'>;
type UserUpdateInput = Partial<Pick<User, 'name' | 'email'>>;

function createUser(input: UserCreateInput): Promise<User> {
  // Implementation
}

// ❌ Bad - Implicit any
function createUser(input) {
  // Implementation
}

2.3 Modern TypeScript Features

  • Use TypeScript 5.0+ features:
    • const assertions for immutable data
    • satisfies operator for type checking without widening
    • Template literal types for string manipulation
    • Mapped types for transformations
    • Conditional types for advanced type manipulation
    • Type predicates for type guards
// const assertions
const config = {
  apiUrl: 'https://api.example.com',
  timeout: 5000,
} as const;

// satisfies operator
const theme = {
  primary: '#007bff',
  secondary: '#6c757d',
} satisfies Record<string, string>;

// Type predicates
function isUser(obj: unknown): obj is User {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    'id' in obj &&
    'email' in obj
  );
}

2.4 TypeScript ESLint Rules

  • Use strict ESLint rules for TypeScript
  • No any types without justification
  • No @ts-ignore without explanation
  • Prefer unknown over any for type-safe handling
  • Use type-only imports when possible
// βœ… Good
import type { User } from './types';
import { createUser } from './services';

// ❌ Bad
import { User, createUser } from './types';

3. JavaScript/TypeScript Standards

3.1 Code Style

  • Use ESLint with strict rules
  • Use Prettier for code formatting
  • Follow Airbnb or Standard style guide
  • Use ES modules (import/export) over CommonJS
  • Prefer const over let, avoid var
  • Use arrow functions for callbacks
  • Use template literals for string concatenation
  • Use destructuring for object/array access
  • Use optional chaining (?.) and nullish coalescing (??)
// βœ… Good
const user = await getUserById(id);
const { name, email } = user ?? {};
const displayName = name ?? 'Anonymous';
const emailDomain = email?.split('@')[1];

// ❌ Bad
const user = await getUserById(id);
const name = user && user.name ? user.name : 'Anonymous';

3.2 Async/Await

  • Always use async/await over Promises chains
  • Handle errors with try/catch
  • Use Promise.all() for parallel operations
  • Use Promise.allSettled() when some failures are acceptable
  • Avoid mixing async/await with .then()/.catch()
// βœ… Good
async function fetchUserData(userId: string): Promise<UserData> {
  try {
    const [user, posts, comments] = await Promise.all([
      getUserById(userId),
      getPostsByUserId(userId),
      getCommentsByUserId(userId),
    ]);
    return { user, posts, comments };
  } catch (error) {
    logger.error('Failed to fetch user data', { userId, error });
    throw new UserDataFetchError(userId, error);
  }
}

// ❌ Bad
function fetchUserData(userId: string) {
  return getUserById(userId)
    .then(user => {
      return getPostsByUserId(userId).then(posts => {
        return getCommentsByUserId(userId).then(comments => {
          return { user, posts, comments };
        });
      });
    })
    .catch(error => {
      console.log(error);
    });
}

3.3 Error Handling

  • Use custom error classes
  • Always handle errors explicitly
  • Provide meaningful error messages
  • Log errors with context
  • Use error boundaries in React
// Custom error classes
class ValidationError extends Error {
  constructor(
    message: string,
    public readonly field: string,
    public readonly value: unknown
  ) {
    super(message);
    this.name = 'ValidationError';
  }
}

class NotFoundError extends Error {
  constructor(resource: string, id: string) {
    super(`${resource} with id ${id} not found`);
    this.name = 'NotFoundError';
  }
}

// Error handling
async function updateUser(id: string, data: UserUpdateInput): Promise<User> {
  try {
    const user = await userRepository.findById(id);
    if (!user) {
      throw new NotFoundError('User', id);
    }
    return await userRepository.update(id, data);
  } catch (error) {
    if (error instanceof NotFoundError) {
      throw error;
    }
    logger.error('Failed to update user', { id, data, error });
    throw new DatabaseError('Failed to update user', error);
  }
}

4. React Standards

4.1 Component Structure

  • Use functional components exclusively (no class components)
  • Use TypeScript for all components
  • Keep components small and focused (single responsibility)
  • Extract reusable logic into custom hooks
  • Use composition over prop drilling
  • Prefer explicit props over spreading
// βœ… Good - Explicit props, small component
interface ButtonProps {
  label: string;
  onClick: () => void;
  variant?: 'primary' | 'secondary';
  disabled?: boolean;
}

export function Button({
  label,
  onClick,
  variant = 'primary',
  disabled = false,
}: ButtonProps): JSX.Element {
  return (
    <button
      type="button"
      onClick={onClick}
      disabled={disabled}
      className={`btn btn-${variant}`}
    >
      {label}
    </button>
  );
}

// ❌ Bad - Large component, implicit props
export function Button(props: any) {
  // Too much logic, unclear props
}

4.2 Hooks Best Practices

  • Use hooks at the top level only (not in loops/conditions)
  • Extract complex logic into custom hooks
  • Use useMemo for expensive computations
  • Use useCallback for function references passed to children
  • Use useEffect with proper dependencies
  • Clean up side effects in useEffect
// βœ… Good - Custom hook
function useUserPosts(userId: string | null) {
  const [posts, setPosts] = useState<Post[]>([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    if (!userId) return;

    let cancelled = false;
    setLoading(true);
    setError(null);

    fetchUserPosts(userId)
      .then(data => {
        if (!cancelled) {
          setPosts(data);
          setLoading(false);
        }
      })
      .catch(err => {
        if (!cancelled) {
          setError(err);
          setLoading(false);
        }
      });

    return () => {
      cancelled = true;
    };
  }, [userId]);

  return { posts, loading, error };
}

// ❌ Bad - Logic in component
function UserPosts({ userId }: { userId: string }) {
  const [posts, setPosts] = useState([]);
  useEffect(() => {
    fetchUserPosts(userId).then(setPosts);
    // Missing cleanup, missing loading/error states
  }, []);
}

4.3 State Management

  • Use local state (useState) for component-specific state
  • Use Context API for shared state that doesn't change frequently
  • Use state management libraries (Zustand, Redux Toolkit, Jotai) for complex global state
  • Avoid prop drilling - use Context or state management
  • Keep state as close to where it's used as possible
// βœ… Good - Zustand store
import { create } from 'zustand';

interface AuthStore {
  user: User | null;
  token: string | null;
  login: (email: string, password: string) => Promise<void>;
  logout: () => void;
  isAuthenticated: () => boolean;
}

export const useAuthStore = create<AuthStore>((set, get) => ({
  user: null,
  token: null,
  login: async (email, password) => {
    const response = await authService.login(email, password);
    set({ user: response.user, token: response.token });
  },
  logout: () => {
    set({ user: null, token: null });
  },
  isAuthenticated: () => {
    return get().user !== null && get().token !== null;
  },
}));

4.4 Performance Optimization

  • Use React.memo for expensive components
  • Use useMemo for expensive calculations
  • Use useCallback for stable function references
  • Code split with React.lazy and Suspense
  • Virtualize long lists with react-window or react-virtual
  • Avoid creating objects/functions in render
// βœ… Good - Memoized component
const ExpensiveList = React.memo<{ items: Item[] }>(({ items }) => {
  const sortedItems = useMemo(
    () => items.sort((a, b) => a.name.localeCompare(b.name)),
    [items]
  );

  return (
    <ul>
      {sortedItems.map(item => (
        <ListItem key={item.id} item={item} />
      ))}
    </ul>
  );
});

// ❌ Bad - Re-renders on every parent update
function ExpensiveList({ items }: { items: Item[] }) {
  return (
    <ul>
      {items
        .sort((a, b) => a.name.localeCompare(b.name))
        .map(item => (
          <ListItem key={item.id} item={item} />
        ))}
    </ul>
  );
}

4.5 Component Organization

  • One component per file
  • Co-locate related files (component, styles, tests, types)
  • Use index files for clean imports
  • Group by feature, not by type
components/
  UserProfile/
    UserProfile.tsx
    UserProfile.test.tsx
    UserProfile.module.css
    types.ts
    index.ts
  Button/
    Button.tsx
    Button.test.tsx
    Button.module.css
    index.ts

4.6 Error Boundaries

  • Implement Error Boundaries for error handling
  • Catch errors in component tree
  • Provide fallback UI
  • Log errors to error tracking service
  • Use error boundaries at appropriate levels
// βœ… Good - Error Boundary
import { Component, ErrorInfo, ReactNode } from 'react';

interface Props {
  children: ReactNode;
  fallback?: ReactNode;
}

interface State {
  hasError: boolean;
  error?: Error;
}

export class ErrorBoundary extends Component<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error: Error): State {
    return { hasError: true, error };
  }

  componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
    // Log to error tracking service
    console.error('Error caught by boundary:', error, errorInfo);
    // Sentry.captureException(error, { contexts: { react: errorInfo } });
  }

  render(): ReactNode {
    if (this.state.hasError) {
      return (
        this.props.fallback || (
          <div>
            <h2>Something went wrong</h2>
            <button onClick={() => this.setState({ hasError: false })}>
              Try again
            </button>
          </div>
        )
      );
    }

    return this.props.children;
  }
}

4.7 Form Handling & Validation

  • Use React Hook Form or Formik for form management
  • Validate on both client and server
  • Provide clear error messages
  • Handle form submission states
  • Implement proper accessibility
// βœ… Good - React Hook Form with Zod
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';

const userSchema = z.object({
  email: z.string().email('Invalid email address'),
  name: z.string().min(2, 'Name must be at least 2 characters'),
  password: z.string().min(8, 'Password must be at least 8 characters'),
});

type UserFormData = z.infer<typeof userSchema>;

export function UserForm() {
  const {
    register,
    handleSubmit,
    formState: { errors, isSubmitting },
  } = useForm<UserFormData>({
    resolver: zodResolver(userSchema),
  });

  const onSubmit = async (data: UserFormData) => {
    try {
      await createUser(data);
    } catch (error) {
      // Handle error
    }
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <label htmlFor="email">Email</label>
        <input
          id="email"
          type="email"
          {...register('email')}
          aria-invalid={errors.email ? 'true' : 'false'}
        />
        {errors.email && (
          <span role="alert">{errors.email.message}</span>
        )}
      </div>
      {/* More fields... */}
      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? 'Submitting...' : 'Submit'}
      </button>
    </form>
  );
}

4.8 Data Fetching Patterns

  • Use React Query (TanStack Query) or SWR for server state
  • Implement proper loading and error states
  • Use caching and refetching strategies
  • Handle optimistic updates
  • Implement pagination and infinite scrolling
// βœ… Good - React Query
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

function useUsers() {
  return useQuery({
    queryKey: ['users'],
    queryFn: async () => {
      const response = await fetch('/api/users');
      if (!response.ok) throw new Error('Failed to fetch users');
      return response.json();
    },
    staleTime: 5 * 60 * 1000, // 5 minutes
    cacheTime: 10 * 60 * 1000, // 10 minutes
  });
}

function useCreateUser() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: async (data: UserCreateInput) => {
      const response = await fetch('/api/users', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data),
      });
      if (!response.ok) throw new Error('Failed to create user');
      return response.json();
    },
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['users'] });
    },
  });
}
  • One component per file
  • Co-locate related files (component, styles, tests, types)
  • Use index files for clean imports
  • Group by feature, not by type
components/
  UserProfile/
    UserProfile.tsx
    UserProfile.test.tsx
    UserProfile.module.css
    types.ts
    index.ts
  Button/
    Button.tsx
    Button.test.tsx
    Button.module.css
    index.ts

5. Next.js Standards

5.1 Project Structure

app/                          # App Router (Next.js 13+)
  (auth)/                     # Route groups
    login/
      page.tsx
    register/
      page.tsx
  (dashboard)/
    dashboard/
      page.tsx
      loading.tsx
      error.tsx
  api/                        # API routes
    users/
      route.ts
    posts/
      [id]/
        route.ts
  layout.tsx
  page.tsx
  globals.css
components/                   # Shared components
  ui/
  forms/
  layout/
lib/                          # Utilities and helpers
  utils/
  constants/
  validations/
hooks/                         # Custom React hooks
  useAuth.ts
  useUser.ts
types/                         # TypeScript types
  user.ts
  post.ts
public/                        # Static assets
  images/
  icons/
styles/                       # Global styles
  globals.css
  variables.css

5.2 App Router Best Practices

  • Use Server Components by default (React Server Components)
  • Use Client Components ('use client') only when needed
  • Use Server Actions for mutations
  • Use Route Handlers for API endpoints
  • Leverage Streaming and Suspense for better UX
  • Use Parallel Routes and Intercepting Routes when appropriate
// βœ… Good - Server Component (default)
// app/posts/page.tsx
import { getPosts } from '@/lib/api/posts';

export default async function PostsPage() {
  const posts = await getPosts(); // Server-side data fetching

  return (
    <div>
      <h1>Posts</h1>
      {posts.map(post => (
        <PostCard key={post.id} post={post} />
      ))}
    </div>
  );
}

// βœ… Good - Client Component (when needed)
// app/components/PostForm.tsx
'use client';

import { useState } from 'react';
import { createPost } from '@/app/actions/posts';

export function PostForm() {
  const [isSubmitting, setIsSubmitting] = useState(false);

  async function handleSubmit(formData: FormData) {
    setIsSubmitting(true);
    try {
      await createPost(formData);
    } finally {
      setIsSubmitting(false);
    }
  }

  return (
    <form action={handleSubmit}>
      {/* Form fields */}
    </form>
  );
}

5.3 Data Fetching

  • Use Server Components for data fetching when possible
  • Use Server Actions for form submissions and mutations
  • Use Route Handlers for external API proxies
  • Implement proper error handling and loading states
  • Use revalidation strategies (ISR, on-demand, time-based)
// βœ… Good - Server Action
// app/actions/posts.ts
'use server';

import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
import { createPostSchema } from '@/lib/validations/post';

export async function createPost(formData: FormData) {
  const rawData = {
    title: formData.get('title'),
    content: formData.get('content'),
  };

  const validatedData = createPostSchema.parse(rawData);

  try {
    const post = await postService.create(validatedData);
    revalidatePath('/posts');
    redirect(`/posts/${post.id}`);
  } catch (error) {
    throw new Error('Failed to create post');
  }
}

// βœ… Good - Route Handler
// app/api/posts/route.ts
import { NextRequest, NextResponse } from 'next/server';

export async function GET(request: NextRequest) {
  try {
    const searchParams = request.nextUrl.searchParams;
    const page = searchParams.get('page') ?? '1';
    const limit = searchParams.get('limit') ?? '10';

    const posts = await postService.getPaginated({
      page: Number(page),
      limit: Number(limit),
    });

    return NextResponse.json({ posts });
  } catch (error) {
    return NextResponse.json(
      { error: 'Failed to fetch posts' },
      { status: 500 }
    );
  }
}

5.4 Metadata and SEO

  • Use metadata API for page metadata
  • Generate dynamic metadata when needed
  • Use Open Graph and Twitter Card metadata
  • Implement proper sitemap and robots.txt
// βœ… Good - Metadata
// app/posts/[id]/page.tsx
import type { Metadata } from 'next';

type Props = {
  params: { id: string };
};

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const post = await getPostById(params.id);

  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      images: [post.imageUrl],
    },
    twitter: {
      card: 'summary_large_image',
      title: post.title,
      description: post.excerpt,
    },
  };
}

5.5 Middleware

  • Use middleware for authentication, redirects, and headers
  • Keep middleware lightweight and fast
  • Use matcher to limit middleware execution
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  const token = request.cookies.get('auth-token');

  // Protect dashboard routes
  if (request.nextUrl.pathname.startsWith('/dashboard')) {
    if (!token) {
      return NextResponse.redirect(new URL('/login', request.url));
    }
  }

  // Redirect authenticated users away from auth pages
  if (request.nextUrl.pathname.startsWith('/login')) {
    if (token) {
      return NextResponse.redirect(new URL('/dashboard', request.url));
    }
  }

  return NextResponse.next();
}

export const config = {
  matcher: ['/dashboard/:path*', '/login', '/register'],
};

6. Node.js & Express.js Standards

6.1 Project Structure

src/
  controllers/              # Request handlers
    userController.ts
    postController.ts
  services/                 # Business logic
    userService.ts
    postService.ts
  repositories/             # Data access
    userRepository.ts
    postRepository.ts
  models/                   # Data models/schemas
    User.ts
    Post.ts
  routes/                   # Route definitions
    userRoutes.ts
    postRoutes.ts
  middleware/               # Custom middleware
    auth.ts
    validation.ts
    errorHandler.ts
  utils/                    # Utilities
    logger.ts
    errors.ts
  types/                    # TypeScript types
    user.ts
    post.ts
  config/                   # Configuration
    database.ts
    env.ts
  app.ts                    # Express app setup
  server.ts                 # Server entry point

6.2 Express.js Best Practices

  • Use TypeScript for all Express code
  • Use async/await for route handlers
  • Use middleware for cross-cutting concerns
  • Implement proper error handling middleware
  • Use route handlers (thin controllers)
  • Validate input with validation libraries (Zod, Joi, Yup)
  • Use helmet for security headers
  • Use cors for CORS configuration
  • Use compression for response compression
  • Use rate limiting for API protection

6.2.1 CORS Configuration

  • Configure CORS properly for production
  • Whitelist specific origins
  • Handle credentials correctly
  • Use environment variables for allowed origins
// βœ… Good - CORS configuration
import cors from 'cors';

const corsOptions = {
  origin: (origin, callback) => {
    const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',') ?? [];
    
    // Allow requests with no origin (mobile apps, Postman, etc.)
    if (!origin) return callback(null, true);
    
    if (allowedOrigins.indexOf(origin) !== -1) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  },
  credentials: true,
  optionsSuccessStatus: 200,
  methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
  allowedHeaders: ['Content-Type', 'Authorization'],
};

app.use(cors(corsOptions));

6.2.2 Rate Limiting

  • Implement rate limiting on all API endpoints
  • Use different limits for different endpoints
  • Provide rate limit headers in response
  • Handle rate limit errors gracefully
// βœ… Good - Rate limiting
import rateLimit from 'express-rate-limit';

// General API rate limiter
const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // Limit each IP to 100 requests per windowMs
  message: 'Too many requests from this IP, please try again later.',
  standardHeaders: true, // Return rate limit info in `RateLimit-*` headers
  legacyHeaders: false, // Disable `X-RateLimit-*` headers
});

// Strict rate limiter for auth endpoints
const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 5, // Limit each IP to 5 requests per windowMs
  skipSuccessfulRequests: true,
});

app.use('/api/', apiLimiter);
app.use('/api/auth/login', authLimiter);
app.use('/api/auth/register', authLimiter);
  • Use TypeScript for all Express code
  • Use async/await for route handlers
  • Use middleware for cross-cutting concerns
  • Implement proper error handling middleware
  • Use route handlers (thin controllers)
  • Validate input with validation libraries (Zod, Joi, Yup)
  • Use helmet for security headers
  • Use cors for CORS configuration
  • Use compression for response compression
  • Use rate limiting for API protection
// βœ… Good - Express app setup
// src/app.ts
import express from 'express';
import helmet from 'helmet';
import cors from 'cors';
import compression from 'compression';
import rateLimit from 'express-rate-limit';
import { errorHandler } from './middleware/errorHandler';
import { requestLogger } from './middleware/logger';
import userRoutes from './routes/userRoutes';
import postRoutes from './routes/postRoutes';

const app = express();

// Security middleware
app.use(helmet());
app.use(
  cors({
    origin: process.env.ALLOWED_ORIGINS?.split(',') ?? [],
    credentials: true,
  })
);

// Performance middleware
app.use(compression());

// Rate limiting
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // Limit each IP to 100 requests per windowMs
});
app.use('/api/', limiter);

// Body parsing
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));

// Logging
app.use(requestLogger);

// Routes
app.use('/api/users', userRoutes);
app.use('/api/posts', postRoutes);

// Health check
app.get('/health', (req, res) => {
  res.json({ status: 'ok', timestamp: new Date().toISOString() });
});

// Error handling (must be last)
app.use(errorHandler);

export default app;

6.3 Route Handlers (Controllers)

  • Keep controllers thin - delegate to services
  • Handle errors and return appropriate status codes
  • Validate input before processing
  • Use proper HTTP status codes
  • Return consistent response format
// βœ… Good - Thin controller
// src/controllers/userController.ts
import { Request, Response, NextFunction } from 'express';
import { userService } from '../services/userService';
import { createUserSchema, updateUserSchema } from '../validations/user';

export const userController = {
  async getUsers(req: Request, res: Response, next: NextFunction) {
    try {
      const { page = 1, limit = 10, search } = req.query;
      const users = await userService.getUsers({
        page: Number(page),
        limit: Number(limit),
        search: search as string,
      });
      res.json({
        success: true,
        data: users,
        pagination: {
          page: Number(page),
          limit: Number(limit),
        },
      });
    } catch (error) {
      next(error);
    }
  },

  async getUserById(req: Request, res: Response, next: NextFunction) {
    try {
      const { id } = req.params;
      const user = await userService.getUserById(id);
      if (!user) {
        return res.status(404).json({
          success: false,
          error: 'User not found',
        });
      }
      res.json({ success: true, data: user });
    } catch (error) {
      next(error);
    }
  },

  async createUser(req: Request, res: Response, next: NextFunction) {
    try {
      const validatedData = createUserSchema.parse(req.body);
      const user = await userService.createUser(validatedData);
      res.status(201).json({ success: true, data: user });
    } catch (error) {
      next(error);
    }
  },

  async updateUser(req: Request, res: Response, next: NextFunction) {
    try {
      const { id } = req.params;
      const validatedData = updateUserSchema.parse(req.body);
      const user = await userService.updateUser(id, validatedData);
      res.json({ success: true, data: user });
    } catch (error) {
      next(error);
    }
  },

  async deleteUser(req: Request, res: Response, next: NextFunction) {
    try {
      const { id } = req.params;
      await userService.deleteUser(id);
      res.status(204).send();
    } catch (error) {
      next(error);
    }
  },
};

6.4 Service Layer

  • Contain all business logic in services
  • Services depend on repositories, not models directly
  • Use dependency injection for testability
  • Handle business rules and validations
  • Return domain objects, not database documents
// βœ… Good - Service layer
// src/services/userService.ts
import { userRepository } from '../repositories/userRepository';
import { User, UserCreateInput, UserUpdateInput } from '../types/user';
import { NotFoundError, ValidationError } from '../utils/errors';
import { hashPassword, comparePassword } from '../utils/password';

export const userService = {
  async getUsers(options: {
    page: number;
    limit: number;
    search?: string;
  }): Promise<{ users: User[]; total: number }> {
    return userRepository.findMany(options);
  },

  async getUserById(id: string): Promise<User | null> {
    return userRepository.findById(id);
  },

  async createUser(input: UserCreateInput): Promise<User> {
    // Business logic: Check if email exists
    const existingUser = await userRepository.findByEmail(input.email);
    if (existingUser) {
      throw new ValidationError('Email already exists', 'email', input.email);
    }

    // Business logic: Hash password
    const hashedPassword = await hashPassword(input.password);

    return userRepository.create({
      ...input,
      password: hashedPassword,
    });
  },

  async updateUser(id: string, input: UserUpdateInput): Promise<User> {
    const user = await userRepository.findById(id);
    if (!user) {
      throw new NotFoundError('User', id);
    }

    // Business logic: If email changed, check uniqueness
    if (input.email && input.email !== user.email) {
      const existingUser = await userRepository.findByEmail(input.email);
      if (existingUser) {
        throw new ValidationError('Email already exists', 'email', input.email);
      }
    }

    return userRepository.update(id, input);
  },

  async deleteUser(id: string): Promise<void> {
    const user = await userRepository.findById(id);
    if (!user) {
      throw new NotFoundError('User', id);
    }
    await userRepository.delete(id);
  },
};

6.5 Repository Layer

  • Abstract data access logic
  • Use MongoDB with Mongoose or native driver
  • Implement interfaces for testability
  • Handle database-specific operations
  • Return domain objects
// βœ… Good - Repository layer
// src/repositories/userRepository.ts
import { User, UserCreateInput, UserUpdateInput } from '../types/user';
import { UserModel } from '../models/User';

export const userRepository = {
  async findMany(options: {
    page: number;
    limit: number;
    search?: string;
  }): Promise<{ users: User[]; total: number }> {
    const query: Record<string, unknown> = {};

    if (options.search) {
      query.$or = [
        { name: { $regex: options.search, $options: 'i' } },
        { email: { $regex: options.search, $options: 'i' } },
      ];
    }

    const [users, total] = await Promise.all([
      UserModel.find(query)
        .skip((options.page - 1) * options.limit)
        .limit(options.limit)
        .lean()
        .exec(),
      UserModel.countDocuments(query),
    ]);

    return {
      users: users.map(user => this.toDomain(user)),
      total,
    };
  },

  async findById(id: string): Promise<User | null> {
    const user = await UserModel.findById(id).lean().exec();
    return user ? this.toDomain(user) : null;
  },

  async findByEmail(email: string): Promise<User | null> {
    const user = await UserModel.findOne({ email }).lean().exec();
    return user ? this.toDomain(user) : null;
  },

  async create(input: UserCreateInput): Promise<User> {
    const user = await UserModel.create(input);
    return this.toDomain(user.toObject());
  },

  async update(id: string, input: UserUpdateInput): Promise<User> {
    const user = await UserModel.findByIdAndUpdate(
      id,
      { $set: input },
      { new: true, lean: true }
    ).exec();

    if (!user) {
      throw new Error('User not found');
    }

    return this.toDomain(user);
  },

  async delete(id: string): Promise<void> {
    await UserModel.findByIdAndDelete(id).exec();
  },

  // Transform database document to domain object
  toDomain(doc: unknown): User {
    const user = doc as User;
    return {
      id: user._id.toString(),
      email: user.email,
      name: user.name,
      role: user.role,
      createdAt: user.createdAt,
    };
  },
};

6.6 Error Handling

  • Use custom error classes
  • Implement global error handler middleware
  • Return consistent error responses
  • Log errors with context
  • Don't expose internal errors to clients
// βœ… Good - Error handling
// src/utils/errors.ts
export class AppError extends Error {
  constructor(
    message: string,
    public readonly statusCode: number = 500,
    public readonly isOperational: boolean = true
  ) {
    super(message);
    this.name = this.constructor.name;
    Error.captureStackTrace(this, this.constructor);
  }
}

export class NotFoundError extends AppError {
  constructor(resource: string, id: string) {
    super(`${resource} with id ${id} not found`, 404);
  }
}

export class ValidationError extends AppError {
  constructor(
    message: string,
    public readonly field: string,
    public readonly value: unknown
  ) {
    super(message, 400);
  }
}

export class UnauthorizedError extends AppError {
  constructor(message: string = 'Unauthorized') {
    super(message, 401);
  }
}

// src/middleware/errorHandler.ts
import { Request, Response, NextFunction } from 'express';
import { AppError } from '../utils/errors';
import { logger } from '../utils/logger';

export function errorHandler(
  error: Error,
  req: Request,
  res: Response,
  next: NextFunction
): void {
  // Log error
  logger.error('Error occurred', {
    error: error.message,
    stack: error.stack,
    path: req.path,
    method: req.method,
  });

  // Handle known errors
  if (error instanceof AppError) {
    res.status(error.statusCode).json({
      success: false,
      error: error.message,
      ...(process.env.NODE_ENV === 'development' && {
        stack: error.stack,
      }),
    });
    return;
  }

  // Handle unknown errors
  res.status(500).json({
    success: false,
    error: 'Internal server error',
    ...(process.env.NODE_ENV === 'development' && {
      message: error.message,
      stack: error.stack,
    }),
  });
}

7. MongoDB Standards

7.1 Schema Design

  • Use Mongoose for schema definition and validation
  • Define schemas with proper types and validation
  • Use indexes for frequently queried fields
  • Use virtuals for computed properties
  • Use methods and statics for schema-level logic
  • Use pre/post hooks for lifecycle events
// βœ… Good - Mongoose schema
// src/models/User.ts
import mongoose, { Schema, Document } from 'mongoose';

export interface IUser extends Document {
  email: string;
  name: string;
  password: string;
  role: 'admin' | 'user' | 'moderator';
  createdAt: Date;
  updatedAt: Date;
  comparePassword(candidatePassword: string): Promise<boolean>;
}

const userSchema = new Schema<IUser>(
  {
    email: {
      type: String,
      required: [true, 'Email is required'],
      unique: true,
      lowercase: true,
      trim: true,
      match: [/^\S+@\S+\.\S+$/, 'Please provide a valid email'],
      index: true,
    },
    name: {
      type: String,
      required: [true, 'Name is required'],
      trim: true,
      minlength: [2, 'Name must be at least 2 characters'],
      maxlength: [50, 'Name cannot exceed 50 characters'],
    },
    password: {
      type: String,
      required: [true, 'Password is required'],
      minlength: [8, 'Password must be at least 8 characters'],
      select: false, // Don't include in queries by default
    },
    role: {
      type: String,
      enum: ['admin', 'user', 'moderator'],
      default: 'user',
      index: true,
    },
  },
  {
    timestamps: true,
    toJSON: {
      transform: (doc, ret) => {
        ret.id = ret._id;
        delete ret._id;
        delete ret.__v;
        delete ret.password;
        return ret;
      },
    },
  }
);

// Indexes
userSchema.index({ email: 1 });
userSchema.index({ role: 1 });
userSchema.index({ createdAt: -1 });

// Methods
userSchema.methods.comparePassword = async function (
  candidatePassword: string
): Promise<boolean> {
  return bcrypt.compare(candidatePassword, this.password);
};

// Pre-save hook
userSchema.pre('save', async function (next) {
  if (!this.isModified('password')) return next();
  this.password = await bcrypt.hash(this.password, 12);
  next();
});

export const UserModel = mongoose.model<IUser>('User', userSchema);

7.2 Query Optimization

  • Use projection to limit returned fields
  • Use lean() for read-only queries (faster)
  • Use indexes for frequently queried fields
  • Use aggregation pipelines for complex queries
  • Avoid N+1 queries - use populate() or aggregation
  • Use pagination for large datasets
  • Use cursor-based pagination for real-time data
// βœ… Good - Optimized queries
// Get users with pagination and projection
const users = await UserModel.find({ role: 'user' })
  .select('name email createdAt') // Projection
  .lean() // Faster, returns plain objects
  .sort({ createdAt: -1 })
  .skip((page - 1) * limit)
  .limit(limit)
  .exec();

// Use aggregation for complex queries
const userStats = await UserModel.aggregate([
  { $match: { role: 'user' } },
  {
    $group: {
      _id: '$role',
      count: { $sum: 1 },
      avgAge: { $avg: '$age' },
    },
  },
]);

// Populate relationships efficiently
const posts = await PostModel.find({ authorId })
  .populate('author', 'name email') // Only select needed fields
  .lean()
  .exec();

7.3 Transactions

  • Use transactions for multi-document operations
  • Keep transactions short
  • Handle transaction errors properly
  • Use sessions for transaction management
// βœ… Good - Transaction usage
import mongoose from 'mongoose';

async function transferFunds(
  fromUserId: string,
  toUserId: string,
  amount: number
): Promise<void> {
  const session = await mongoose.startSession();
  session.startTransaction();

  try {
    // Update sender balance
    await UserModel.findByIdAndUpdate(
      fromUserId,
      { $inc: { balance: -amount } },
      { session }
    );

    // Update receiver balance
    await UserModel.findByIdAndUpdate(
      toUserId,
      { $inc: { balance: amount } },
      { session }
    );

    // Create transaction record
    await TransactionModel.create(
      [
        {
          fromUserId,
          toUserId,
          amount,
          type: 'transfer',
        },
      ],
      { session }
    );

    await session.commitTransaction();
  } catch (error) {
    await session.abortTransaction();
    throw error;
  } finally {
    session.endSession();
  }
}

8. Testing Standards

8.1 Testing Framework

  • Use Jest for unit and integration tests
  • Use React Testing Library for React component tests
  • Use Supertest for API endpoint tests
  • Aim for 80%+ code coverage
  • Write tests before or alongside code (TDD/BDD)

8.2 Unit Tests

  • Test individual functions and methods
  • Mock external dependencies
  • Test edge cases and error scenarios
  • Keep tests isolated and independent
// βœ… Good - Unit test
// src/services/__tests__/userService.test.ts
import { userService } from '../userService';
import { userRepository } from '../../repositories/userRepository';
import { NotFoundError, ValidationError } from '../../utils/errors';

jest.mock('../../repositories/userRepository');

describe('userService', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  describe('getUserById', () => {
    it('should return user when found', async () => {
      const mockUser = { id: '1', email: '[email protected]', name: 'Test' };
      (userRepository.findById as jest.Mock).mockResolvedValue(mockUser);

      const result = await userService.getUserById('1');

      expect(result).toEqual(mockUser);
      expect(userRepository.findById).toHaveBeenCalledWith('1');
    });

    it('should return null when user not found', async () => {
      (userRepository.findById as jest.Mock).mockResolvedValue(null);

      const result = await userService.getUserById('1');

      expect(result).toBeNull();
    });
  });

  describe('createUser', () => {
    it('should create user with hashed password', async () => {
      const input = {
        email: '[email protected]',
        name: 'Test',
        password: 'password123',
      };
      const mockUser = { ...input, id: '1', password: 'hashed' };
      (userRepository.findByEmail as jest.Mock).mockResolvedValue(null);
      (userRepository.create as jest.Mock).mockResolvedValue(mockUser);

      const result = await userService.createUser(input);

      expect(result).toEqual(mockUser);
      expect(userRepository.findByEmail).toHaveBeenCalledWith(input.email);
    });

    it('should throw ValidationError when email exists', async () => {
      const input = {
        email: '[email protected]',
        name: 'Test',
        password: 'password123',
      };
      (userRepository.findByEmail as jest.Mock).mockResolvedValue({
        id: '1',
        email: input.email,
      });

      await expect(userService.createUser(input)).rejects.toThrow(
        ValidationError
      );
    });
  });
});

8.3 Integration Tests

  • Test API endpoints end-to-end
  • Use test database
  • Clean up test data after tests
  • Test authentication and authorization
// βœ… Good - Integration test
// src/routes/__tests__/userRoutes.test.ts
import request from 'supertest';
import app from '../../app';
import { UserModel } from '../../models/User';
import { connectDB, disconnectDB } from '../../config/database';

beforeAll(async () => {
  await connectDB(process.env.TEST_MONGODB_URI!);
});

afterAll(async () => {
  await UserModel.deleteMany({});
  await disconnectDB();
});

describe('POST /api/users', () => {
  it('should create a new user', async () => {
    const userData = {
      email: '[email protected]',
      name: 'Test User',
      password: 'password123',
    };

    const response = await request(app)
      .post('/api/users')
      .send(userData)
      .expect(201);

    expect(response.body.success).toBe(true);
    expect(response.body.data.email).toBe(userData.email);
    expect(response.body.data.name).toBe(userData.name);
    expect(response.body.data.password).toBeUndefined();
  });

  it('should return 400 for invalid email', async () => {
    const userData = {
      email: 'invalid-email',
      name: 'Test User',
      password: 'password123',
    };

    const response = await request(app)
      .post('/api/users')
      .send(userData)
      .expect(400);

    expect(response.body.success).toBe(false);
  });
});

8.4 React Component Tests

  • Test user interactions, not implementation
  • Use React Testing Library queries
  • Test accessibility
  • Mock external dependencies
// βœ… Good - React component test
// components/UserProfile/__tests__/UserProfile.test.tsx
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { UserProfile } from '../UserProfile';
import { useAuthStore } from '@/stores/authStore';

jest.mock('@/stores/authStore');

describe('UserProfile', () => {
  const mockUser = {
    id: '1',
    email: '[email protected]',
    name: 'Test User',
  };

  beforeEach(() => {
    (useAuthStore as jest.Mock).mockReturnValue({
      user: mockUser,
      updateProfile: jest.fn(),
    });
  });

  it('should display user information', () => {
    render(<UserProfile />);

    expect(screen.getByText('Test User')).toBeInTheDocument();
    expect(screen.getByText('[email protected]')).toBeInTheDocument();
  });

  it('should allow editing user name', async () => {
    const user = userEvent.setup();
    const updateProfile = jest.fn();
    (useAuthStore as jest.Mock).mockReturnValue({
      user: mockUser,
      updateProfile,
    });

    render(<UserProfile />);

    const editButton = screen.getByRole('button', { name: /edit/i });
    await user.click(editButton);

    const nameInput = screen.getByLabelText(/name/i);
    await user.clear(nameInput);
    await user.type(nameInput, 'Updated Name');

    const saveButton = screen.getByRole('button', { name: /save/i });
    await user.click(saveButton);

    await waitFor(() => {
      expect(updateProfile).toHaveBeenCalledWith({
        name: 'Updated Name',
      });
    });
  });
});

9. Security Best Practices

9.1 Authentication & Authorization

  • Use JWT tokens for authentication
  • Store tokens securely (httpOnly cookies preferred)
  • Implement refresh tokens for better security
  • Use bcrypt for password hashing (cost factor 12+)
  • Implement rate limiting on auth endpoints
  • Use HTTPS in production
  • Validate and sanitize all inputs
// βœ… Good - JWT authentication
// src/middleware/auth.ts
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import { UnauthorizedError } from '../utils/errors';

interface AuthRequest extends Request {
  user?: {
    id: string;
    email: string;
    role: string;
  };
}

export function authenticate(
  req: AuthRequest,
  res: Response,
  next: NextFunction
): void {
  try {
    const token = req.cookies.token || req.headers.authorization?.split(' ')[1];

    if (!token) {
      throw new UnauthorizedError('Authentication required');
    }

    const decoded = jwt.verify(
      token,
      process.env.JWT_SECRET!
    ) as { id: string; email: string; role: string };

    req.user = decoded;
    next();
  } catch (error) {
    next(new UnauthorizedError('Invalid or expired token'));
  }
}

export function authorize(...roles: string[]) {
  return (req: AuthRequest, res: Response, next: NextFunction): void => {
    if (!req.user) {
      return next(new UnauthorizedError('Authentication required'));
    }

    if (!roles.includes(req.user.role)) {
      return next(
        new UnauthorizedError('Insufficient permissions')
      );
    }

    next();
  };
}

9.2 Input Validation

  • Validate all inputs on the server side
  • Use Zod or Joi for schema validation
  • Sanitize user inputs
  • Use parameterized queries (MongoDB handles this)
  • Implement CSRF protection for state-changing operations
// βœ… Good - Input validation with Zod
// src/validations/user.ts
import { z } from 'zod';

export const createUserSchema = z.object({
  email: z.string().email('Invalid email address'),
  name: z
    .string()
    .min(2, 'Name must be at least 2 characters')
    .max(50, 'Name cannot exceed 50 characters'),
  password: z
    .string()
    .min(8, 'Password must be at least 8 characters')
    .regex(
      /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/,
      'Password must contain uppercase, lowercase, and number'
    ),
});

export const updateUserSchema = createUserSchema.partial();

// Middleware
export function validate(schema: z.ZodSchema) {
  return (req: Request, res: Response, next: NextFunction): void => {
    try {
      req.body = schema.parse(req.body);
      next();
    } catch (error) {
      if (error instanceof z.ZodError) {
        res.status(400).json({
          success: false,
          error: 'Validation failed',
          details: error.errors,
        });
        return;
      }
      next(error);
    }
  };
}

9.3 Security Headers

  • Use helmet for security headers
  • Implement CORS properly
  • Use Content Security Policy (CSP)
  • Prevent XSS attacks
  • Prevent clickjacking
// βœ… Good - Security headers
import helmet from 'helmet';

app.use(
  helmet({
    contentSecurityPolicy: {
      directives: {
        defaultSrc: ["'self'"],
        styleSrc: ["'self'", "'unsafe-inline'"],
        scriptSrc: ["'self'"],
        imgSrc: ["'self'", 'data:', 'https:'],
      },
    },
    crossOriginEmbedderPolicy: false,
  })
);

10. Performance Optimization

10.1 Frontend Performance

  • Use code splitting and lazy loading
  • Optimize images (Next.js Image component)
  • Use memoization (React.memo, useMemo, useCallback)
  • Implement virtual scrolling for long lists
  • Use CDN for static assets
  • Minimize bundle size (analyze with webpack-bundle-analyzer)
  • Use Service Workers for caching
// βœ… Good - Code splitting
import { lazy, Suspense } from 'react';

const HeavyComponent = lazy(() => import('./HeavyComponent'));

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <HeavyComponent />
    </Suspense>
  );
}

// βœ… Good - Image optimization (Next.js)
import Image from 'next/image';

function ProductImage({ src, alt }: { src: string; alt: string }) {
  return (
    <Image
      src={src}
      alt={alt}
      width={500}
      height={500}
      loading="lazy"
      placeholder="blur"
    />
  );
}

10.2 Backend Performance

  • Use caching (Redis) for frequently accessed data
  • Implement database indexing
  • Use connection pooling
  • Implement pagination for large datasets
  • Use compression (gzip/brotli)
  • Optimize database queries
  • Use background jobs for heavy operations
// βœ… Good - Caching with Redis
import Redis from 'ioredis';

const redis = new Redis(process.env.REDIS_URL!);

async function getUserById(id: string): Promise<User | null> {
  // Check cache first
  const cached = await redis.get(`user:${id}`);
  if (cached) {
    return JSON.parse(cached);
  }

  // Fetch from database
  const user = await userRepository.findById(id);
  if (user) {
    // Cache for 1 hour
    await redis.setex(`user:${id}`, 3600, JSON.stringify(user));
  }

  return user;
}

10.3 Database Performance

  • Create indexes on frequently queried fields
  • Use compound indexes for multi-field queries
  • Avoid N+1 queries
  • Use projection to limit returned fields
  • Use lean() for read-only queries
  • Implement pagination
  • Monitor slow queries
// βœ… Good - Database indexes
userSchema.index({ email: 1 }); // Single field
userSchema.index({ role: 1, createdAt: -1 }); // Compound index
userSchema.index({ 'location.coordinates': '2dsphere' }); // Geospatial

11. Error Handling & Logging

11.1 Error Handling

  • Use custom error classes
  • Implement global error handlers
  • Return consistent error responses
  • Log errors with context
  • Don't expose sensitive information

11.2 Logging

  • Use structured logging (Winston, Pino)
  • Log at appropriate levels (error, warn, info, debug)
  • Include context in logs (user ID, request ID, etc.)
  • Don't log sensitive data (passwords, tokens)
  • Use log aggregation in production
// βœ… Good - Structured logging
import winston from 'winston';

const logger = winston.createLogger({
  level: process.env.LOG_LEVEL || 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  transports: [
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' }),
  ],
});

if (process.env.NODE_ENV !== 'production') {
  logger.add(
    new winston.transports.Console({
      format: winston.format.simple(),
    })
  );
}

// Usage
logger.info('User created', { userId: user.id, email: user.email });
logger.error('Failed to create user', { error: error.message, stack: error.stack });

12. Package Management & Dependencies

12.1 Package Managers

  • Use npm, yarn, or pnpm consistently across the project
  • Prefer pnpm or yarn for better dependency resolution
  • Lock dependency versions with package-lock.json, yarn.lock, or pnpm-lock.yaml
  • Always commit lock files to version control
  • Use exact versions (1.2.3) or caret ranges (^1.2.3) appropriately
  • Avoid wildcard versions (*)
// βœ… Good - package.json
{
  "dependencies": {
    "express": "^4.18.2",
    "mongoose": "^7.5.0",
    "zod": "^3.22.4"
  },
  "devDependencies": {
    "@types/express": "^4.17.17",
    "@types/node": "^20.5.0",
    "typescript": "^5.1.6"
  }
}

// ❌ Bad
{
  "dependencies": {
    "express": "*",
    "mongoose": "latest"
  }
}

12.2 Dependency Management

  • Regularly update dependencies (npm audit, npm outdated)
  • Review security advisories (npm audit)
  • Use npm ci in CI/CD pipelines (faster, more reliable)
  • Pin critical dependencies to exact versions
  • Document why specific versions are required
  • Use peer dependencies correctly
# Check for vulnerabilities
npm audit

# Fix vulnerabilities automatically
npm audit fix

# Check outdated packages
npm outdated

# Update dependencies
npm update

# Install exact versions (CI/CD)
npm ci

12.3 Package Selection

  • Prefer well-maintained packages (check GitHub stars, recent commits)
  • Check package statistics (downloads, maintenance status)
  • Review package code quality and security
  • Prefer official packages over third-party alternatives
  • Check bundle size impact for frontend packages
  • Verify TypeScript support

12.4 Workspace Management (Monorepos)

  • Use npm workspaces, yarn workspaces, or pnpm workspaces for monorepos
  • Share common dependencies at root level
  • Use workspace protocol for internal packages
  • Keep workspace dependencies in sync
// βœ… Good - Workspace setup
{
  "name": "my-monorepo",
  "workspaces": [
    "packages/*",
    "apps/*"
  ],
  "private": true
}

13. Environment Configuration

13.1 Environment Variables

  • Use .env files for local development
  • Use .env.example as a template (commit this)
  • Never commit .env files to version control
  • Use different .env files for different environments
  • Document all required environment variables
  • Use dotenv or dotenv-expand for loading
# βœ… Good - .env.example
DATABASE_URL=mongodb://localhost:27017/myapp
JWT_SECRET=your-secret-key-here
JWT_EXPIRES_IN=7d
NODE_ENV=development
PORT=3000
REDIS_URL=redis://localhost:6379

13.2 Configuration Management

  • Store configuration in config/ directory
  • Use process.env with defaults
  • Validate environment variables on startup
  • Use configuration objects, not direct process.env access
  • Type environment variables with TypeScript
// βœ… Good - Configuration management
// src/config/env.ts
import { z } from 'zod';

const envSchema = z.object({
  NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
  PORT: z.string().transform(Number).default('3000'),
  DATABASE_URL: z.string().url(),
  JWT_SECRET: z.string().min(32),
  JWT_EXPIRES_IN: z.string().default('7d'),
});

export const env = envSchema.parse(process.env);

// Usage
import { env } from './config/env';
const port = env.PORT;

13.3 Environment-Specific Configuration

  • Use different configs for dev, staging, production
  • Use environment variables for secrets
  • Use config files for non-sensitive settings
  • Validate configuration on application startup

14. API Design & Versioning

14.1 RESTful API Design

  • Follow RESTful conventions
  • Use proper HTTP methods (GET, POST, PUT, PATCH, DELETE)
  • Use proper HTTP status codes
  • Use resource-based URLs
  • Implement consistent response formats
  • Use plural nouns for resources
// βœ… Good - RESTful API
GET    /api/v1/users          // List users
GET    /api/v1/users/:id       // Get user
POST   /api/v1/users           // Create user
PUT    /api/v1/users/:id       // Update user (full)
PATCH  /api/v1/users/:id       // Update user (partial)
DELETE /api/v1/users/:id       // Delete user

// ❌ Bad
GET    /api/getUsers
POST   /api/createUser
POST   /api/updateUser
POST   /api/deleteUser

14.2 API Versioning

  • Version APIs from the start (/api/v1/)
  • Use URL versioning (/api/v1/, /api/v2/)
  • Maintain backward compatibility when possible
  • Document breaking changes
  • Deprecate old versions gracefully
// βœ… Good - API versioning
app.use('/api/v1', v1Routes);
app.use('/api/v2', v2Routes);

// Deprecation header
app.use('/api/v1', (req, res, next) => {
  res.setHeader('X-API-Deprecated', 'true');
  res.setHeader('X-API-Sunset', '2025-12-31');
  next();
});

14.3 API Response Format

  • Use consistent response structure
  • Include success/error indicators
  • Provide meaningful error messages
  • Include pagination metadata
  • Use proper HTTP status codes
// βœ… Good - Consistent response format
// Success response
{
  "success": true,
  "data": {
    "id": "123",
    "name": "John Doe",
    "email": "[email protected]"
  }
}

// Error response
{
  "success": false,
  "error": {
    "message": "Validation failed",
    "code": "VALIDATION_ERROR",
    "details": [
      {
        "field": "email",
        "message": "Invalid email format"
      }
    ]
  }
}

// Paginated response
{
  "success": true,
  "data": [...],
  "pagination": {
    "page": 1,
    "limit": 10,
    "total": 100,
    "totalPages": 10
  }
}

14.4 API Documentation

  • Document all API endpoints
  • Use OpenAPI/Swagger for API documentation
  • Include request/response examples
  • Document authentication requirements
  • Include error response examples
  • Keep documentation up to date
// βœ… Good - OpenAPI documentation
// Using swagger-jsdoc
/**
 * @swagger
 * /api/v1/users:
 *   get:
 *     summary: Get all users
 *     tags: [Users]
 *     parameters:
 *       - in: query
 *         name: page
 *         schema:
 *           type: integer
 *         description: Page number
 *     responses:
 *       200:
 *         description: List of users
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 success:
 *                   type: boolean
 *                 data:
 *                   type: array
 *                   items:
 *                     $ref: '#/components/schemas/User'
 */

15. Styling Standards

15.1 CSS-in-JS vs CSS Modules

  • Use CSS Modules or Tailwind CSS for most projects
  • Use styled-components or Emotion for component-scoped styles
  • Avoid global CSS when possible
  • Use CSS variables for theming
  • Follow BEM naming convention if using CSS Modules
// βœ… Good - CSS Modules
// Button.module.css
.button {
  padding: 0.5rem 1rem;
  border-radius: 0.25rem;
}

.primary {
  background-color: var(--color-primary);
}

// Button.tsx
import styles from './Button.module.css';

export function Button({ variant = 'primary' }: ButtonProps) {
  return (
    <button className={`${styles.button} ${styles[variant]}`}>
      Click me
    </button>
  );
}

// βœ… Good - Tailwind CSS
export function Button({ variant = 'primary' }: ButtonProps) {
  return (
    <button className="px-4 py-2 rounded bg-blue-500 text-white hover:bg-blue-600">
      Click me
    </button>
  );
}

15.2 Responsive Design

  • Use mobile-first approach
  • Use relative units (rem, em, %) over fixed units (px)
  • Test on multiple screen sizes
  • Use CSS Grid and Flexbox for layouts
  • Implement proper breakpoints
// βœ… Good - Responsive design with Tailwind
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
  {items.map(item => <Card key={item.id} item={item} />)}
</div>

15.3 Theming

  • Support light/dark themes
  • Use CSS variables for theme values
  • Provide theme switching functionality
  • Ensure sufficient color contrast (WCAG AA)

16. Accessibility (a11y)

16.1 WCAG Compliance

  • Follow WCAG 2.1 Level AA standards
  • Ensure keyboard navigation works
  • Provide proper ARIA labels
  • Maintain proper heading hierarchy
  • Ensure sufficient color contrast
  • Provide alt text for images
// βœ… Good - Accessible component
export function Button({
  label,
  onClick,
  disabled,
  ariaLabel,
}: ButtonProps) {
  return (
    <button
      type="button"
      onClick={onClick}
      disabled={disabled}
      aria-label={ariaLabel || label}
      className="btn btn-primary"
    >
      {label}
    </button>
  );
}

// βœ… Good - Accessible form
<form aria-label="User registration form">
  <label htmlFor="email">
    Email Address
    <input
      id="email"
      type="email"
      required
      aria-describedby="email-error"
      aria-invalid={hasError}
    />
    {hasError && (
      <span id="email-error" role="alert" className="error">
        Please enter a valid email address
      </span>
    )}
  </label>
</form>

16.2 Semantic HTML

  • Use semantic HTML elements (<nav>, <main>, <article>, <section>)
  • Use proper heading hierarchy (h1 β†’ h2 β†’ h3)
  • Use landmarks for page structure
  • Ensure form labels are properly associated

16.3 Testing Accessibility

  • Use axe-core or jest-axe for automated testing
  • Test with keyboard navigation
  • Test with screen readers
  • Use Lighthouse accessibility audit

17. Internationalization (i18n)

17.1 i18n Setup

  • Use next-intl (Next.js) or react-i18next (React)
  • Extract all user-facing strings
  • Support multiple languages from the start
  • Use proper locale formatting for dates, numbers, currencies
// βœ… Good - i18n with next-intl
// messages/en.json
{
  "common": {
    "welcome": "Welcome",
    "submit": "Submit",
    "cancel": "Cancel"
  },
  "users": {
    "title": "Users",
    "create": "Create User"
  }
}

// Component
import { useTranslations } from 'next-intl';

export function UserForm() {
  const t = useTranslations('users');
  
  return (
    <form>
      <h1>{t('title')}</h1>
      <button type="submit">{t('create')}</button>
    </form>
  );
}

17.2 Date and Number Formatting

  • Use locale-aware formatting
  • Format dates according to user's locale
  • Format numbers and currencies properly
  • Handle timezones correctly

18. Build & Bundling

18.1 Build Tools

  • Use Vite or Next.js built-in bundler
  • Configure proper code splitting
  • Optimize bundle size
  • Use tree shaking
  • Minimize production builds
// βœ… Good - Vite config
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['react', 'react-dom'],
          utils: ['lodash', 'date-fns'],
        },
      },
    },
    chunkSizeWarningLimit: 1000,
  },
});

18.2 Code Splitting

  • Split code by route
  • Split vendor code separately
  • Lazy load heavy components
  • Use dynamic imports
// βœ… Good - Code splitting
import { lazy, Suspense } from 'react';

const HeavyComponent = lazy(() => import('./HeavyComponent'));

function App() {
  return (
    <Suspense fallback={<Loading />}>
      <HeavyComponent />
    </Suspense>
  );
}

18.3 Bundle Analysis

  • Use webpack-bundle-analyzer or rollup-plugin-visualizer
  • Monitor bundle size
  • Identify large dependencies
  • Optimize imports

19. File Uploads

19.1 File Upload Handling

  • Validate file types and sizes
  • Use multer (Express) or formidable for file uploads
  • Store files securely (cloud storage preferred)
  • Generate unique filenames
  • Sanitize filenames
  • Implement virus scanning (production)
// βœ… Good - File upload with multer
import multer from 'multer';
import { v4 as uuidv4 } from 'uuid';
import path from 'path';

const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, 'uploads/');
  },
  filename: (req, file, cb) => {
    const ext = path.extname(file.originalname);
    cb(null, `${uuidv4()}${ext}`);
  },
});

const upload = multer({
  storage,
  limits: {
    fileSize: 5 * 1024 * 1024, // 5MB
  },
  fileFilter: (req, file, cb) => {
    const allowedTypes = /jpeg|jpg|png|gif/;
    const extname = allowedTypes.test(
      path.extname(file.originalname).toLowerCase()
    );
    const mimetype = allowedTypes.test(file.mimetype);

    if (extname && mimetype) {
      cb(null, true);
    } else {
      cb(new Error('Only image files are allowed'));
    }
  },
});

// Route
app.post('/api/upload', upload.single('image'), async (req, res) => {
  if (!req.file) {
    return res.status(400).json({ error: 'No file uploaded' });
  }
  // Process file...
});

19.2 Cloud Storage

  • Use AWS S3, Cloudinary, or Azure Blob Storage for production
  • Generate signed URLs for secure access
  • Implement CDN for file delivery
  • Handle file deletion properly

20. Background Jobs & Queues

20.1 Queue Management

  • Use Bull (Redis-based) or Agenda (MongoDB-based) for job queues
  • Process heavy operations asynchronously
  • Implement retry logic with exponential backoff
  • Monitor queue health
  • Handle job failures gracefully
// βœ… Good - Bull queue setup
import Queue from 'bull';
import Redis from 'ioredis';

const emailQueue = new Queue('email', {
  redis: {
    host: process.env.REDIS_HOST,
    port: Number(process.env.REDIS_PORT),
  },
});

// Add job
emailQueue.add('send-welcome-email', {
  userId: user.id,
  email: user.email,
}, {
  attempts: 3,
  backoff: {
    type: 'exponential',
    delay: 2000,
  },
});

// Process job
emailQueue.process('send-welcome-email', async (job) => {
  const { userId, email } = job.data;
  await emailService.sendWelcomeEmail(email);
});

20.2 Job Types

  • Use queues for: email sending, image processing, data export, report generation
  • Keep jobs idempotent when possible
  • Implement job priorities
  • Set appropriate timeouts

21. WebSocket & Real-time Communication

21.1 WebSocket Setup

  • Use Socket.io for real-time communication
  • Implement proper authentication
  • Handle connection errors gracefully
  • Use rooms/namespaces for organization
  • Implement rate limiting
// βœ… Good - Socket.io setup
import { Server } from 'socket.io';
import { Server as HttpServer } from 'http';

const httpServer = new HttpServer(app);
const io = new Server(httpServer, {
  cors: {
    origin: process.env.ALLOWED_ORIGINS?.split(',') ?? [],
    credentials: true,
  },
});

// Authentication middleware
io.use(async (socket, next) => {
  try {
    const token = socket.handshake.auth.token;
    const user = await verifyToken(token);
    socket.data.user = user;
    next();
  } catch (error) {
    next(new Error('Authentication failed'));
  }
});

// Connection handling
io.on('connection', (socket) => {
  console.log(`User ${socket.data.user.id} connected`);

  socket.on('join-room', (roomId) => {
    socket.join(roomId);
  });

  socket.on('send-message', async (data) => {
    // Save message to database
    const message = await messageService.create(data);
    // Broadcast to room
    io.to(data.roomId).emit('new-message', message);
  });

  socket.on('disconnect', () => {
    console.log(`User ${socket.data.user.id} disconnected`);
  });
});

21.2 Real-time Best Practices

  • Use rooms for scoped communication
  • Implement reconnection logic on client
  • Handle connection state changes
  • Limit message frequency
  • Validate messages on server

22. Email Services

22.1 Email Setup

  • Use Nodemailer with SMTP or SendGrid/ AWS SES for production
  • Use email templates (Handlebars, EJS)
  • Implement email queuing
  • Handle bounces and failures
  • Track email delivery
// βœ… Good - Email service with Nodemailer
import nodemailer from 'nodemailer';
import { compile } from 'handlebars';
import fs from 'fs/promises';

const transporter = nodemailer.createTransport({
  host: process.env.SMTP_HOST,
  port: Number(process.env.SMTP_PORT),
  secure: true,
  auth: {
    user: process.env.SMTP_USER,
    pass: process.env.SMTP_PASS,
  },
});

export const emailService = {
  async sendWelcomeEmail(user: User): Promise<void> {
    const template = await fs.readFile('templates/welcome.hbs', 'utf-8');
    const compiled = compile(template);

    await transporter.sendMail({
      from: process.env.FROM_EMAIL,
      to: user.email,
      subject: 'Welcome!',
      html: compiled({ name: user.name }),
    });
  },
};

22.2 Email Templates

  • Use HTML templates with inline CSS
  • Provide plain text alternatives
  • Test across email clients
  • Include unsubscribe links
  • Follow email best practices

23. Monitoring & Observability

23.1 Application Monitoring

  • Use Sentry for error tracking
  • Use Datadog, New Relic, or Prometheus for metrics
  • Monitor application performance (APM)
  • Track key business metrics
  • Set up alerts for critical issues
// βœ… Good - Sentry setup
import * as Sentry from '@sentry/node';

Sentry.init({
  dsn: process.env.SENTRY_DSN,
  environment: process.env.NODE_ENV,
  tracesSampleRate: 1.0,
});

// Error tracking
try {
  await riskyOperation();
} catch (error) {
  Sentry.captureException(error, {
    tags: { section: 'user-creation' },
    extra: { userId: user.id },
  });
  throw error;
}

23.2 Health Checks

  • Implement health check endpoints
  • Check database connectivity
  • Check external service availability
  • Return appropriate status codes
  • Include version information
// βœ… Good - Health check endpoint
app.get('/health', async (req, res) => {
  const checks = {
    database: await checkDatabase(),
    redis: await checkRedis(),
    externalApi: await checkExternalApi(),
  };

  const isHealthy = Object.values(checks).every(check => check.status === 'ok');

  res.status(isHealthy ? 200 : 503).json({
    status: isHealthy ? 'healthy' : 'unhealthy',
    checks,
    timestamp: new Date().toISOString(),
    version: process.env.APP_VERSION,
  });
});

23.3 Logging

  • Use structured logging
  • Include correlation IDs
  • Log at appropriate levels
  • Don't log sensitive information
  • Use log aggregation (ELK, CloudWatch)

24. CI/CD & Deployment

24.1 Continuous Integration

  • Run tests on every commit
  • Run linting and type checking
  • Run security audits
  • Build and test in isolated environments
  • Use GitHub Actions, GitLab CI, or CircleCI
# βœ… Good - GitHub Actions workflow
name: CI

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '20'
      - run: npm ci
      - run: npm run lint
      - run: npm run type-check
      - run: npm test
      - run: npm audit

24.2 Deployment Strategies

  • Use Docker for containerization
  • Use Kubernetes or Docker Compose for orchestration
  • Implement blue-green or canary deployments
  • Use environment-specific configurations
  • Automate deployments with CI/CD
# βœ… Good - Dockerfile
FROM node:20-alpine AS builder

WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-alpine AS runner
WORKDIR /app

ENV NODE_ENV=production

COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./

EXPOSE 3000
CMD ["node", "dist/server.js"]

24.3 Environment Management

  • Use separate environments (dev, staging, production)
  • Manage secrets securely (Vault, AWS Secrets Manager)
  • Use infrastructure as code (Terraform, CloudFormation)
  • Document deployment procedures

25. Git Workflow & Commits

12.1 Branch Strategy

  • Use feature branches for new features
  • Use main/master for production-ready code
  • Use develop for integration (if using Git Flow)
  • Keep branches short-lived
  • Delete merged branches

12.2 Commit Messages

  • Use Conventional Commits format
  • Write clear, descriptive messages
  • Reference issues/tickets
  • Keep commits atomic and focused
<type>[optional scope]: <description>

[optional body]

[optional footer(s)]

Types: feat, fix, docs, style, refactor, test, chore, perf, ci, build, revert

Examples:

feat(auth): add user registration endpoint
fix(api): resolve token validation issue
docs: update API documentation
test(api): add integration tests for auth
refactor(services): extract user validation logic

26. Pull Request Guidelines

26.1 PR Checklist (Mandatory)

Every Pull Request must include the following checklist and all items must be satisfied before review approval.

26.1.1 General

  • PR title is clear and descriptive
  • PR scope is small and focused (single responsibility)
  • Linked to ticket/task/issue
  • No unrelated changes included
  • Branch is up to date with main/master

26.1.2 Code Quality

  • Code follows TypeScript/JavaScript standards
  • No any types without justification
  • No @ts-ignore without explanation
  • Proper error handling implemented
  • No duplicated logic
  • No commented-out code
  • Meaningful variable, function, and component names
  • Consistent code style (ESLint, Prettier)

26.1.3 Architecture & Design

  • Components are focused and reusable
  • Business logic resides in Services
  • Database access only via Repositories
  • DTOs/types used instead of raw objects
  • SOLID principles respected
  • No forbidden anti-patterns introduced
  • Proper separation of concerns

26.1.4 Database & Performance

  • Queries reviewed for N+1 issues
  • Proper indexes added (if applicable)
  • Transactions used for multi-step operations
  • No unnecessary data fetching
  • Caching considered where appropriate
  • Bundle size impact considered (frontend)

26.1.5 Testing

  • Tests written using Jest and React Testing Library
  • New business logic has unit tests
  • Feature tests updated/added
  • Tests are deterministic (no flaky tests)
  • Coverage does not decrease
  • Edge cases tested

26.1.6 Tooling & CI

  • ESLint passes
  • Prettier formatting applied
  • TypeScript compilation succeeds
  • All tests pass
  • CI pipeline is green
  • No security vulnerabilities (npm audit)

26.1.7 Security

  • No secrets committed
  • Authorization checked (middleware, policies)
  • Input validated (Zod schemas)
  • Sensitive data not logged
  • XSS/CSRF protections in place
  • Rate limiting considered

26.1.8 Documentation

  • Code is self-documenting
  • Complex logic has comments
  • README updated if needed
  • API documentation updated
  • Type definitions are clear

26.2 PR Description Template

## Description
Brief description of changes

## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update

## Related Issue
Closes #123

## Changes Made
- Change 1
- Change 2
- Change 3

## Testing
- [ ] Unit tests added/updated
- [ ] Integration tests added/updated
- [ ] Manual testing completed

## Screenshots (if applicable)
[Add screenshots]

## Checklist
- [ ] Code follows project standards
- [ ] Tests pass
- [ ] Documentation updated
- [ ] No breaking changes (or documented)

27. Code Review Guidelines

27.1 Review Checklist

  • Code follows TypeScript/JavaScript standards
  • No any types without justification
  • Proper error handling
  • Tests written and passing
  • No security vulnerabilities
  • Performance considerations addressed
  • Documentation updated
  • No commented-out code
  • Consistent code style
  • Accessibility considered (frontend)
  • Responsive design tested (frontend)

27.2 Review Focus Areas

  • Correctness: Does it work as intended?
  • Security: Any vulnerabilities?
  • Performance: Any bottlenecks?
  • Maintainability: Is it easy to understand?
  • Testing: Are edge cases covered?
  • Documentation: Is it well-documented?
  • Accessibility: Is it accessible?
  • User Experience: Is the UX good?

27.3 Code Review Rubric

Reviewers must evaluate PRs using the following rubric. Approval requires no Critical issues and all Major issues resolved.

27.3.1 Severity Levels

Level Description
Critical Must fix before merge
Major Should fix before merge
Minor Can fix later
Nit Style or preference

27.3.2 Evaluation Criteria

1. Correctness (Critical)

  • Does the code do what the ticket describes?
  • Are edge cases handled?
  • Are error paths covered?
  • Do tests cover the functionality?

2. Architecture & Design (Critical / Major)

  • Proper separation of concerns
  • No business logic in components/controllers
  • Correct use of services, repositories, hooks
  • Easy to extend without modification
  • Follows established patterns

3. Readability & Maintainability (Major)

  • Clear naming
  • Small, focused functions/components
  • Self-explanatory code
  • Adequate comments where needed
  • Consistent with codebase style

4. Performance & Scalability (Major)

  • Efficient database access
  • No unnecessary queries or renders
  • Proper caching where appropriate
  • Background jobs used for heavy operations
  • Bundle size considered

5. Testing Quality (Critical)

  • Tests exist and are meaningful
  • Tests assert behavior, not implementation
  • Edge cases tested
  • Test coverage maintained or improved

6. Security (Critical)

  • Proper authorization
  • Input validation present
  • No sensitive data exposure
  • No XSS/CSRF vulnerabilities
  • Secrets not committed

7. Consistency & Standards (Major)

  • Conforms to this standards guide
  • Matches existing patterns
  • Tooling compliance (ESLint, Prettier, TypeScript)
  • Follows naming conventions

8. Accessibility (Major - Frontend)

  • WCAG compliance
  • Keyboard navigation works
  • Screen reader friendly
  • Proper ARIA labels

27.4 Review Decision Matrix

Condition Decision
Any Critical issue ❌ Reject
Major issues present πŸ”„ Changes requested
Only Minor/Nits βœ… Approve

27.5 Reviewer Responsibilities

  • Be constructive and specific
  • Suggest improvements, not just problems
  • Enforce standards consistently
  • Focus on long-term maintainability
  • Review within 24-48 hours when possible
  • Approve promptly when standards are met
  • Explain the "why" behind feedback
  • Be respectful and professional

27.6 Author Responsibilities

  • Respond to all comments
  • Push fixes promptly
  • Do not dismiss feedback without justification
  • Keep PRs up to date with main/master
  • Request re-review after addressing feedback
  • Be open to suggestions and improvements
  • Ask questions if feedback is unclear
  • Keep PRs focused and small

28. Anti-Patterns (Forbidden)

28.1 Architecture Anti-Patterns

❌ Fat components - Components with business logic
❌ Business logic in API routes - Logic should be in services
❌ God objects - Classes/modules that do too much
❌ Tight coupling - Direct dependencies on concrete implementations
❌ Over-engineering - Using complex patterns for simple operations
❌ Prop drilling - Passing props through many levels

28.2 Code Quality Anti-Patterns

❌ Any types - Using any without justification
❌ Untyped functions - Functions without type annotations
❌ Magic numbers/strings - Hard-coded values
❌ Commented-out code - Dead code should be removed
❌ Copy-paste programming - Duplicated logic
❌ Large functions - Functions doing too much

28.3 React Anti-Patterns

❌ Mutating state directly - Always use setState/useState
❌ Using index as key - Use stable, unique keys
❌ Side effects in render - Use useEffect
❌ Creating objects/functions in render - Use useMemo/useCallback
❌ Unnecessary re-renders - Optimize with memoization

28.4 Database Anti-Patterns

❌ N+1 queries - Use populate or aggregation
❌ Missing indexes - Index frequently queried fields
❌ Fetching unnecessary data - Use projection
❌ No pagination - Paginate large datasets
❌ Synchronous operations - Use async/await properly

28.5 Security Anti-Patterns

❌ Secrets in code - Use environment variables
❌ No input validation - Validate all inputs
❌ SQL injection risks - Use parameterized queries
❌ XSS vulnerabilities - Sanitize user input
❌ No rate limiting - Implement rate limiting
❌ Weak passwords - Enforce strong passwords


29. Final Rule

If it is not tested, typed, documented, reviewed, and formatted β€” it is not production-ready.


30. Additional Resources

30.1 Recommended Tools

  • Linting: ESLint, Prettier
  • Testing: Jest, React Testing Library, Supertest
  • Type Checking: TypeScript
  • API Testing: Postman, Insomnia
  • Database Tools: MongoDB Compass
  • Monitoring: Sentry, LogRocket
  • Performance: Lighthouse, WebPageTest

30.2 Learning Resources

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment