New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details →
Socket
Book a DemoSign in
Socket

learningpad-api-client

Package Overview
Dependencies
Maintainers
1
Versions
1
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

learningpad-api-client

A powerful, type-safe API client built on top of React Query and Axios with automatic token management and error handling

latest
Source
npmnpm
Version
1.0.0
Version published
Maintainers
1
Created
Source

@learningpad/api-client

A powerful, type-safe API client built on top of React Query and Axios with automatic token management, error handling, and seamless integration with React applications.

Features

  • 🚀 Built on React Query - Leverages the power of TanStack Query for caching, background updates, and more
  • 🔐 Automatic Token Management - Handles access token refresh automatically
  • 🛡️ Type-Safe - Full TypeScript support with comprehensive type definitions
  • 🎯 Service-Oriented - Organize your APIs by service with easy configuration
  • 🔄 Smart Retry Mechanism - Only one token refresh happens even when multiple APIs fail with 401 simultaneously
  • 📱 React Hooks - Easy-to-use hooks for queries and mutations
  • 🎨 Customizable - Flexible configuration and error handling
  • 📦 Zero Dependencies - Only requires React Query and Axios as peer dependencies
  • 🌐 Universal - Works in both browser and Node.js environments

Installation

npm install @learningpad/api-client @tanstack/react-query axios
# or
yarn add @learningpad/api-client @tanstack/react-query axios
# or
pnpm add @learningpad/api-client @tanstack/react-query axios

Quick Start

1. Setup React Query Provider

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ApiConfig } from "@learningpad/api-client";

const queryClient = new QueryClient();

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      {/* Your app components */}
    </QueryClientProvider>
  );
}

2. Initialize API Client

import { ApiConfig } from "@learningpad/api-client";

// Initialize the API client
ApiConfig.initialize({
  services: {
    auth: {
      name: "auth",
      baseURL: "https://api.example.com/auth",
    },
    api: {
      name: "api",
      baseURL: "https://api.example.com/v1",
    },
  },
  defaultTimeout: 30000,
  onUnauthorized: () => {
    // Handle unauthorized access
    window.location.href = "/login";
  },
});

3. Use in Components

import { useApiQuery, useApiMutation } from "@learningpad/api-client";

function UserProfile() {
  // Query data
  const {
    data: user,
    isLoading,
    error,
  } = useApiQuery({
    serviceName: "api",
    key: ["user", "profile"],
    url: "/user/profile",
  });

  // Mutate data
  const updateProfile = useApiMutation({
    serviceName: "api",
    url: "/user/profile",
    method: "put",
    successMessage: "Profile updated successfully!",
  });

  const handleUpdate = (data: any) => {
    updateProfile.mutate(data);
  };

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <h1>{user?.name}</h1>
      <button onClick={() => handleUpdate({ name: "New Name" })}>
        Update Profile
      </button>
    </div>
  );
}

Configuration

Basic Configuration

import { ApiConfig } from "@learningpad/api-client";

ApiConfig.initialize({
  services: {
    // Define your API services
    auth: {
      name: "auth",
      baseURL: "https://auth.example.com",
      timeout: 10000,
      headers: {
        "X-API-Version": "1.0",
      },
    },
    api: {
      name: "api",
      baseURL: "https://api.example.com/v1",
      timeout: 30000,
    },
  },
  defaultTimeout: 30000,
  defaultHeaders: {
    "Content-Type": "application/json",
  },
});

Advanced Configuration with Custom Token Manager

import { ApiConfig, TokenManager } from "@learningpad/api-client";

const customTokenManager: TokenManager = {
  getAccessToken: () => {
    // Your custom token retrieval logic
    return localStorage.getItem("access_token");
  },
  getRefreshToken: () => {
    return localStorage.getItem("refresh_token");
  },
  setAccessToken: (token: string) => {
    localStorage.setItem("access_token", token);
  },
  setRefreshToken: (token: string) => {
    localStorage.setItem("refresh_token", token);
  },
  clearTokens: () => {
    localStorage.removeItem("access_token");
    localStorage.removeItem("refresh_token");
  },
};

const customNotificationManager = {
  success: (message: string) => toast.success(message),
  error: (message: string) => toast.error(message),
  info: (message: string) => toast.info(message),
  warning: (message: string) => toast.warning(message),
};

