Skip to content

Custom Schemas ​

PGRestify provides comprehensive support for custom PostgreSQL schemas, allowing you to organize your database structure according to your application's needs. This guide covers schema configuration, multi-schema applications, and best practices.

Overview ​

PostgreSQL schemas provide a way to organize database objects into logical groups. PGRestify and PostgREST can work with single or multiple schemas, giving you flexibility in how you structure your database.

Common Schema Patterns ​

  • Single Schema: All tables in one schema (typically api or public)
  • Multi-Schema: Different schemas for different purposes (api, auth, admin)
  • Domain-Driven: Schemas organized by business domains (users, products, orders)
  • Environment-Based: Different schemas for different environments

Schema Configuration ​

PostgREST Schema Configuration ​

Configure which schemas PostgREST exposes through your postgrest.conf:

conf
# Single schema
db-schemas = "api"

# Multiple schemas (comma-separated)
db-schemas = "public,api,auth"

# Multiple schemas with different access levels
db-schemas = "api,admin,reporting"

PGRestify Integration ​

PGRestify automatically reads your PostgREST schema configuration:

typescript
// Client automatically uses configured schemas
const client = createClient({
  url: 'http://localhost:3000',
  // Schema configuration is read from PostgREST
});

// Access tables from different schemas
const users = await client.from('users').select('*'); // api.users
const profiles = await client.from('profiles').select('*'); // api.profiles

Setting Up Custom Schemas ​

CLI Commands ​

Use the PGRestify CLI to manage schemas:

bash
# Initialize project with custom schema
pgrestify api init --schema custom_schema

# Generate schema structure
pgrestify api schema generate --schema business_logic

# Pull existing schema
pgrestify api pull --schema existing_schema

# Apply changes to specific schema  
pgrestify api apply --schema target_schema

Manual Schema Setup ​

Create schemas manually in PostgreSQL:

sql
-- Create custom schemas
CREATE SCHEMA IF NOT EXISTS api;
CREATE SCHEMA IF NOT EXISTS auth;
CREATE SCHEMA IF NOT EXISTS admin;
CREATE SCHEMA IF NOT EXISTS business;

-- Grant permissions to PostgREST roles
GRANT USAGE ON SCHEMA api TO web_anon, authenticated;
GRANT USAGE ON SCHEMA auth TO authenticated;
GRANT USAGE ON SCHEMA admin TO web_admin;
GRANT USAGE ON SCHEMA business TO authenticated, web_admin;

Multi-Schema Applications ​

Schema Organization Patterns ​

By Application Layer ​

sql
-- API layer (public-facing)
CREATE SCHEMA api;
CREATE TABLE api.users (...);
CREATE TABLE api.posts (...);

-- Authentication layer
CREATE SCHEMA auth;
CREATE TABLE auth.sessions (...);
CREATE TABLE auth.refresh_tokens (...);

-- Administrative layer
CREATE SCHEMA admin;
CREATE TABLE admin.audit_logs (...);
CREATE TABLE admin.system_config (...);

By Business Domain ​

sql
-- User management domain
CREATE SCHEMA users;
CREATE TABLE users.profiles (...);
CREATE TABLE users.preferences (...);

-- E-commerce domain
CREATE SCHEMA commerce;
CREATE TABLE commerce.products (...);
CREATE TABLE commerce.orders (...);

-- Content management domain
CREATE SCHEMA content;
CREATE TABLE content.articles (...);
CREATE TABLE content.media (...);

PostgREST Configuration for Multi-Schema ​

conf
# postgrest.conf for multi-schema setup
db-schemas = "api,users,commerce,content"
db-anon-role = "web_anon"
db-authenticated-role = "authenticated"

# Optional: Default schema for unqualified names
db-schema = "api"

Client Usage with Multiple Schemas ​

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

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

// Access tables from different schemas
// PostgREST automatically handles schema routing

// Default schema (api)
const posts = await client.from('posts').select('*');

// If PostgREST exposes multiple schemas,
// they're all accessible through the same endpoint
const users = await client.from('users').select('*');
const products = await client.from('products').select('*');

Schema-Specific Configuration ​

Environment-Based Schemas ​

Configure different schemas for different environments:

typescript
// config/database.ts
const schemaConfig = {
  development: 'dev_api',
  staging: 'staging_api', 
  production: 'api'
};

const currentSchema = schemaConfig[process.env.NODE_ENV || 'development'];

