Skip to content

Client Creation ​

Learn how to create and configure PGRestify clients for different use cases and environments.

Overview ​

PGRestify provides multiple client creation methods to fit different architectural patterns and use cases. The client is your main entry point for interacting with your PostgREST API.

Basic Client Creation ​

Simple Client ​

The most straightforward way to create a client:

typescript
import { createClient } from '@webcoded/pgrestify';

const client = createClient({
  url: 'http://localhost:3000'
});

// Start using immediately
const users = await client.from('users').select('*').execute();

Configured Client ​

Add configuration options for production use:

typescript
import { createClient } from '@webcoded/pgrestify';

const client = createClient({
  url: 'https://api.yourapp.com',
  
  // Authentication
  auth: {
    autoRefresh: true,
    storage: 'localStorage', // or 'sessionStorage', 'memory'
    storageKey: 'pgrestify.session'
  },
  
  // Global settings
  transformColumns: true,
  
  // Caching
  cache: {
    ttl: 300000, // 5 minutes
    max: 100     // Maximum cached queries
  },
  
  // Request configuration
  fetch: {
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json'
    },
    timeout: 10000 // 10 seconds
  }
});

Client Configuration Options ​

Core Configuration ​

typescript
interface ClientConfig {
  // Required: PostgREST API URL
  url: string;
  
  // Optional: Custom fetch implementation
  fetch?: typeof fetch;
  
  // Optional: Global column transformation
  transformColumns?: boolean;
  
  // Optional: Default schema (default: 'public')
  schema?: string;
  
  // Optional: API key for services that require it
  apiKey?: string;
}

Authentication Configuration ​

typescript
interface AuthConfig {
  // Enable automatic token refresh
  autoRefresh?: boolean;
  
  // Storage mechanism for tokens
  storage?: 'localStorage' | 'sessionStorage' | 'memory' | Storage;
  
  // Custom storage key
  storageKey?: string;
  
  // JWT configuration
  jwt?: {
    // Custom JWT secret for development
    secret?: string;
    
    // Token expiration buffer (refresh before expiry)
    expirationBuffer?: number;
  };
}

Caching Configuration ​

typescript
interface CacheConfig {
  // Time-to-live in milliseconds
  ttl?: number;
  
  // Maximum number of cached queries
  max?: number;
  
  // Cache implementation
  implementation?: 'memory' | 'localStorage' | Cache;
  
  // Custom cache key generator
  keyGenerator?: (query: string) => string;
}

Client Factory Patterns ​

Environment-based Clients ​

Create different clients for different environments:

typescript
// config/clients.ts
import { createClient } from '@webcoded/pgrestify';

const createEnvironmentClient = () => {
  const baseConfig = {
    transformColumns: true,
    auth: { autoRefresh: true }
  };

  switch (process.env.NODE_ENV) {
    case 'development':
      return createClient({
        ...baseConfig,
        url: 'http://localhost:3000',
        cache: { ttl: 60000 } // Short cache for development
      });
      
    case 'production':
      return createClient({
        ...baseConfig,
        url: process.env.PGRESTIFY_API_URL!,
        cache: { ttl: 300000, max: 1000 },
        fetch: {
          timeout: 30000 // Longer timeout for production
        }
      });
      
    case 'test':
      return createClient({
        ...baseConfig,
        url: 'http://localhost:3001',
        cache: { ttl: 0 } // No caching in tests
      });
      
    default:
      throw new Error(`Unknown environment: ${process.env.NODE_ENV}`);
  }
};

export const client = createEnvironmentClient();

Multi-tenant Clients ​

Handle multiple tenants or databases:

typescript
// services/ClientManager.ts
import { createClient, PostgRESTClient } from '@webcoded/pgrestify';

class ClientManager {
  private clients = new Map<string, PostgRESTClient>();
  
  getClient(tenantId: string): PostgRESTClient {
    if (!this.clients.has(tenantId)) {
      const client = createClient({
        url: `https://${tenantId}.api.yourapp.com`,
        transformColumns: true,
        auth: { 
          autoRefresh: true,
          storageKey: `pgrestify.session.${tenantId}`
        }
      });
      
      this.clients.set(tenantId, client);
    }
    
    return this.clients.get(tenantId)!;
  }
  
  clearClient(tenantId: string): void {
    this.clients.delete(tenantId);
  }
}

export const clientManager = new ClientManager();

// Usage
const client = clientManager.getClient('tenant-123');

Singleton Pattern ​

Ensure single client instance across your app:

typescript
// lib/client.ts
import { createClient, PostgRESTClient } from '@webcoded/pgrestify';

class PGRestifyClient {
  private static instance: PostgRESTClient;
  
  static getInstance(): PostgRESTClient {
    if (!PGRestifyClient.instance) {
      PGRestifyClient.instance = createClient({
        url: process.env.NEXT_PUBLIC_PGRESTIFY_URL!,
        transformColumns: true,
        auth: { autoRefresh: true },
        cache: { ttl: 300000, max: 100 }
      });
    }
    
    return PGRestifyClient.instance;
  }
  
