skillstack
skills/supabase/agent-skills/supabase-postgres-best-practices

supabase-postgres-best-practices

Installation

$ npx skills add https://github.com/supabase/agent-skills --skill supabase-postgres-best-practices

Summary

Optimizing PostgreSQL on Supabase: RLS, Functions, and Edge Functions.

  • Implementing robust Row Level Security (RLS) policies
  • Optimizing SQL queries and database indexing on Supabase
  • Building serverless logic with Supabase Edge Functions
  • Using Supabase Auth for seamless user management

SKILL.md

Supabase Postgres Best Practices

Advanced guidance and implementation patterns for Supabase Postgres Best Practices, focusing on performance, scalability, and maintainability in modern production environments.

When to Use This Skill

  • Building enterprise-grade applications requiring high reliability
  • Implementing complex architecture patterns and state management
  • Optimizing performance-critical paths in the application
  • Designing reusable library components or shared utilities
  • Standardizing development workflows across large teams
  • Migrating legacy codebases to modern best-practices
  • Implementing advanced security and type-safety measures
  • Scaling applications to handle high-traffic or large datasets

Core Concepts

1. Architecture Fundamentals

Purpose: Establish a robust foundation for scalable and maintainable systems.

Implementation Example:

// Advanced pattern implementation
export class ArchitectureManager<T extends BaseConfig> {
  private registry = new Map<string, T>();
  
  register(id: string, config: T): void {
    if (this.registry.has(id)) {
      throw new Error(`Duplicate registration: ${id}`);
    }
    this.registry.set(id, {
      ...config,
      timestamp: Date.now(),
      status: 'initialized'
    });
  }

  resolve(id: string): T | undefined {
    return this.registry.get(id);
  }
}

2. Performance Optimization

Purpose: Maximize throughput and minimize latency across critical paths.

// Memoization and caching strategy
const cache = new WeakMap<object, Result>();

export function optimize<T extends object>(input: T): Result {
  if (cache.has(input)) return cache.get(input)!;
  
  const result = performExpensiveOperation(input);
  cache.set(input, result);
  return result;
}

3. State Management Patterns

Purpose: Efficiently manage complex application state with fine-grained reactivity.

type State = {
  data: Record<string, any>;
  version: number;
  lastUpdated: Date;
};

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case 'UPDATE':
      return {
        ...state,
        data: { ...state.data, ...action.payload },
        version: state.version + 1,
        lastUpdated: new Date()
      };
    default:
      return state;
  }
}

Advanced Patterns

Pattern 1: Plug-and-Play Middleware

type Middleware<T> = (context: T, next: () => Promise<void>) => Promise<void>;

class Pipeline<T> {
  private stack: Middleware<T>[] = [];

  use(middleware: Middleware<T>): void {
    this.stack.push(middleware);
  }

  async execute(context: T): Promise<void> {
    let index = -1;
    const runner = async (i: number): Promise<void> => {
      if (i <= index) throw new Error('next() called multiple times');
      index = i;
      const fn = this.stack[i];
      if (fn) await fn(context, () => runner(i + 1));
    };
    await runner(0);
  }
}

Pattern 2: Resilient Error Boundaries

export async function withRetry<T>(
  fn: () => Promise<T>,
  options: { retries: number; delay: number } = { retries: 3, delay: 1000 }
): Promise<T> {
  try {
    return await fn();
  } catch (error) {
    if (options.retries === 0) throw error;
    await new Promise(r => setTimeout(r, options.delay));
    return withRetry(fn, { ...options, retries: options.retries - 1 });
  }
}

Pattern 3: Dynamic Strategy Resolution

interface Strategy {
  execute(data: any): any;
}

class StrategyContext {
  private strategies: Record<string, Strategy> = {};

  setStrategy(name: string, strategy: Strategy): void {
    this.strategies[name] = strategy;
  }

  run(name: string, data: any): any {
    const strategy = this.strategies[name];
    if (!strategy) throw new Error(`Strategy ${name} not found`);
    return strategy.execute(data);
  }
}

Best Practices

  1. Use explicit type declarations for all public interfaces to ensure API stability
  2. Leverage the satisfies operator for validated object literals without losing inference
  3. Implement structured logging with trace IDs for better production observability
  4. Follow the principle of least privilege when designing security boundaries
  5. Use asynchronous operations by default to avoid blocking the main event loop
  6. Implement comprehensive unit and integration tests for all core logic
  7. Favor composition over inheritance for more flexible and testable code
  8. Use dependency injection to decouple components and improve testability
  9. Document complex logic with inline comments and clear JSDoc annotations
  10. Monitor performance metrics in real-time to identify bottlenecks early
  11. Implement graceful degradation for non-critical features during failures
  12. Use semantic versioning for shared libraries to manage breaking changes

Common Pitfalls

  • Premature Optimization — Focus on readability first, then optimize confirmed bottlenecks
  • God Objects — Avoid classes or functions that handle too many responsibilities
  • Tight Coupling — Ensure components can be tested and modified in isolation
  • Ignoring Edge Cases — Always handle null, undefined, and empty state scenarios
  • Hardcoding Configurations — Use environment variables and config providers
  • Silent Failures — Never swallow errors; always log or propagate them properly
  • Missing Documentation — Code should be self-documenting, but complex intent needs explanation