Skip to main content

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: '' }
);