  static resetInstance(): void {
    PGRestifyClient.instance = null as any;
  }
}

export const client = PGRestifyClient.getInstance();

Framework-Specific Setup ​

React Applications ​

Set up client with React context:

typescript
// context/PGRestifyContext.tsx
import React, { createContext, useContext } from 'react';
import { createClient, PostgRESTClient } from '@webcoded/pgrestify';

const client = createClient({
  url: process.env.REACT_APP_PGRESTIFY_URL!,
  transformColumns: true,
  auth: { autoRefresh: true }
});

const PGRestifyContext = createContext<PostgRESTClient>(client);

export const PGRestifyProvider: React.FC<{ children: React.ReactNode }> = ({ 
  children 
}) => {
  return (
    <PGRestifyContext.Provider value={client}>
      {children}
    </PGRestifyContext.Provider>
  );
};

export const usePGRestifyClient = () => {
  const context = useContext(PGRestifyContext);
  if (!context) {
    throw new Error('usePGRestifyClient must be used within PGRestifyProvider');
  }
  return context;
};

Next.js Applications ​

App Router Setup ​

typescript
// lib/pgrestify.ts
import { createClient } from '@webcoded/pgrestify';

export const client = createClient({
  url: process.env.NEXT_PUBLIC_PGRESTIFY_URL!,
  transformColumns: true,
  auth: { 
    autoRefresh: true,
    storage: typeof window !== 'undefined' ? 'localStorage' : 'memory'
  }
});

// For server-side usage
export const serverClient = createClient({
  url: process.env.PGRESTIFY_URL!, // Server-side URL (can be internal)
  transformColumns: true,
  // No auth storage on server
});

Pages Router Setup ​

typescript
// lib/pgrestify.ts
import { createClient } from '@webcoded/pgrestify';

const isServer = typeof window === 'undefined';

export const client = createClient({
  url: isServer 
    ? process.env.PGRESTIFY_URL! 
    : process.env.NEXT_PUBLIC_PGRESTIFY_URL!,
  transformColumns: true,
  auth: {
    autoRefresh: !isServer,
    storage: isServer ? 'memory' : 'localStorage'
  }
});

Node.js Applications ​

Server-side client setup:

typescript
// lib/database.ts
import { createClient } from '@webcoded/pgrestify';

export const dbClient = createClient({
  url: process.env.PGRESTIFY_URL!,
  transformColumns: true,
  
  // Server-side specific configuration
  fetch: {
    timeout: 30000,
    headers: {
      'User-Agent': 'MyApp/1.0.0'
    }
  },
  
  // Use memory storage for server
  auth: { storage: 'memory' },
  
  // Longer cache times on server
  cache: { ttl: 600000, max: 500 }
});

// Database health check
export const checkDatabaseHealth = async (): Promise<boolean> => {
  try {
    await dbClient.from('health_check').select('1').limit(1).execute();
    return true;
  } catch {
    return false;
  }
};

Advanced Configuration ​

Custom Fetch Implementation ​

Use your own fetch implementation:

typescript
import { createClient } from '@webcoded/pgrestify';

// Custom fetch with retry logic
const fetchWithRetry = async (url: string, options: RequestInit = {}) => {
  const maxRetries = 3;
  let lastError: Error;
  
  for (let i = 0; i < maxRetries; i++) {
    try {
      const response = await fetch(url, options);
      return response;
    } catch (error) {
      lastError = error as Error;
      if (i < maxRetries - 1) {
        await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i)));
      }
    }
  }
  
  throw lastError!;
};

const client = createClient({
  url: 'https://api.yourapp.com',
  fetch: fetchWithRetry
});

Custom Storage Implementation ​

Implement your own storage for tokens:

typescript
import { createClient } from '@webcoded/pgrestify';

class CustomStorage implements Storage {
  private data = new Map<string, string>();
  
  get length(): number {
    return this.data.size;
  }
  
  getItem(key: string): string | null {
    return this.data.get(key) || null;
  }
  
  setItem(key: string, value: string): void {
    this.data.set(key, value);
  }
  
  removeItem(key: string): void {
    this.data.delete(key);
  }
  
  clear(): void {
    this.data.clear();
  }
  
  key(index: number): string | null {
    const keys = Array.from(this.data.keys());
    return keys[index] || null;
  }
}

const client = createClient({
  url: 'https://api.yourapp.com',
  auth: {
    storage: new CustomStorage()
  }
});

Custom Cache Implementation ​

Implement your own caching strategy:

typescript
import { createClient, QueryCache } from '@webcoded/pgrestify';

class RedisCache implements QueryCache {
  constructor(private redis: any) {} // Redis client
  
  get<T>(key: string): T | null {
    const value = this.redis.get(key);
    return value ? JSON.parse(value) : null;
  }
  
