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
| Aspect | DataSource + Store | Server Actions | API Routes |
|---|---|---|---|
| Type Safety | Full TypeScript inference | Full TypeScript inference | Manual types |
| CRUD Operations | Automatic | Manual | Manual |
| Pagination | Built-in | Manual | Manual |
| Caching | Automatic | useQuery caching | None |
| Complex Queries | Limited | Full SQL support | Full control |
| Webhooks | No | No | Yes |
| File Streaming | No | No | Yes |
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 -
useQueryprovides 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
- DataSources - Define your data model
- Stores - Use stores for CRUD
- Server Actions - Complex queries