Schema-Specific Permissions ​

Set up different permission levels per schema:

sql
-- Public API schema - limited access
GRANT USAGE ON SCHEMA api TO web_anon;
GRANT SELECT ON ALL TABLES IN SCHEMA api TO web_anon;

-- Internal schema - authenticated access only
GRANT USAGE ON SCHEMA internal TO authenticated;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA internal TO authenticated;

-- Admin schema - admin access only
GRANT ALL ON SCHEMA admin TO web_admin;
GRANT ALL ON ALL TABLES IN SCHEMA admin TO web_admin;

Schema Management with CLI ​

Generating Schema Structure ​

bash
# Generate complete schema with tables, functions, and policies
pgrestify api schema generate \
  --schema business_logic \
  --include-tables \
  --include-functions \
  --include-policies

# Generate specific components
pgrestify api schema generate \
  --schema auth \
  --tables-only \
  --output sql/auth-schema.sql

Schema Validation ​

bash
# Validate schema configuration
pgrestify api schema validate --schema api

# Check schema consistency
pgrestify api schema validate --all-schemas

# Validate against PostgREST requirements
pgrestify api schema validate --postgrest-compatibility

Schema Migration ​

bash
# Migrate specific schema
pgrestify api migrate --schema target_schema

# Migrate all configured schemas  
pgrestify api migrate --all-schemas

# Dry run migration
pgrestify api migrate --schema api --dry-run

Cross-Schema Relationships ​

Foreign Keys Across Schemas ​

sql
-- Reference table in different schema
CREATE TABLE api.posts (
  id SERIAL PRIMARY KEY,
  title TEXT NOT NULL,
  author_id INTEGER REFERENCES users.profiles(id),
  category_id INTEGER REFERENCES content.categories(id)
);

-- Set up proper permissions for cross-schema access
GRANT SELECT ON users.profiles TO authenticated;
GRANT SELECT ON content.categories TO web_anon;

Views Spanning Multiple Schemas ​

sql
-- Create view combining data from multiple schemas
CREATE VIEW api.post_details AS
SELECT 
  p.id,
  p.title,
  p.content,
  u.username,
  c.name AS category_name
FROM api.posts p
JOIN users.profiles u ON p.author_id = u.id
JOIN content.categories c ON p.category_id = c.id;

-- Grant access to the view
GRANT SELECT ON api.post_details TO web_anon;

Functions Accessing Multiple Schemas ​

sql
-- Function that works across schemas
CREATE OR REPLACE FUNCTION api.get_user_posts(user_id INTEGER)
RETURNS TABLE(
  post_id INTEGER,
  title TEXT,
  username TEXT
)
LANGUAGE sql SECURITY DEFINER
AS $$
  SELECT 
    p.id,
    p.title,
    u.username
  FROM api.posts p
  JOIN users.profiles u ON p.author_id = u.id
  WHERE u.id = user_id;
$$;

Schema Security and Access Control ​

Row Level Security with Schemas ​

sql
-- Enable RLS on tables in specific schemas
ALTER TABLE api.posts ENABLE ROW LEVEL SECURITY;
ALTER TABLE users.profiles ENABLE ROW LEVEL SECURITY;

-- Schema-aware RLS policies
CREATE POLICY "api_posts_select" ON api.posts
  FOR SELECT TO web_anon
  USING (published = true);

CREATE POLICY "users_own_profile" ON users.profiles
  FOR ALL TO authenticated
  USING (id = current_user_id());

Schema-Based Role Management ​

sql
-- Create schema-specific roles
CREATE ROLE api_read NOLOGIN;
CREATE ROLE api_write NOLOGIN;
CREATE ROLE users_admin NOLOGIN;

-- Grant schema access to roles
GRANT USAGE ON SCHEMA api TO api_read, api_write;
GRANT SELECT ON ALL TABLES IN SCHEMA api TO api_read;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA api TO api_write;

-- Grant admin access to users schema
GRANT ALL ON SCHEMA users TO users_admin;

Best Practices ​

Schema Organization ​

sql
-- Good: Clear separation of concerns
CREATE SCHEMA api;      -- Public API endpoints
CREATE SCHEMA auth;     -- Authentication/authorization
CREATE SCHEMA internal; -- Internal operations
CREATE SCHEMA audit;    -- Audit logging