  set<T>(key: string, value: T, ttl?: number): void {
    const serialized = JSON.stringify(value);
    if (ttl) {
      this.redis.setex(key, Math.floor(ttl / 1000), serialized);
    } else {
      this.redis.set(key, serialized);
    }
  }
  
  delete(key: string): void {
    this.redis.del(key);
  }
  
  clear(): void {
    this.redis.flushdb();
  }
  
  invalidate(pattern: string): void {
    // Implement pattern-based invalidation
    const keys = this.redis.keys(pattern);
    if (keys.length > 0) {
      this.redis.del(...keys);
    }
  }
}

const client = createClient({
  url: 'https://api.yourapp.com',
  cache: {
    implementation: new RedisCache(redisClient),
    ttl: 300000
  }
});

Error Handling ​

Connection Error Handling ​

Handle connection and configuration errors:

typescript
import { createClient, PGRestifyError } from '@webcoded/pgrestify';

const createRobustClient = async () => {
  try {
    const client = createClient({
      url: process.env.PGRESTIFY_URL!,
      transformColumns: true
    });
    
    // Test connection
    await client.from('users').select('id').limit(1).execute();
    
    return client;
  } catch (error) {
    if (error instanceof PGRestifyError) {
      console.error('PGRestify configuration error:', error.message);
    } else {
      console.error('Connection error:', error);
    }
    
    throw error;
  }
};

// Usage with error handling
export const initializeClient = async () => {
  try {
    return await createRobustClient();
  } catch (error) {
    // Fallback or retry logic
    console.warn('Failed to create client, using fallback configuration');
    
    return createClient({
      url: 'http://localhost:3000', // Fallback URL
      transformColumns: false,
      cache: { ttl: 0 } // Disable caching on fallback
    });
  }
};

Best Practices ​

Configuration Management ​

typescript
// config/pgrestify.ts
interface AppConfig {
  pgrestify: {
    url: string;
    transformColumns: boolean;
    auth: {
      autoRefresh: boolean;
      storageKey: string;
    };
    cache: {
      ttl: number;
      max: number;
    };
  };
}

const config: AppConfig = {
  pgrestify: {
    url: process.env.PGRESTIFY_URL || 'http://localhost:3000',
    transformColumns: true,
    auth: {
      autoRefresh: true,
      storageKey: 'myapp.session'
    },
    cache: {
      ttl: 300000, // 5 minutes
      max: 100
    }
  }
};

export const client = createClient(config.pgrestify);

Environment Validation ​

typescript
// lib/client.ts
import { createClient } from '@webcoded/pgrestify';

// Validate required environment variables
const requiredEnvVars = ['NEXT_PUBLIC_PGRESTIFY_URL'];
const missingEnvVars = requiredEnvVars.filter(envVar => !process.env[envVar]);

if (missingEnvVars.length > 0) {
  throw new Error(
    `Missing required environment variables: ${missingEnvVars.join(', ')}`
  );
}

export const client = createClient({
  url: process.env.NEXT_PUBLIC_PGRESTIFY_URL!,
  transformColumns: true,
  auth: { autoRefresh: true }
});

Type Safety ​

typescript
// types/database.ts
export interface Database {
  users: {
    id: number;
    email: string;
    name: string;
    created_at: string;
  };
  posts: {
    id: number;
    title: string;
    content: string;
    user_id: number;
    published: boolean;
  };
}

// lib/typed-client.ts
import { createClient } from '@webcoded/pgrestify';
import type { Database } from '../types/database';

export const typedClient = createClient({
  url: process.env.NEXT_PUBLIC_PGRESTIFY_URL!,
  transformColumns: true
});

// Type-safe table access
export const getUsersTable = () => typedClient.from<Database['users']>('users');
export const getPostsTable = () => typedClient.from<Database['posts']>('posts');

Testing Clients ​

Mock Client for Tests ​

typescript
// __tests__/utils/mock-client.ts
import { createClient } from '@webcoded/pgrestify';

export const createMockClient = () => {
  return createClient({
    url: 'http://localhost:3001', // Test database
    transformColumns: true,
    cache: { ttl: 0 }, // No caching in tests
    auth: { storage: 'memory' } // Use memory storage for tests
  });
};

// Usage in tests
describe('User service', () => {
  let client: PostgRESTClient;
  
  beforeEach(() => {
    client = createMockClient();
  });
  
  it('should fetch users', async () => {
    const users = await client.from('users').select('*').execute();
    expect(users.data).toBeDefined();
  });
});

Summary ​

Client creation in PGRestify is flexible and powerful:

  • Simple setup for quick prototyping
  • Rich configuration for production use
  • Framework integrations for popular libraries
  • Advanced patterns for complex architectures
  • Error handling for robust applications
  • Type safety for better development experience

Choose the pattern that best fits your application's needs and scale from simple to complex as your requirements grow.

Released under the MIT License.