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:
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:
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
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
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
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:
// 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:
// 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:
// 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:
// 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
// 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
// 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:
// 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:
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:
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:
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:
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
// 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
// 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
// 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
// __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.