ApiConfig.initialize({
  services: {
    auth: {
      name: "auth",
      baseURL: "https://auth.example.com",
    },
    api: {
      name: "api",
      baseURL: "https://api.example.com/v1",
    },
  },
  tokenManager: customTokenManager,
  notificationManager: customNotificationManager,
  onUnauthorized: () => {
    // Redirect to login
    window.location.href = "/login";
  },
  onTokenRefresh: (newToken: string) => {
    console.log("Token refreshed:", newToken);
  },
  onTokenRefreshError: (error: Error) => {
    console.error("Token refresh failed:", error);
  },
});

Smart Retry Mechanism

The API client includes an intelligent retry mechanism that prevents multiple token refresh requests when multiple APIs fail with 401 errors simultaneously.

How it works

  • Multiple API calls fail with 401 - When 4 API calls fail with 401 at the same time
  • Only one token refresh - Only the first call triggers a token refresh
  • Other calls wait - The remaining 3 calls wait for the refresh to complete
  • All calls retry - Once the new token is obtained, ALL calls retry with the new token
  • All calls succeed - All calls succeed (assuming the refresh was successful)

Example

// These 4 calls will all fail with 401 simultaneously
// The retry mechanism ensures only ONE token refresh happens
// All 4 calls will wait for that single refresh and then retry
const promises = [
  apiClient.getService("api").get("/users/profile"),
  apiClient.getService("api").get("/users/settings"),
  apiClient.getService("api").get("/users/notifications"),
  apiClient.getService("api").get("/users/preferences"),
];

const responses = await Promise.all(promises);
// All calls will succeed after a single token refresh

Configuration

You can configure the token refresh behavior per service:

ApiConfig.initialize({
  services: {
    auth: {
      name: "auth",
      baseURL: "https://api.example.com/auth",
      // Token refresh configuration
      refreshEndpoint: "/refresh", // Custom refresh endpoint
      refreshMethod: "post", // HTTP method for refresh
      refreshRequestBody: {}, // Payload for refresh request
      refreshTokenHeaderName: "Authorization", // Header name for refresh token
      refreshTokenPrefix: "Bearer", // Prefix for refresh token
      accessTokenPath: "data.access_token", // Path to access token in response
    },
    api: {
      name: "api",
      baseURL: "https://api.example.com/v1",
    },
  },
  // Custom refresh handler (optional)
  onRefreshRequest: async (refreshToken, axiosLib, services) => {
    // Your custom refresh logic
    const response = await axiosLib.post("/custom/refresh", {
      refresh_token: refreshToken,
    });
    return response.data.access_token;
  },
});

API Reference

Hooks

useApiQuery

const { data, isLoading, error, refetch } = useApiQuery({
  serviceName: "api", // Required: Service name
  key: ["users"], // Required: Query key
  url: "/users", // Required: API endpoint
  enabled: true, // Optional: Enable/disable query
  method: "get", // Optional: HTTP method (default: 'get')
  params: { page: 1 }, // Optional: Query parameters
  data: { filter: "active" }, // Optional: Request body (for POST)
  config: {
    // Optional: Axios config
    headers: { "X-Custom": "value" },
  },
  options: {
    // Optional: React Query options
    staleTime: 5 * 60 * 1000, // 5 minutes
    cacheTime: 10 * 60 * 1000, // 10 minutes
  },
});

useApiMutation

const mutation = useApiMutation({
  serviceName: "api", // Required: Service name
  url: "/users", // Required: API endpoint
  method: "post", // Optional: HTTP method (default: 'post')
  keyToInvalidate: ["users"], // Optional: Queries to invalidate on success
  successMessage: "User created successfully!", // Optional: Success message
  errorMessage: "Failed to create user", // Optional: Custom error message
  config: {
    // Optional: Axios config
    headers: { "X-Custom": "value" },
  },
  options: {
    // Optional: React Query mutation options
    onSuccess: (data) => {
      console.log("Success:", data);
    },
    onError: (error) => {
      console.error("Error:", error);
    },
  },
});

// Use the mutation
mutation.mutate({ name: "John Doe", email: "john@example.com" });

Service-Specific Hooks

