Patterns

Data Fetching Decision Tree

Choose the right data fetching strategy based on your use case.

Decision Tree

Do you need standard CRUD operations?
├─ Yes → Use DataSource + useStore

└─ No → Do you need complex aggregations/joins?
    ├─ Yes → Use Server Actions + useQuery

    └─ No → Do you need webhooks or file streaming?
        └─ Yes → Use API Routes (route.ts)

When to Use What

DataSource + useStore

Use for:

  • Standard CRUD operations (create, read, update, delete)
  • Paginated lists/tables
  • Single-entity operations where a DataSource exists
  • Filtered data that maps to a DataSource

Example:

const store = useStore<Entity>({
  datasourceId: 'Entity',
  page: 'entity-page',
  alias: 'entity-list',
  autoQuery: true,
});

Server Actions + useQuery

Use for:

  • Charts with complex aggregations
  • Reports with custom SQL
  • Multi-table joins for analytics
  • Dashboard statistics
  • Complex business logic with multiple queries

Example:

const result = useQuery('getSalesChart', startDate, endDate, 'month');

API Routes (route.ts)

Use for:

  • Webhooks from external services
  • Public endpoints (no auth required)
  • External API integrations
  • File uploads/downloads with streaming

Example:

export const POST = withDBSessionRoute(async function callback(
  client: PgPoolClient,
  session: Session,
  req: Request,
) {
  const body = await req.json();
  return Response.json({ status: 'OK' });
});

Comparison Table

AspectDataSource + StoreServer ActionsAPI Routes
Type SafetyFull TypeScript inferenceFull TypeScript inferenceManual types
CRUD OperationsAutomaticManualManual
PaginationBuilt-inManualManual
CachingAutomaticuseQuery cachingNone
Complex QueriesLimitedFull SQL supportFull control
WebhooksNoNoYes
File StreamingNoNoYes

Benefits of Server Actions over API Routes

  • Type safety - Full TypeScript inference from action to component
  • Simpler code - No manual fetch calls, error handling built into useQuery
  • Automatic caching - useQuery provides caching and deduplication
  • Colocation - Action logic lives next to the page that uses it
  • Role-based access - Centralized access control in action registry

Examples

DataSource Pattern (Standard CRUD)

// Best for: List of entities with filtering
const store = useStore<Entity>({
  datasourceId: 'Entity',
  page: 'entity-page',
  alias: 'entity-list',
  autoQuery: true,
});

Server Action Pattern (Complex Query)

// Best for: Dashboard with aggregations
const result = useQuery('getDashboardStats', dateRange);

API Route Pattern (Webhook)

// Best for: External service webhooks
export const POST = withDBSessionRoute(async function webhook(
  client: PgPoolClient,
  session: Session,
  req: Request,
) {
  // Handle webhook
});

Next Steps

Previous
Multi-tab Detail