-- Avoid: Too many schemas or unclear purposes
CREATE SCHEMA misc;     -- Unclear purpose
CREATE SCHEMA temp;     -- Temporary schemas in production

Naming Conventions ​

sql
-- Use descriptive schema names
users_management    -- Clear purpose
product_catalog     -- Business domain
financial_reports   -- Functional area

-- Avoid generic names
data               -- Too generic
stuff              -- Meaningless
app                -- Unclear scope

Permission Management ​

sql
-- Set default privileges for future objects
ALTER DEFAULT PRIVILEGES IN SCHEMA api 
  GRANT SELECT ON TABLES TO web_anon;

ALTER DEFAULT PRIVILEGES IN SCHEMA api 
  GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO authenticated;

-- Use explicit grants rather than broad permissions
GRANT USAGE ON SCHEMA specific_schema TO specific_role;
-- Rather than
GRANT ALL ON SCHEMA specific_schema TO public;

Advanced Schema Patterns ​

Tenant-Based Schemas ​

sql
-- Multi-tenant application with schema per tenant
CREATE SCHEMA tenant_company_a;
CREATE SCHEMA tenant_company_b;

-- Shared schema for common data
CREATE SCHEMA shared;
CREATE TABLE shared.countries (...);
CREATE TABLE shared.currencies (...);

-- Tenant-specific data
CREATE TABLE tenant_company_a.users (...);
CREATE TABLE tenant_company_b.users (...);

Versioned Schemas ​

sql
-- API versioning through schemas
CREATE SCHEMA api_v1;
CREATE SCHEMA api_v2;

-- Gradual migration between versions
CREATE VIEW api_v1.users AS 
  SELECT id, name, email FROM api_v2.users;

-- Configure PostgREST for specific version
-- db-schemas = "api_v2,shared"

Microservice Schemas ​

sql
-- Each microservice gets its own schema
CREATE SCHEMA user_service;
CREATE SCHEMA order_service;
CREATE SCHEMA payment_service;

-- Shared reference data
CREATE SCHEMA shared;

-- Cross-service communication through views/functions
CREATE VIEW order_service.user_details AS
  SELECT id, name, email FROM user_service.users;

Troubleshooting ​

Schema Not Found ​

bash
# Check available schemas
SELECT schema_name FROM information_schema.schemata;

# Verify PostgREST configuration
pgrestify api config show

# Update schema configuration
pgrestify api config update --schema "api,users,content"

Permission Denied ​

sql
-- Check schema permissions
SELECT 
  schema_name,
  grantee,
  privilege_type
FROM information_schema.schema_privileges
WHERE schema_name IN ('api', 'users', 'auth');

-- Grant missing permissions
GRANT USAGE ON SCHEMA api TO web_anon;

Cross-Schema References ​

sql
-- Ensure all referenced schemas are accessible
-- Check foreign key constraints
SELECT 
  tc.table_name,
  tc.constraint_name,
  tc.constraint_type,
  kcu.table_schema,
  kcu.table_name AS referenced_table,
  kcu.column_name AS referenced_column
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu 
  ON tc.constraint_name = kcu.constraint_name
WHERE tc.constraint_type = 'FOREIGN KEY';

Migration Examples ​

Single to Multi-Schema Migration ​

sql
-- Step 1: Create new schemas
CREATE SCHEMA api;
CREATE SCHEMA auth;

-- Step 2: Move tables to appropriate schemas
ALTER TABLE users SET SCHEMA api;
ALTER TABLE sessions SET SCHEMA auth;

-- Step 3: Update foreign key references
ALTER TABLE posts 
  ADD CONSTRAINT fk_author 
  FOREIGN KEY (author_id) REFERENCES api.users(id);

-- Step 4: Update PostgREST configuration
-- db-schemas = "api,auth"

-- Step 5: Update application code if needed

Schema Consolidation ​

sql
-- Move tables from multiple schemas to single schema
ALTER TABLE schema_a.table1 SET SCHEMA consolidated;
ALTER TABLE schema_b.table2 SET SCHEMA consolidated;
ALTER TABLE schema_c.table3 SET SCHEMA consolidated;

-- Update references
-- Update PostgREST configuration
-- db-schemas = "consolidated"

-- Drop empty schemas
DROP SCHEMA schema_a CASCADE;
DROP SCHEMA schema_b CASCADE;
DROP SCHEMA schema_c CASCADE;

Next Steps ​

Released under the MIT License.