Skip to main content

Overview

DADDO uses Axios as the HTTP client for all API communications. The API layer is configured with request/response interceptors for automatic token injection, error handling, and session management.

Axios Instance Configuration

The application creates a custom Axios instance with base configuration:
src/Redux/api.js
import axios from "axios";
import store from "./store";
import { LOGOUT } from "./actions/Auth/login";

const URL_DEPLOY = import.meta.env.VITE_URL2;

export const api = axios.create({
  baseURL: URL_DEPLOY,
  withCredentials: false,
});
The API base URL is configured via the VITE_URL2 environment variable, allowing different URLs for development, staging, and production environments.

Environment Configuration

Create a .env file in your project root:
.env
VITE_URL2=https://api.yourdomain.com
Never commit .env files to version control. Use .env.example for documenting required variables.

Request Interceptor

The request interceptor automatically attaches authentication tokens to all outgoing requests:
src/Redux/api.js
api.interceptors.request.use((config) => {
  const token =
    localStorage.getItem("token") ||
    sessionStorage.getItem("token");

  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }

  return config;
});

How It Works

  1. Token Retrieval: Checks both localStorage and sessionStorage for authentication token
  2. Header Injection: Adds Authorization: Bearer <token> header if token exists
  3. Automatic: No need to manually add auth headers in action creators
The dual storage check supports both “Remember Me” (localStorage) and session-only (sessionStorage) authentication modes.

Response Interceptor

The response interceptor handles authentication errors and automatic logout:
src/Redux/api.js
api.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.response?.status === 401) {
      const token =
        localStorage.getItem("token") ||
        sessionStorage.getItem("token");

      // Only logout if there was a token (expired token scenario)
      if (token) {
        store.dispatch({ type: LOGOUT });
        localStorage.removeItem("token");
        sessionStorage.removeItem("token");
        localStorage.removeItem("user");
        sessionStorage.removeItem("user");
      }
    }

    return Promise.reject(error);
  }
);

Error Handling Logic

1

401 Detection

Intercepts HTTP 401 (Unauthorized) responses from the API
2

Token Verification

Checks if a token exists in storage (expired vs. no token)
3

Automatic Logout

Dispatches LOGOUT action to clear Redux state
4

Storage Cleanup

Removes token and user data from both storage types
5

Redirect

Protected routes automatically redirect to login page
The interceptor only triggers logout if a token exists, preventing unnecessary logout actions for unauthenticated API calls (like login itself).

Authentication Flow

Login Process

Token-Based Request Flow

API Endpoints Used

Authentication Endpoints

MethodEndpointDescription
POST/user/loginUser login
POST/user/registerUser registration
POST/user/forgot-passwordRequest password reset
POST/user/reset-passwordReset password with token
PUT/user/updateUpdate user profile

Product Endpoints

MethodEndpointDescription
GET/productsGet all user products
POST/productsCreate new product
PUT/products/:idUpdate product
DELETE/products/:idDelete product
GET/products/categoriesGet product categories
GET/catalog/:userIdGet public catalog

Sales Endpoints

MethodEndpointDescription
GET/sellsGet user sales
POST/sellsCreate new sale
GET/sells/:idGet sale details
PUT/sells/:id/confirmConfirm/complete sale
DELETE/sells/:idDelete sale

Dashboard Endpoints

MethodEndpointDescription
GET/dashboardGet dashboard analytics
GET/dashboard/profitGet profit by date range

Making API Calls in Actions

GET Request Example

import { api } from "../../api";

export const getProducts = () => async (dispatch) => {
  dispatch({ type: 'GET_PROD_REQUEST' });

  try {
    const response = await api.get('/products');
    dispatch({ type: 'GET_PROD_SUCCESS', payload: response.data });
  } catch (error) {
    dispatch({ 
      type: 'GET_PROD_FAILURE', 
      payload: error.response?.data?.message || error.message 
    });
  }
};

POST Request Example

import { api } from "../../api";

