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

@appello/services

Package Overview
Dependencies
Maintainers
3
Versions
43
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@appello/services

Services package with api / graphql

latest
Source
npmnpm
Version
5.0.0
Version published
Maintainers
3
Created
Source

Frontend Services Library

A comprehensive TypeScript library providing reusable service utilities for handling API requests, GraphQL operations, and data fetching across web and mobile React applications.

📋 Table of Contents

  • ✨ Features
  • 📦 Installation
  • 🚀 Quick Start
  • 🏗️ Core Services
  • 🔗 Integration Examples
  • 🛠️ Utilities
  • ⚙️ Configuration
  • 💡 Complete Example
  • ❓ Troubleshooting

✨ Features

  • 🌐 REST API Service - Axios-based with automatic token refresh
  • 🔗 GraphQL Client - Apollo Client with auth, error handling, and subscriptions
  • 🏪 RTK Query Integration - Redux Toolkit Query base query
  • 🔄 React Query Support - TanStack React Query client factory
  • 🔐 Authentication - JWT token management with refresh logic
  • ⚠️ Error Handling - Standardized error processing for forms
  • 🎯 TypeScript - Full type safety and IntelliSense support
  • 📱 Cross-Platform - Works with React web and React Native

📦 Installation

npm install @appello/services

Peer Dependencies:

npm install @appello/common

🚀 Quick Start

Basic Setup

import { createApiService, createGqlClient } from '@appello/services';

// REST API Service
const apiService = createApiService({
  url: 'https://api.example.com',
  getAccessToken: () => localStorage.getItem('accessToken'),
  getRefreshToken: () => localStorage.getItem('refreshToken'),
  onTokenRefreshSuccess: tokens => {
    localStorage.setItem('accessToken', tokens.accessToken);
    localStorage.setItem('refreshToken', tokens.refreshToken);
  },
  onTokenRefreshError: () => {
    // Handle logout
  },
  refreshTokenUrl: '/auth/refresh',
});

// GraphQL Client
const gqlClient = createGqlClient({
  url: 'https://api.example.com/graphql',
  wsUrl: 'wss://api.example.com/graphql',
  getAccessToken: () => localStorage.getItem('accessToken'),
  getRefreshToken: () => localStorage.getItem('refreshToken'),
  onTokenRefreshSuccess: tokens => {
    localStorage.setItem('accessToken', tokens.accessToken);
    localStorage.setItem('refreshToken', tokens.refreshToken);
  },
  onTokenRefreshError: () => {
    // Handle logout
  },
  refreshTokens: async (client, context) => {
    // Custom refresh logic
  },
});

Error Handling

import { createProcessApiErrorResponse } from '@appello/services';

const processApiError = createProcessApiErrorResponse({
  onGlobalError: message => toast.error(message),
  onUnknownErrors: message => console.error(message),
});

🏗️ Core Services

ServicePurposeFactory Function
----------------------------------------------------------------------------------
REST APIHTTP requests with auth & error handlingcreateApiService(config)
GraphQLApollo Client with subscriptions & authcreateGqlClient(config)
React QueryTanStack React Query clientcreateQueryClient(config)

🔗 Integration Examples

🏪 RTK Query Integration

import { createApi } from '@reduxjs/toolkit/query/react';
import { axiosBaseQuery, handleRtkQueryError } from '@appello/services';

export const api = createApi({
  baseQuery: axiosBaseQuery({ api: apiService }),
  endpoints: builder => ({
    login: builder.mutation({
      query: credentials => ({
        url: '/auth/login',
        method: 'POST',
        data: credentials,
      }),
    }),
  }),
});

// Usage
const [login] = api.useLoginMutation();

try {
  await login(credentials).unwrap();
} catch (error) {
  processError({ errors: handleRtkQueryError(error) });
}

🔄 React Query Integration

import { useMutation } from '@tanstack/react-query';
import { handleApiRequestError } from '@appello/services';

const mutation = useMutation({
  mutationFn: credentials => apiService.post('/auth/login', credentials),
  onError: error => {
    processError({ errors: handleApiRequestError({ error }) });
  },
});

🎯 Form Integration

import { useForm } from 'react-hook-form';