import { createUseQuery, createUseMutation } from "@learningpad/api-client";

// Create service-specific hooks
const useAuthQuery = createUseQuery("auth");
const useAuthMutation = createUseMutation("auth");

// Use them
const { data: user } = useAuthQuery({
  key: ["user"],
  url: "/me",
});

const login = useAuthMutation({
  url: "/login",
  method: "post",
});

Direct API Methods

import { ApiService } from "@learningpad/api-client";

const apiService = new ApiService("api");

// Direct API calls (outside React components)
const users = await apiService.get("/users");
const newUser = await apiService.post("/users", { name: "John" });
const updatedUser = await apiService.put("/users/1", { name: "Jane" });
const deleted = await apiService.delete("/users/1");

Error Handling

The API client provides comprehensive error handling:

import {
  getErrorMessage,
  isAxiosError,
  getErrorStatus,
} from "@learningpad/api-client";

const { data, error } = useApiQuery({
  serviceName: "api",
  key: ["users"],
  url: "/users",
});

if (error) {
  if (isAxiosError(error)) {
    const status = getErrorStatus(error);
    const message = getErrorMessage(error);

    if (status === 404) {
      console.log("Resource not found");
    } else if (status === 500) {
      console.log("Server error");
    }
  }
}

Utilities

Query Key Helpers

import { createQueryKey } from "@learningpad/api-client";

// Create consistent query keys
const userKey = createQueryKey("user", userId);
const usersKey = createQueryKey("users", { page, limit });

Retry Configuration

import { createRetryConfig } from "@learningpad/api-client";

const retryConfig = createRetryConfig(3, 1000); // 3 retries, 1s base delay

const { data } = useApiQuery({
  serviceName: "api",
  key: ["data"],
  url: "/data",
  options: retryConfig,
});

Cache Configuration

import { createCacheConfig } from "@learningpad/api-client";

const cacheConfig = createCacheConfig(
  5 * 60 * 1000, // 5 minutes stale time
  10 * 60 * 1000 // 10 minutes cache time
);

const { data } = useApiQuery({
  serviceName: "api",
  key: ["data"],
  url: "/data",
  options: cacheConfig,
});

TypeScript Support

The package is fully typed with comprehensive TypeScript definitions:

import {
  UseQueryApiProps,
  UseMutationApiProps,
  ApiError,
} from "@learningpad/api-client";

interface User {
  id: string;
  name: string;
  email: string;
}

interface CreateUserRequest {
  name: string;
  email: string;
}

// Typed query
const { data: users } = useApiQuery<User[]>({
  serviceName: "api",
  key: ["users"],
  url: "/users",
});

// Typed mutation
const createUser = useApiMutation<User, CreateUserRequest>({
  serviceName: "api",
  url: "/users",
  method: "post",
});

Examples

Complete Example with Authentication

import React from "react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import {
  ApiConfig,
  useApiQuery,
  useApiMutation,
} from "@learningpad/api-client";

// Initialize API client
ApiConfig.initialize({
  services: {
    auth: {
      name: "auth",
      baseURL: "https://auth.example.com",
    },
    api: {
      name: "api",
      baseURL: "https://api.example.com/v1",
    },
  },
  onUnauthorized: () => {
    window.location.href = "/login";
  },
});

const queryClient = new QueryClient();

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <UserDashboard />
    </QueryClientProvider>
  );
}

function UserDashboard() {
  const { data: user, isLoading } = useApiQuery({
    serviceName: "api",
    key: ["user"],
    url: "/user/profile",
  });

  const updateProfile = useApiMutation({
    serviceName: "api",
    url: "/user/profile",
    method: "put",
    keyToInvalidate: ["user"],
    successMessage: "Profile updated successfully!",
  });

  if (isLoading) return <div>Loading...</div>;

  return (
    <div>
      <h1>Welcome, {user?.name}!</h1>
      <button
        onClick={() => updateProfile.mutate({ name: "New Name" })}
        disabled={updateProfile.isPending}
      >
        {updateProfile.isPending ? "Updating..." : "Update Profile"}
      </button>
    </div>
  );
}

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT © LearningPad Team

Support

For support, email support@learningpad.com or join our Slack channel.

Keywords

react

FAQs

Package last updated on 06 Oct 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts