⚡ Z-Fetch

🤝 All Options

Complete guide to instance configuration options and request-level settings.

Z-Fetch provides comprehensive configuration options that can be set at the instance level for consistent behavior across all requests, or at the request level for specific customization.

Instance Configuration

Set default options when creating an instance:

import { createInstance } from "@z-fetch/fetch";
 
const api = createInstance({
  baseUrl: "https://api.example.com",
  timeout: 30000,
  headers: {
    Accept: "application/json",
    "Content-Type": "application/json",
  },
  withCredentials: true,
  retries: 3,
});

Core Configuration Options

Base URL

Set a base URL for all requests:

const api = createInstance({
  baseUrl: "https://api.example.com",
});
 
// Requests will be made to: https://api.example.com/users
await api.get("/users");
await api.post("/users", { body: userData });

Headers

Set default headers for all requests:

const api = createInstance({
  headers: {
    Accept: "application/json",
    "Content-Type": "application/json",
    "User-Agent": "MyApp/1.0",
    "X-API-Version": "v2",
  },
});

Headers are intelligently merged:

// Instance headers + request headers
await api.post("/users", {
  headers: {
    Authorization: "Bearer token",
    "X-Request-ID": "abc123",
  },
  body: userData,
});
// Final headers include both instance and request headers

Authentication

Bearer Token

Automatic Bearer token authentication:

const api = createInstance({
  bearerToken: "your-jwt-token",
});
 
// Automatically adds: Authorization: Bearer your-jwt-token
await api.get("/protected-endpoint");

Basic Authentication

const api = createInstance({
  headers: {
    Authorization: `Basic ${btoa("username:password")}`,
  },
});

API Key Authentication

const api = createInstance({
  headers: {
    "X-API-Key": "your-api-key",
  },
});

Credentials and CORS

Control how credentials are sent with requests:

const api = createInstance({
  withCredentials: true, // Sets fetch credentials: 'include'
  mode: "cors",
  headers: {
    "Access-Control-Allow-Origin": "*",
  },
});
  • Fetch path: sets credentials: 'include'.
  • XHR path (when using progress callbacks): sets xhr.withCredentials = true.

Advanced Configuration

Timeout

Set request timeout in milliseconds:

const api = createInstance({
  timeout: 30000, // 30 seconds
});
 
// Per-request timeout override
await api.get("/slow-endpoint", {
  timeout: 60000, // 60 seconds for this request
});

Retry Configuration

Configure automatic retries for failed requests:

const api = createInstance({
  retries: 3, // Number of retries
  retryDelay: 1000, // Delay between retries (ms)
  retryCondition: (error) => {
    // Custom retry logic
    return error.status >= 500 || error.message.includes("network");
  },
});

Cache Configuration

Control request and response caching:

const api = createInstance({
  cache: "no-cache", // native fetch cache control
  withCache: true, // Enable Z-Fetch caching for GET
  revalidateCache: 60000, // Revalidate cache every minute
});
  • Successful GET responses are cached when withCache: true.
  • Failed or canceled GETs are not cached; the next call hits the network.
  • Background revalidation preserves existing cache on failure.

Progress Tracking Configuration

Upload Progress

const api = createInstance({
  onUploadProgress: (event) => {
    const percentage = Math.round((event.loaded / event.total) * 100);
    console.log(`Upload: ${percentage}%`);
    updateGlobalUploadProgress(percentage);
  },
});

Download Progress

const api = createInstance({
  onDownloadProgress: (event) => {
    const percentage = Math.round((event.loaded / event.total) * 100);
    console.log(`Download: ${percentage}%`);
    updateGlobalDownloadProgress(percentage);
  },
});

Error Configuration

Error Handling Mode

Choose how errors are handled - returned in result or thrown:

const api = createInstance({
  throwOnError: false, // Default: errors returned in result.error
});
 
// Or use throwing mode for traditional try-catch
const apiWithThrow = createInstance({
  throwOnError: true, // Errors are thrown as exceptions
});
 
// Default mode usage
const result = await api.get("/users");
if (result.error) {
  console.error("Error:", result.error);
}
 
// Throwing mode usage
try {
  const result = await apiWithThrow.get("/users");
  console.log("Data:", result.data);
} catch (error) {
  console.error("Error:", error.message, error.status);
}

Choose Your Style

throwOnError: false (default) is safer and more explicit. throwOnError: true is familiar for developers from fetch/axios. Both work with all features.

Error Mapping

Map errors to user-friendly text. By default, only z-fetch internal errors are mapped:

const api = createInstance({
  throwOnError: true, // Works with error mapping
  errorMapping: {
    // Map z-fetch internal errors (default behavior)
    "fetch failed": "Network connection failed",
    "network error": "Unable to connect to server",
    NetworkError: "Connection lost",
  },
});
 
// Backend HTTP errors use original response.statusText from your API

Optionally enable mapping for backend HTTP errors:

const api = createInstance({
  mapErrors: true, // Enable mapping for backend errors
  errorMapping: {
    // Map backend HTTP status codes
    401: "Please sign in",
    403: "Access denied",
    404: "Not found",
    500: "Server error",
    // Also map z-fetch internal errors
    "fetch failed": "Network connection failed",
  },
});