const { handleSubmit, setError } = useForm();

const onSubmit = async data => {
  try {
    await apiService.post('/auth/login', data);
  } catch (error) {
    processError({
      errors: handleApiRequestError({ error }),
      fields: ['username', 'password'],
      setFormError: setError,
    });
  }
};

🌐 GraphQL Setup

import { createGqlClient } from '@appello/services';

const gqlClient = createGqlClient({
  url: 'https://api.example.com/graphql',
  wsUrl: 'wss://api.example.com/graphql',
  getAccessToken: () => localStorage.getItem('accessToken'),
  getRefreshToken: () => localStorage.getItem('refreshToken'),
  onTokenRefreshSuccess: tokens => {
    localStorage.setItem('accessToken', tokens.accessToken);
    localStorage.setItem('refreshToken', tokens.refreshToken);
  },
  onTokenRefreshError: () => {
    window.location.href = '/login';
  },
  refreshTokens: async (client, context) => {
    const { data } = await client.mutate({
      mutation: REFRESH_TOKEN_MUTATION,
      context,
    });
    return data.refreshToken;
  },
});

🛠️ Utilities

Error Handling Functions

FunctionPurposeUse Case
handleApiRequestError({ error })Process REST API errorsConvert Axios errors to user-friendly format
handleRtkQueryError(error)Process RTK Query errorsExtract error data from RTK Query responses
createProcessApiErrorResponse(config)Form error processorIntegrate API errors with React Hook Form
createProcessGqlErrorResponse(config)GraphQL error processorHandle GraphQL errors in forms

Authentication Utilities

🔐 setAuthorizationHeader

Adds Bearer token to request headers without mutating the original headers object.

import { setAuthorizationHeader } from '@appello/services';

const headers = { 'Content-Type': 'application/json' };
const token = 'my-jwt-token';

const newHeaders = setAuthorizationHeader(token, headers);
// Result: { 'Content-Type': 'application/json', Authorization: 'Bearer my-jwt-token' }

Parameters:

  • token: string - The access token
  • headers: AxiosRequestHeaders - Existing headers object

Returns: AxiosRequestHeaders - New headers object with Authorization header

📊 getGqlAuthorizationHeader

Creates an authorization header object for GraphQL requests.

import { getGqlAuthorizationHeader } from '@appello/services';

const authHeader = getGqlAuthorizationHeader('my-jwt-token');
// Result: { Authorization: 'Bearer my-jwt-token' }

Parameters:

  • token: string - The access token

Returns: { Authorization: string } - Authorization header object

Token Management

🔄 refreshTokens

Handles JWT token refresh for expired authentication tokens with sophisticated queuing and retry mechanisms.

// This function is typically used internally by the API service
// but can be customized through ApiServiceConfig

Purpose: Implements a comprehensive token refresh mechanism that prevents race conditions by ensuring only one refresh operation runs at a time, while queuing additional requests until the refresh completes.

Features:

  • Race Condition Prevention: Only one refresh operation runs at a time
  • Request Queuing: Queues subsequent requests during refresh
  • Custom Refresh Logic: Supports both endpoint-based and custom refresh functions
  • Automatic Retry: Retries failed requests after successful token refresh
  • Error Handling: Proper cleanup on refresh failures

Configuration: Configured through ApiServiceConfig in the API service setup.

🏪 handleRtkQueryError

Extracts error data from RTK Query responses and converts them to a standardized format.

import { handleRtkQueryError } from '@appello/services';

try {
  await login(credentials).unwrap();
} catch (error) {
  const errors = handleRtkQueryError(error);
  processError({ errors });
}

Parameters:

  • error: unknown - RTK Query error object

Returns: ResponseErrors - Standardized error format

Features:

  • RTK Query Integration: Specifically designed for RTK Query error handling
  • Error Type Detection: Handles different RTK Query error types
  • Standardized Output: Converts to consistent ResponseErrors format
  • Type Safety: Full TypeScript support

Form Error Processing

⚠️ handleApiRequestError

Processes Axios API errors into a standardized ResponseErrors format.

import { handleApiRequestError } from '@appello/services';

