Server Functions
Server Functions are async functions specifically designed for Next.js server-side environments. They provide direct backend API access without client-side state management overhead, making them ideal for API routes, server components, and server actions.
Overview
Server Functions in Intrig come in two variants that serve different use cases:
- Action Functions - High-level functions with automatic error handling, response parsing, and type safety
- Execute Functions - Lower-level functions that return raw response data and headers for advanced use cases
- Environment Integration - Seamless integration with Next.js middleware and environment variables
- Hydration Support - Optional client-side state hydration for server-rendered data
- Type Safety - Full TypeScript support with generated types from your API specifications
Function Types
Action Functions
Action functions are the primary way to interact with APIs from server-side code. They handle response parsing, validation, and error handling automatically.
Naming Convention: {operationName}Action (e.g., getUserAction, createProductAction)
Type Signature:
function {operationName}Action(
body?: RequestBody,
params?: Params,
options?: AsyncRequestOptions
): Promise<ResponseType>
Execute Functions
Execute functions provide lower-level access with raw response data and headers, useful when you need access to response metadata.
Naming Convention: execute{OperationName} (e.g., executeGetUser, executeCreateProduct)
Type Signature:
function execute{OperationName}(
body?: RequestBody,
params?: Params,
options?: AsyncRequestOptions
): Promise<{ data: any, headers: Record<string, string> }>
Basic Usage
Action Functions - Simple API Calls
// app/api/users/route.ts
import { getUserAction } from '@intrig/next/server/userApi/getUser/action';
export async function GET(request: Request) {
try {
const user = await getUserAction({ id: '123' });
return Response.json(user);
} catch (error) {
return Response.json({ error: error.message }, { status: 500 });
}
}
Execute Functions - Raw Response Access
// app/api/users/route.ts
import { executeGetUser } from '@intrig/next/server/userApi/getUser/execute';
export async function GET(request: Request) {
try {
const { data, headers } = await executeGetUser({ id: '123' });
return Response.json(data, {
headers: {
'X-Cache-Status': headers['x-cache-status'] || 'miss',
'Content-Type': 'application/json',
},
});
} catch (error) {
return Response.json({ error: error.message }, { status: 500 });
}
}
Server Components
// app/dashboard/page.tsx
import { getUserAction, getOrdersAction } from '@intrig/next/server';
interface DashboardPageProps {
params: { userId: string };
}
export default async function DashboardPage({ params }: DashboardPageProps) {
try {
// Parallel data fetching
const [user, orders] = await Promise.all([
getUserAction({ id: params.userId }),
getOrdersAction({ userId: params.userId }),
]);
return (
<div>
<h1>Welcome, {user.name}</h1>
<div>
<h2>Recent Orders</h2>
{orders.map(order => (
<div key={order.id}>{order.title}</div>
))}
</div>
</div>
);
} catch (error) {
return <div>Error loading dashboard: {error.message}</div>;
}
}
Advanced Configuration
AsyncRequestOptions
Both action and execute functions accept an optional options parameter:
interface AsyncRequestOptions {
hydrate?: boolean; // Cache response for client-side hydration
key?: string; // Custom cache key (when hydrate is true)
}
Hydration for Client-Side Access
// app/profile/page.tsx - Server Component
import { getUserAction } from '@intrig/next/server/userApi/getUser/action';
export default async function ProfilePage({ params }) {
// Fetch on server and hydrate for client-side access
const user = await getUserAction(
{ id: params.userId },
{ hydrate: true, key: `user-${params.userId}` }
);
return <div><h1>{user.name}</h1></div>;
}
Error Handling Patterns
// app/api/users/route.ts
import { getUserAction } from '@intrig/next/server/userApi/getUser/action';
import { isAxiosError } from 'axios';
export async function GET(request: Request) {
try {
const user = await getUserAction({ id: '123' });
return Response.json(user);
} catch (error) {
if (isAxiosError(error)) {
// Handle HTTP errors
const status = error.response?.status || 500;
const message = error.response?.data?.message || 'API Error';
return Response.json(
{ error: message },
{ status }
);
} else if (error instanceof Error && error.name === 'ValidationError') {
// Handle validation errors
return Response.json(
{ error: 'Invalid response format' },
{ status: 502 }
);
} else {
// Handle unexpected errors
console.error('Unexpected error:', error);
return Response.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}
}
Integration Patterns
With Middleware Headers
// app/api/protected/route.ts
import { getUserAction } from '@intrig/next/server/userApi/getUser/action';
import { getHeaders } from '@intrig/next';
export async function GET() {
const headers = await getHeaders();
const userId = headers['X-User-ID'];
if (!userId) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
try {
// User ID is automatically available from middleware
const user = await getUserAction({ id: userId });
return Response.json(user);
} catch (error) {
return Response.json({ error: error.message }, { status: 500 });
}
}
Server Actions (App Router)
// app/actions.ts
'use server';
import { createUserAction } from '@intrig/next/server';
import { revalidatePath } from 'next/cache';
export async function createUser(formData: FormData) {
try {
const userData = {
name: formData.get('name') as string,
email: formData.get('email') as string,
};
const user = await createUserAction(userData);
revalidatePath('/users');
return { success: true, user };
} catch (error) {
return { success: false, error: error.message };
}
}
Type Safety
Server functions automatically validate responses against generated schemas:
// app/api/validated/route.ts
import { getUserAction } from '@intrig/next/server/userApi/getUser/action';
export async function GET() {
try {
// Response is automatically validated against the schema
const user = await getUserAction({ id: '123' });
// TypeScript knows the exact shape of user
console.log(user.name); // ✅ Type-safe
console.log(user.email); // ✅ Type-safe
console.log(user.invalid); // ❌ TypeScript error
return Response.json(user);
} catch (error) {
if (error.name === 'ValidationError') {
// Handle schema validation errors
return Response.json(
{ error: 'Invalid API response format' },
{ status: 502 }
);
}
throw error;
}
}
Performance Considerations
Parallel Requests
// app/dashboard/page.tsx
export default async function Dashboard() {
// Fetch multiple resources in parallel
const [user, orders, notifications] = await Promise.allSettled([
getUserAction({ id: 'current' }),
getOrdersAction({ limit: 10 }),
getNotificationsAction({ unread: true }),
]);
return (
<div>
{user.status === 'fulfilled' && (
<UserProfile user={user.value} />
)}
{orders.status === 'fulfilled' && (
<OrdersList orders={orders.value} />
)}
{notifications.status === 'fulfilled' && (
<NotificationsList notifications={notifications.value} />
)}
</div>
);
}
Error Handling
Server functions can throw several types of errors:
// app/api/users/route.ts
import { getUserAction } from '@intrig/next/server/userApi/getUser/action';
import { isAxiosError } from 'axios';
export async function GET() {
try {
const user = await getUserAction({ id: '123' });
return Response.json(user);
} catch (error) {
// Network errors (HTTP errors, connection issues, timeouts)
if (isAxiosError(error)) {
const status = error.response?.status || 500;
return Response.json({ error: error.response?.data }, { status });
}
// Validation errors (response doesn't match schema)
if (error.name === 'ValidationError') {
return Response.json({ error: 'Invalid API response' }, { status: 502 });
}
// Configuration errors (missing environment variables)
if (error.message.includes('API_URL is not defined')) {
return Response.json({ error: 'Configuration error' }, { status: 500 });
}
return Response.json({ error: 'Internal server error' }, { status: 500 });
}
}
Best Practices
Error Boundary Pattern
async function safeApiCall<T>(
apiCall: () => Promise<T>,
fallback: T
): Promise<T> {
try {
return await apiCall();
} catch (error) {
console.error('API call failed:', error);
return fallback;
}
}
// Usage
const user = await safeApiCall(
() => getUserAction({ id: '123' }),
{ name: 'Unknown User', email: '' }
);
Related
- IntrigLayout - Learn about client-side layout configuration
- Middleware - Understand header injection and preprocessing
- Stateful Hook - Compare with client-side data fetching
- Core Concepts: Server-Client Architecture - Architectural overview