Claude
Skills
Sign in
Back

fetch-architecture

Included with Lifetime
$97 forever

# Fetch Architecture Skill

Generalscripts

What this skill does

# Fetch Architecture Skill

Client and server-side fetch utilities for Next.js applications with two distinct data paths: direct backend calls (server) and API route proxying (client).

## When to Use This Skill

Use this skill when asked to:
- Set up fetch utilities for Next.js
- Configure client-side API calls with auth refresh
- Implement server-side data fetching
- Create API route proxies to backend services
- Handle authentication tokens across layers

## Architecture Overview

```
┌─────────────────────────────────────────────────────────────┐
│                    Browser (Client)                          │
│  ┌─────────────────────────────────────────────────────┐    │
│  │  Client Components                                   │    │
│  │  • api.get/post/put/delete                          │    │
│  │  • CSRF via csrfManager                             │    │
│  │  • URL auto-prefix (/setting/x → /api/setting/x)   │    │
│  └──────────────────────────┬──────────────────────────┘    │
└─────────────────────────────┼───────────────────────────────┘
                              │ HTTP (cookies)
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                    Next.js Server                            │
│                                                              │
│  ┌─────────────────────────────────────────────────────┐    │
│  │  API Routes (app/api/...)          ← client only     │    │
│  │  • withAuth(token, forwardHeaders)                   │    │
│  │  • backendFetch() from backend.ts                    │    │
│  │  • Pre-emptive token refresh                         │    │
│  │  • CSRF forwarding                                   │    │
│  └──────────────────────────┬──────────────────────────┘    │
│                             │ HTTP (Bearer token)            │
│  ┌─────────────────────────────────────────────────────┐    │
│  │  Server Actions (lib/actions/)     ← SSR path        │    │
│  │  • directBackendFetch() — calls backend DIRECTLY     │    │
│  │  • getAccessToken() + getBackendURL()                │    │
│  │  • CSRF via getServerCsrfToken() for mutations       │    │
│  └──────────────────────────┬──────────────────────────┘    │
│                             │ HTTP (Bearer token)            │
│  ┌─────────────────────────────────────────────────────┐    │
│  │  Server Components (pages)                           │    │
│  │  • Call server actions for SSR data                  │    │
│  │  • Layout handles auth check                         │    │
│  └─────────────────────────────────────────────────────┘    │
└─────────────────────────────┼───────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                    FastAPI Backend                           │
│  /backend/setting/...  /backend/management/...              │
└─────────────────────────────────────────────────────────────┘
```

## Anti-Patterns (NEVER DO)

Server actions MUST call the backend directly via `directBackendFetch()`.
Routing server actions through Next.js API routes is FORBIDDEN (unnecessary 2-hop overhead).

```typescript
// ❌ WRONG — server action routing through API route (2 hops)
import { serverGet } from '@/lib/fetch/server';
const data = await serverGet('/api/setting/users');

// ❌ WRONG — server action calling API route URL
const res = await fetch('http://localhost:3000/api/setting/users', {
  headers: { Cookie: cookieHeader }
});

// ✅ CORRECT — server action calls backend directly (1 hop)
import { serverGet } from '@/lib/fetch/server';
const data = await serverGet<UsersResponse>('/backend/setting/users');
```

**Rule:** Server actions always use `/backend/...` URLs. API routes are only for client-side requests.

## Directory Structure

```
lib/
├── fetch/
│   ├── index.ts              # Exports
│   ├── client.ts             # Client-side fetch (browser) — exports `api`
│   ├── server.ts             # Server-side fetch (direct backend calls)
│   ├── backend.ts            # Backend fetch (used in API routes only)
│   ├── api-route-helper.ts   # API route wrappers (withAuth)
│   ├── errors.ts             # Error classes
│   └── types.ts              # TypeScript types
└── auth/
    ├── server-auth.ts        # Server authentication (getAccessToken, auth)
    ├── auth-cookies.ts       # Cookie management (getBackendURL)
    ├── auth-service.ts       # Client auth (token refresh)
    ├── csrf-manager.ts       # Client CSRF token management
    ├── redirect-guard.ts     # Redirect loop detection
    └── auth-logger.ts        # Auth event logging
```

## Core Files

### 1. Error Classes

```typescript
// lib/fetch/errors.ts
export class ApiError extends Error {
  constructor(
    message: string,
    public status: number,
    public data?: unknown
  ) {
    super(message);
    this.name = 'ApiError';
  }
}

export function extractErrorMessage(data: unknown, status: number): string {
  if (typeof data === 'string') return data;
  if (typeof data === 'object' && data !== null) {
    const obj = data as Record<string, unknown>;
    if (typeof obj.detail === 'string') return obj.detail;
    if (typeof obj.message === 'string') return obj.message;
    if (typeof obj.error === 'string') return obj.error;
  }
  return 'An error occurred';
}
```

### 2. Type Definitions

```typescript
// lib/fetch/types.ts
export interface FetchOptions {
  headers?: Record<string, string>;
  timeout?: number;
  cache?: RequestCache;
  next?: NextFetchRequestConfig;
}

export interface FetchRequestOptions extends FetchOptions {
  method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
  body?: unknown;
}
```

### 3. Server Fetch (Direct Backend Calls)

```typescript
// lib/fetch/server.ts
"use server";

/**
 * Server-side fetch utilities for server actions.
 * All requests call the backend directly (single hop).
 * Mutations include a CSRF token fetched from the backend.
 */

import { getAccessToken } from '@/lib/auth/server-auth';
import { getBackendURL } from '@/lib/auth/auth-cookies';
import { ApiError, extractErrorMessage } from './errors';
import type { FetchOptions, FetchRequestOptions } from './types';

const DEFAULT_TIMEOUT = 30000;

async function getServerCsrfToken(accessToken: string): Promise<string | null> {
  try {
    const backendUrl = getBackendURL();
    const response = await fetch(`${backendUrl}/backend/csrf-token`, {
      method: 'GET',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${accessToken}`,
      },
      cache: 'no-store',
    });
    if (!response.ok) return null;
    const data = await response.json();
    return data.csrfToken ?? data.csrf_token ?? null;
  } catch {
    return null;
  }
}

async function directBackendFetch<T>(
  url: string,
  options: FetchRequestOptions = {}
): Promise<T> {
  const token = await getAccessToken();
  if (!token) {
    throw new ApiError('Not authenticated', 401);
  }

  const backendUrl = getBackendURL();
  const fullUrl = `${backendUrl}${url}`;
  const method = options.method || 'GET';

  const controller = new AbortController();
  const timeoutId = setTimeout(
    () => controller.abort(),
    options.timeout || DEFAULT_TIMEOUT
  );

  // Fetch CSRF token for state-changing requests
  const csrfHeaders: Record<string, string> = {};
  if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(method)) {
    const csrfToken = await getServerCsrfToken(token);
    if (csrfToken) {
      csrfHeaders['X-CSRF-Token'] = csrfToken;
    }
  }

  try {
    const response = await fetch(fullUrl, {
      method,
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${token}`,
        'X-Request-ID': crypto.randomUUID(),
        ...csrfHeaders,
        ...options.headers,
      },
      body: options.body ? JSON.stringify(options.body) : undefined,
      signal: controller.signal,
      c

Related in General