try {
  await apiService.post('/api/endpoint', data);
} catch (error) {
  const errors = handleApiRequestError({ error });
  // errors: { field1: 'Error message', field2: 'Another error' }
}

Parameters:

  • error: AxiosError - The Axios error object
  • handleDetailError?: (error: ResponseErrors) => ResponseErrors - Custom field error handler
  • onManualHandleError?: (error: AxiosError) => ResponseErrors - Override error handling

Returns: ResponseErrors - Map of field names to error messages

Features:

  • Handles network, server, and validation errors
  • Flattens nested field errors to dot notation
  • Takes first error from arrays
  • Supports custom error processing

📝 createProcessApiErrorResponse

Creates a processor for integrating API errors with React Hook Form.

import { createProcessApiErrorResponse } from '@appello/services';

const processError = createProcessApiErrorResponse({
  onGlobalError: message => toast.error(message),
  onUnknownErrors: message => console.error(message),
});

// Usage with form
const { setError } = useForm();

try {
  await apiService.post('/api/login', credentials);
} catch (error) {
  processError({
    errors: handleApiRequestError({ error }),
    fields: ['username', 'password'],
    setFormError: setError,
  });
}

Configuration Options:

  • onGlobalError?: (message: string) => void - Handle non-field errors
  • onUnknownErrors?: (message: string) => void - Handle unknown errors
  • onUnhandledFieldErrors?: (errors: UnhandledFieldError[]) => void - Handle unhandled fields

Usage Parameters:

  • errors: ResponseErrors - Error object from handleApiRequestError
  • fields: string[] | Record<string, string> - Form fields to handle
  • setFormError?: (name: string, error: { message: string }) => void - React Hook Form setError

🔧 createProcessGqlErrorResponse

Creates a processor for handling GraphQL errors in forms.

import { createProcessGqlErrorResponse } from '@appello/services';

const processGqlError = createProcessGqlErrorResponse({
  onNonFieldError: message => toast.error(message),
  onUnknownError: message => console.error(message),
});

// Usage with GraphQL errors
const { setError } = useForm();

try {
  await apolloClient.mutate({ mutation: LOGIN_MUTATION, variables: credentials });
} catch (gqlError) {
  processGqlError(gqlError, {
    fields: ['username', 'password'],
    setFormError: setError,
  });
}

Configuration Options:

  • onNonFieldError?: (message: string) => void - Handle general errors
  • onUnknownError?: (message: string) => void - Handle unknown errors
  • onUnhandledFieldErrors?: (errors: UnhandledFieldError[]) => void - Handle unhandled fields

Usage Parameters:

  • gqlError: unknown - GraphQL error object
  • fields: string[] | Record<string, string> - Form fields to handle
  • setFormError?: (name: string, error: { message: string }) => void - React Hook Form setError

GraphQL Error Helpers

🔍 GraphQL Error Detection

import {
  getGqlErrors,
  getGqlError,
  isGqlUnauthorizedError,
  isGqlBusinessError,
  isGqlUnknownError,
} from '@appello/services';

// Extract errors from GraphQL response
const errors = getGqlErrors(gqlError);

// Get first error
const firstError = getGqlError(errors);

// Check error types
if (isGqlUnauthorizedError(errors)) {
  // Handle 401 unauthorized
}

if (isGqlBusinessError(errors)) {
  // Handle business logic errors
}

⚙️ Configuration

API Service Configuration

interface ApiServiceConfig<T = ApiBaseTokens> {
  url: string; // Base API URL
  getAccessToken?: () => Promise<string | null>; // Access token getter
  getRefreshToken?: () => Promise<string | null>; // Refresh token getter
  onTokenRefreshSuccess: (tokens: T) => void; // Success callback
  onTokenRefreshError: (error?: unknown) => void; // Error callback
  refreshTokenUrl?: string; // Refresh endpoint
  refreshTokens?: (instance, error) => Promise<T>; // Custom refresh logic
  getAccessTokenFromRefreshRequest?: (data) => string; // Extract token from response
  axiosConfig?: CreateAxiosDefaults; // Axios configuration
}

GraphQL Client Configuration

