Skip to content

Table Joining

PGRestify provides powerful and intuitive table joining capabilities, leveraging PostgreSQL's embedded resources feature.

Basic Joins

PostgREST Native Syntax

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

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

// One-to-Many Join
const usersWithPosts = await client
  .from('users')
  .select(`
    id, 
    name, 
    email,
    posts:posts(id, title, content)
  `)
  .find();

Relations Array Syntax

For a more declarative approach, you can use the relations array to specify which tables to join:

typescript
// Simple relation join
const usersWithProfile = await client
  .from('users')
  .select(['id', 'name', 'email', 'profile.bio', 'profile.avatar_url'])
  .relations(['profile'])
  .find();

// Multiple relations
const usersWithPostsAndComments = await client
  .from('users')
  .select(['id', 'name', 'posts.title', 'posts.content', 'comments.text'])
  .relations(['posts', 'comments'])
  .find();

// Nested relations
const usersWithPostComments = await client
  .from('users')
  .select(['id', 'name', 'posts.title', 'posts.comments.text'])
  .relations(['posts.comments'])
  .find();

Advanced Relations Usage

typescript
// Relations with filtering
const activeUsersWithRecentPosts = await client
  .from('users')
  .select(['id', 'name', 'posts.title', 'posts.created_at'])
  .relations(['posts'])
  .eq('active', true)
  .gte('posts.created_at', '2024-01-01')
  .find();

// Relations with custom select and ordering
const usersWithTopPosts = await client
  .from('users')
  .select(['id', 'name', 'posts.title', 'posts.upvotes'])
  .relations(['posts'])
  .order('posts.upvotes', { ascending: false })
  .limit(10)
  .find();

// Relations with aggregation
const usersWithPostCount = await client
  .from('users')
  .select(['id', 'name'])
  .relations(['posts'])
  .aggregate('posts', 'count')
  .find();

Nested Joins

typescript
// Multi-level nested joins
const complexJoin = await client
  .from('users')
  .select(`
    id, 
    name,
    profile:profiles(bio, avatar_url),
    posts:posts(
      id, 
      title, 
      comments:comments(
        id, 
        content, 
        author:users(name, email)
      )
    )
  `)
  .find();

Filtering Joined Resources

typescript
// Filter joined resources
const filteredJoin = await client
  .from('users')
  .select(`
    id, 
    name,
    posts:posts!inner(
      id, 
      title, 
      content
    )
  `)
  .gte('posts.created_at', '2023-01-01')
  .find();

Aggregated Joins

typescript
// Aggregate joined resources
const userStats = await client
  .from('users')
  .select(`
    id, 
    name,
    posts:posts(count),
    comments:comments(count)
  `)
  .find();

Conditional Joins

typescript
// Conditional join with filtering
const conditionalJoin = await client
  .from('orders')
  .select(`
    id,
    total,
    customer:users!inner(
      id, 
      name, 
      email
    ),
    items:order_items(
      product:products(name, price)
    )
  `)
  .eq('status', 'completed')
  .find();

Type-Safe Joins

typescript
interface User {
  id: number;
  name: string;
}

interface Post {
  id: number;
  user_id: number;
  title: string;
  content: string;
}

// Type-safe join with generics
const typeSafeJoin = await client
  .from<User>('users')
  .select(`
    id, 
    name,
    posts:posts(id, title, content)
  `)
  .find();

Advanced Join Techniques

Outer Joins

typescript
// Left outer join
const usersWithOptionalPosts = await client
  .from('users')
  .select(`
    id, 
    name,
    posts:posts(id, title)!left
  `)
  .find();

Computed Columns in Joins

typescript
// Computed columns in joined resources
const joinWithComputation = await client
  .from('users')
  .select(`
    id, 
    name,
    posts:posts(
      id, 
      title, 
      total_comments:comments(count)
    )
  `)
  .find();

Performance Optimization

typescript
// Optimize joins by selecting specific fields
const optimizedJoin = await client
  .from('users')
  .select(`
    id, 
    name,
    posts:posts(id, title)
  `)
  .limit(100)  // Limit result set
  .find();

Error Handling

typescript
try {
  const joinedData = await client
    .from('users')
    .select(`
      id, 
      name, 
      posts:posts(id, title)
    `)
    .find();
} catch (error) {
  if (error.name === 'JoinError') {
    console.log('Join operation failed:', error.message);
  }
}

Best Practices

  • Use inner joins (!inner) for strict filtering
  • Select only necessary fields to reduce payload
  • Be mindful of join complexity
  • Create appropriate indexes on join columns
  • Use limit and pagination for large datasets

Advanced Configuration

typescript
const client = createClient({
  joins: {
    // Global join configuration
    maxDepth: 3,  // Maximum join nesting level
    defaultJoinType: 'left'
  }
});

Performance Considerations

  • Joins can be computationally expensive
  • Use indexes on join columns
  • Limit the depth and breadth of joins
  • Consider denormalization for complex join scenarios
  • Monitor query performance and execution time

Security Implications

typescript
// Role-based join access
const secureJoin = await client
  .from('users')
  .select(`
    id, 
    name,
    posts:posts(id, title)
  `)
  .withRole('authenticated')
  .find();

Combining with Other Features

typescript
// Join with filtering, sorting, and pagination
const complexJoinQuery = await client
  .from('users')
  .select(`
    id, 
    name,
    posts:posts(id, title, content)
  `)
  .eq('active', true)
  .order('posts.created_at', { ascending: false })
  .paginate({ page: 1, pageSize: 10 })
  .executeWithPagination();

Released under the MIT License.