export const createProduct = (productData) => async (dispatch) => {
  dispatch({ type: 'CREATE_PROD_REQUEST' });

  try {
    const response = await api.post('/products', productData);
    dispatch({ type: 'CREATE_PROD_SUCCESS', payload: response.data });
    return response.data;
  } catch (error) {
    dispatch({ 
      type: 'CREATE_PROD_FAILURE',
      payload: error.response?.data?.error || 'Error creating product'
    });
    throw error;
  }
};

PUT Request Example

import { api } from "../../api";

export const updateProduct = (id, updates) => async (dispatch) => {
  dispatch({ type: 'UPDATE_PROD_REQUEST' });

  try {
    const response = await api.put(`/products/${id}`, updates);
    dispatch({ type: 'UPDATE_PROD_SUCCESS', payload: response.data });
  } catch (error) {
    dispatch({ 
      type: 'UPDATE_PROD_FAILURE',
      payload: error.message 
    });
  }
};

DELETE Request Example

import { api } from "../../api";

export const deleteProduct = (id) => async (dispatch) => {
  try {
    await api.delete(`/products/${id}`);
    dispatch({ type: 'DELETE_PRODUCT', payload: id });
  } catch (error) {
    console.error('Error deleting product:', error);
    throw error;
  }
};

Error Handling Patterns

Component-Level Error Handling

import { useDispatch, useSelector } from 'react-redux';
import { toast } from 'react-toastify';
import { createProduct } from '../Redux/actions/Products/create_product';

function CreateProduct() {
  const dispatch = useDispatch();
  const { loading, error } = useSelector(state => state.products);

  const handleSubmit = async (productData) => {
    try {
      await dispatch(createProduct(productData));
      toast.success('Product created successfully!');
    } catch (error) {
      toast.error(error.response?.data?.message || 'Failed to create product');
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      {error && <div className="error">{error}</div>}
      {/* form fields */}
    </form>
  );
}

Common Error Response Formats

// Success Response
{
  data: {...},
  message: "Operation successful"
}

// Error Response
{
  error: "Error message",
  message: "Detailed error description",
  statusCode: 400
}

// Validation Error
{
  error: "Validation failed",
  fields: {
    email: "Invalid email format",
    password: "Password too short"
  }
}

CORS Configuration

The Axios instance is configured with withCredentials: false. If your API requires cookies or session-based auth, set this to true.
export const api = axios.create({
  baseURL: URL_DEPLOY,
  withCredentials: true,  // Enable if using cookies
});

Request/Response Debugging

Adding Debug Interceptors

// Log all requests (development only)
if (import.meta.env.DEV) {
  api.interceptors.request.use(config => {
    console.log('→ API Request:', config.method.toUpperCase(), config.url);
    return config;
  });

  api.interceptors.response.use(
    response => {
      console.log('← API Response:', response.status, response.config.url);
      return response;
    },
    error => {
      console.error('← API Error:', error.response?.status, error.config.url);
      return Promise.reject(error);
    }
  );
}

API Best Practices

Every API call should have try/catch blocks and dispatch failure actions for proper error state management.
Access nested error properties with optional chaining: error.response?.data?.message
Always provide fallback error messages: error.message || 'An error occurred'
Return API response data from action creators for component-level handling: return response.data
Validate data in components before dispatching API actions to reduce unnecessary requests.

Security Considerations

Token Storage: Tokens are stored in localStorage/sessionStorage. For highly sensitive applications, consider using httpOnly cookies or more secure storage mechanisms.
  • XSS Protection: Sanitize user input to prevent token theft
  • HTTPS Only: Always use HTTPS in production to prevent token interception
  • Token Expiration: Implement token refresh mechanisms for long-lived sessions
  • Logout on 401: Automatic logout prevents using expired tokens

Next Steps

State Management

Learn how API responses are managed in Redux

Architecture

Understand how API layer fits into overall architecture

Build docs developers (and LLMs) love