Backend Error Mapping is Optional

By default (mapErrors: false), only z-fetch internal errors (NETWORK_ERROR, TIMEOUT, CANCELED) are mapped. Set mapErrors: true to also map backend HTTP status codes.

Error Handling Hook

const api = createInstance({
  hooks: {
    onError: (context) => {
      // Global error handling (runs before throwing if throwOnError: true)
      console.error("API Error:", context.error);
      trackError(context.error);
    },
  },
});

Request/Response Processing

JSON Handling

Control automatic JSON parsing:

const api = createInstance({
  parseJson: true, // Auto-parse JSON responses
  stringifyPayload: true, // Auto-stringify request bodies
});
 
// Disable for specific requests
await api.post("/upload", {
  body: formData,
  parseJson: false,
  stringifyPayload: false,
});

Response Processing

const api = createInstance({
  hooks: {
    onResponse: (context) => {
      // Global response processing
      console.log("Response received:", context.response.status);
 
      // Add response metadata
      return {
        data: {
          ...context.data,
          receivedAt: Date.now(),
        },
      };
    },
  },
});

Environment-Based Configuration

Development vs Production

const api = createInstance({
  baseUrl:
    process.env.NODE_ENV === "production"
      ? "https://api.example.com"
      : "https://dev-api.example.com",
 
  timeout:
    process.env.NODE_ENV === "development"
      ? 60000 // Longer timeout in dev
      : 30000,
 
  headers: {
    "X-Environment": process.env.NODE_ENV,
    ...(process.env.NODE_ENV === "development" && {
      "X-Debug": "true",
    }),
  },
});

Configuration Loading

// Load from environment variables
const api = createInstance({
  baseUrl: process.env.REACT_APP_API_URL,
  timeout: parseInt(process.env.REACT_APP_API_TIMEOUT) || 30000,
  bearerToken: process.env.REACT_APP_API_TOKEN,
  headers: {
    "X-API-Key": process.env.REACT_APP_API_KEY,
  },
});

Complete Configuration Example

import { createInstance } from "@z-fetch/fetch";
 
const api = createInstance({
  // Base configuration
  baseUrl: "https://api.example.com",
  timeout: 30000,
 
  // Authentication
  bearerToken: localStorage.getItem("authToken"),
 
  // Headers
  headers: {
    Accept: "application/json",
    "Content-Type": "application/json",
    "X-API-Version": "v2",
    "X-Client-Version": "1.0.0",
  },
 
  // CORS and credentials
  withCredentials: true,
  mode: "cors",
 
  // Error handling
  errorMapping: {
    401: "Authentication required",
    403: "Access denied",
    404: "Resource not found",
    429: "Rate limit exceeded",
    500: "Server error",
    "fetch failed": "Network error",
  },
 
  // Progress tracking
  onUploadProgress: (event) => {
    const percentage = Math.round((event.loaded / event.total) * 100);
    updateUploadProgress(percentage);
  },
 
  onDownloadProgress: (event) => {
    const percentage = Math.round((event.loaded / event.total) * 100);
    updateDownloadProgress(percentage);
  },
 
  // Retry configuration
  retries: 3,
  retryDelay: 1000,
 
  // Cache configuration
  withCache: true,
 
  // Hooks
  hooks: {
    onRequest: (context) => {
      // Add request timestamp
      context.setHeaders((headers) => ({
        ...headers,
        "X-Request-Time": Date.now().toString(),
      }));
 
      // Log requests in development
      if (process.env.NODE_ENV === "development") {
        console.log(`→ ${context.request.method} ${context.request.url}`);
      }
    },
 
    onResponse: (context) => {
      // Log responses in development
      if (process.env.NODE_ENV === "development") {
        console.log(`← ${context.response.status} ${context.request.url}`);
      }
 
      // Add response metadata
      return {
        data: {
          ...context.data,
          meta: {
            requestTime: context.request.headers["X-Request-Time"],
            responseTime: Date.now(),
            status: context.response.status,
          },
        },
      };
    },
 
    onError: (context) => {
      // Log errors
      console.error("API Error:", {
        url: context.request?.url,
        method: context.request?.method,
        status: context.error?.status,
        message: context.error?.message,
      });
 
      // Track errors
      if (typeof gtag !== "undefined") {
        gtag("event", "api_error", {
          error_status: context.error?.status,
          error_url: context.request?.url,
        });
      }
 
      // Handle authentication errors
      if (context.error?.status === 401) {
        localStorage.removeItem("authToken");
        window.location.href = "/login";
      }
    },
  },
});
 
export default api;

Request-Level Overrides

Any instance configuration can be overridden at the request level:

// Override instance settings for specific request
await api.post("/special-endpoint", {
  timeout: 60000, // Override instance timeout
  headers: {
    "X-Special": "true", // Additional headers
  },
  bearerToken: specialToken, // Override instance token
  withCredentials: false, // Override credentials setting
  body: requestData,
});

Configuration Priority

Request-level options always take precedence over instance-level options.