interface GqlClientConfig<T = GqlBaseTokens> {
  url: string; // GraphQL endpoint
  wsUrl?: string; // WebSocket URL for subscriptions
  getAccessToken: () => Promise<string | null>; // Access token getter
  getRefreshToken: () => Promise<string | null>; // Refresh token getter
  onTokenRefreshSuccess: (tokens: T) => void; // Success callback
  onTokenRefreshError: (error?: unknown) => void; // Error callback
  refreshTokens: (client, context) => Promise<T>; // Custom refresh logic
  refreshTokenOperationName?: string; // Refresh mutation name
  refreshTokenErrorMessage?: string; // Error message
  onUnknownError?: (message: string) => void; // Unknown error handler
  cache?: InMemoryCacheConfig; // Apollo cache config
  additionalLinks?: ApolloLink | ApolloLink[]; // Extra Apollo links
  uploadLinkParams?: object; // File upload config
}

💡 Complete Example

import {
  createApiService,
  createGqlClient,
  createProcessApiErrorResponse,
  handleApiRequestError,
} from '@appello/services';
import { toast } from 'react-toastify';

// Tokens management
const getAccessToken = () => localStorage.getItem('accessToken');
const getRefreshToken = () => localStorage.getItem('refreshToken');

const handleTokenRefreshSuccess = tokens => {
  localStorage.setItem('accessToken', tokens.accessToken);
  localStorage.setItem('refreshToken', tokens.refreshToken);
};

const handleTokenRefreshError = () => {
  localStorage.clear();
  window.location.href = '/login';
};

// API Service
export const apiService = createApiService({
  url: process.env.REACT_APP_API_URL,
  getAccessToken,
  getRefreshToken,
  onTokenRefreshSuccess: handleTokenRefreshSuccess,
  onTokenRefreshError: handleTokenRefreshError,
  refreshTokenUrl: '/auth/refresh',
});

// GraphQL Client
export const gqlClient = createGqlClient({
  url: `${process.env.REACT_APP_API_URL}/graphql`,
  wsUrl: `${process.env.REACT_APP_WS_URL}/graphql`,
  getAccessToken,
  getRefreshToken,
  onTokenRefreshSuccess: handleTokenRefreshSuccess,
  onTokenRefreshError: handleTokenRefreshError,
  refreshTokens: async (client, context) => {
    const { data } = await client.mutate({
      mutation: REFRESH_TOKEN_MUTATION,
      context,
    });
    return data.refreshToken;
  },
});

// Error Processing
export const processApiError = createProcessApiErrorResponse({
  onGlobalError: message => toast.error(message),
  onUnknownErrors: message => {
    console.error('Unknown API error:', message);
    toast.error('An unexpected error occurred');
  },
});

// Usage in components
export const useLogin = () => {
  const { setError } = useForm();

  return useMutation({
    mutationFn: credentials => apiService.post('/auth/login', credentials),
    onError: error => {
      processApiError({
        errors: handleApiRequestError({ error }),
        fields: ['email', 'password'],
        setFormError: setError,
      });
    },
  });
};

❓ Troubleshooting

Common Issues

Token Refresh Not Working

  • Ensure refreshTokenUrl is correct
  • Check getRefreshToken returns valid token
  • Verify onTokenRefreshSuccess saves tokens properly

GraphQL Subscriptions Failing

  • Check wsUrl is accessible
  • Verify WebSocket connection in network tab
  • Ensure proper authentication context

Form Errors Not Showing

  • Check field names match between API and form
  • Verify setFormError function is passed correctly
  • Use onUnhandledFieldErrors to debug unmapped fields

RTK Query Integration Issues

  • Ensure axiosBaseQuery receives initialized API service
  • Check error handling in RTK Query endpoints
  • Verify error transformation logic

Debug Mode

Enable detailed logging:

const apiService = createApiService({
  // ... config
  axiosConfig: {
    // Enable request/response logging
    transformRequest: [
      (data, headers) => {
        console.log('API Request:', { data, headers });
        return data;
      },
    ],
  },
});

Package Version: 4.0.1
License: ISC
Author: Appello Software

For more examples and advanced usage, check the test files in the repository.

Keywords

appello

FAQs

Package last updated on 22 Nov 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