Skip to main content

Overview

CryptoDash uses the CoinGecko API to fetch real-time cryptocurrency data. The integration is built with:
  • Axios for HTTP requests
  • Environment variables for configuration
  • Request/response interceptors for consistency
  • In-memory caching to reduce API calls

Axios Client Setup

The base Axios client is configured in src/api/axios.js:
src/api/axios.js
import axios from 'axios'

const API_BASE_URL = import.meta.env.VITE_COINGECKO_BASE_URL ?? 'https://api.coingecko.com/api/v3'
const API_TIMEOUT = Number(import.meta.env.VITE_API_TIMEOUT ?? 10000)
const API_KEY = import.meta.env.VITE_COINGECKO_API_KEY

const apiClient = axios.create({
  baseURL: API_BASE_URL,
  timeout: API_TIMEOUT,
})

export default apiClient
Configuration values are read from Vite environment variables with fallback defaults.

Environment Configuration

Create a .env file in the project root:
.env
VITE_COINGECKO_BASE_URL=https://api.coingecko.com/api/v3
VITE_API_TIMEOUT=10000
VITE_COINGECKO_API_KEY=your_api_key_here  # Optional for Pro users
Never commit .env files to version control. Use .env.example as a template.

Request Interceptors

Request interceptors add headers and authentication:
src/api/axios.js
apiClient.interceptors.request.use(
  (config) => {
    const nextConfig = { ...config }

    nextConfig.headers = {
      Accept: 'application/json',
      ...nextConfig.headers,
    }

    // Add CoinGecko Pro API key if available
    if (API_KEY) {
      nextConfig.headers['x-cg-pro-api-key'] = API_KEY
    }

    // Add auth token for future backend integration
    const authToken = localStorage.getItem('auth_token')
    if (authToken) {
      nextConfig.headers.Authorization = `Bearer ${authToken}`
    }

    return nextConfig
  },
  (error) => Promise.reject(error),
)
The free CoinGecko API doesn’t require an API key. Simply omit VITE_COINGECKO_API_KEY.

Response Interceptors

Response interceptors normalize error handling:
src/api/axios.js
apiClient.interceptors.response.use(
  (response) => response,
  (error) => {
    const normalizedError = {
      status: error.response?.status ?? 0,
      message: error.response?.data?.error || 
               error.response?.data?.message || 
               error.message || 
               'Unexpected API error',
      data: error.response?.data,
      original: error,
    }

    return Promise.reject(normalizedError)
  },
)
This ensures all API errors have a consistent structure:
{
  status: number,
  message: string,
  data: any,
  original: Error
}

API Modules

Dashboard API

Fetches global cryptocurrency statistics.
import apiClient from '../axios'

const GLOBAL_STATS_TTL_MS = 5 * 60 * 1000  // 5 minutes

let globalStatsCache = {
  data: null,
  timestamp: 0,
  inFlight: null,
}

export async function getGlobalStats() {
  const now = Date.now()

  // Return cached data if still valid
  if (globalStatsCache.data && 
      now - globalStatsCache.timestamp < GLOBAL_STATS_TTL_MS) {
    return globalStatsCache.data
  }

  // Return in-flight request if one exists
  if (globalStatsCache.inFlight) {
    return globalStatsCache.inFlight
  }

  // Make new request
  globalStatsCache.inFlight = apiClient
    .get('/global')
    .then(({ data }) => {
      const normalizedData = data?.data ?? null
      globalStatsCache = {
        data: normalizedData,
        timestamp: Date.now(),
        inFlight: null,
      }
      return normalizedData
    })
    .catch((error) => {
      globalStatsCache = {
        ...globalStatsCache,
        inFlight: null,
      }
      throw error
    })

  return globalStatsCache.inFlight
}
This implementation includes request deduplication - multiple simultaneous calls return the same promise.

Market API

Fetches market data and price charts.
src/api/market/marketApi.js
import apiClient from '../axios'

const MARKET_CACHE_TTL_MS = 5 * 60 * 1000
const topMarketCoinsCache = new Map()

export async function getTopMarketCoins({
  vsCurrency = 'usd',
  order = 'market_cap_desc',
  perPage = 10,
  page = 1,
  sparkline = true,
  priceChangePercentage = '24h,7d',
} = {}) {
  const requestParams = {
    vs_currency: vsCurrency,
    order,
    per_page: perPage,
    page,
    sparkline,
    price_change_percentage: priceChangePercentage,
  }

  const cacheKey = JSON.stringify(requestParams)
  const cached = getCachedEntry(topMarketCoinsCache, cacheKey)

  if (cached?.data) {
    return cached.data
  }

  if (cached?.inFlight) {
    return cached.inFlight
  }

  const inFlight = apiClient
    .get('/coins/markets', { params: requestParams })
    .then(({ data }) => {
      const normalizedData = data ?? []
      topMarketCoinsCache.set(cacheKey, {
        data: normalizedData,
        timestamp: Date.now(),
        inFlight: null,
      })
      return normalizedData
    })
    .catch((error) => {
      topMarketCoinsCache.delete(cacheKey)
      throw error
    })

  topMarketCoinsCache.set(cacheKey, {
    data: null,
    timestamp: 0,
    inFlight,
  })

  return inFlight
}
Usage:
// Get top 10 coins by market cap
const coins = await getTopMarketCoins()

// Get top 20 with custom parameters
const coins = await getTopMarketCoins({
  perPage: 20,
  order: 'volume_desc'
})

Portfolio API

Manages portfolio data in localStorage (no backend).
src/api/portfolio/portfolioApi.js
const PORTFOLIO_STORAGE_KEY = 'crypto_dash_portfolios'
const DEFAULT_ALLOCATION_USD = 2000

function readPortfolios() {
  const raw = localStorage.getItem(PORTFOLIO_STORAGE_KEY)
  if (!raw) return []
  
  try {
    const parsed = JSON.parse(raw)
    return Array.isArray(parsed) ? parsed : []
  } catch {
    return []
  }
}

function writePortfolios(portfolios) {
  localStorage.setItem(PORTFOLIO_STORAGE_KEY, JSON.stringify(portfolios))
}

export function getOrCreatePrimaryPortfolio(marketCoins = []) {
  const portfolios = readPortfolios()

  if (!portfolios.length) {
    // Create default portfolio with top 5 coins
    const preferredAssets = ['bitcoin', 'ethereum', 'solana', 'binancecoin', 'ripple']
    const positions = preferredAssets
      .filter(id => marketCoins.find(c => c.id === id))
      .map(id => {
        const coin = marketCoins.find(c => c.id === id)
        return {
          assetId: id,
          investedUsd: DEFAULT_ALLOCATION_USD,
          amount: DEFAULT_ALLOCATION_USD / coin.current_price,
          createdAt: new Date().toISOString(),
        }
      })

    const newPortfolio = {
      id: 'main',
      name: 'Portafolio Principal',
      positions,
      createdAt: new Date().toISOString(),
    }

    writePortfolios([newPortfolio])
    return newPortfolio
  }

  return portfolios[0]
}
The Portfolio API uses localStorage instead of a backend. This makes the app work offline but limits data to a single device.

Caching Strategy

CryptoDash implements multi-level caching:
1

In-Memory Cache

API responses are cached in memory with a 5-minute TTL
const CACHE_TTL_MS = 5 * 60 * 1000  // 5 minutes
2

Request Deduplication

Multiple simultaneous requests for the same data share a single promise
if (cache.inFlight) {
  return cache.inFlight  // Reuse existing request
}
3

LocalStorage

User settings and portfolio data persist in localStorage
localStorage.setItem('crypto-dash-theme', theme)

Error Handling

All API errors are normalized and can be caught consistently:
try {
  const data = await getGlobalStats()
} catch (error) {
  console.error(`Status: ${error.status}`)    // HTTP status code
  console.error(`Message: ${error.message}`)  // User-friendly message
  console.error(`Data: ${error.data}`)        // Response data
  console.error(`Original: ${error.original}`) // Original axios error
}
  • 0 - Network error, timeout, or CORS issue
  • 429 - Rate limit exceeded
  • 404 - Endpoint not found
  • 500 - Server error
CoinGecko free API limits:
  • 10-50 calls/minute depending on endpoint
  • Use caching to stay within limits
  • Consider upgrading to Pro for higher limits

Complete Usage Example

Here’s how the dashboard fetches all data:
src/hooks/useDashboardData.js
import { getGlobalStats } from '../api/dashboard/dashboardApi'
import { getCoinPerformanceChart, getTopMarketCoins } from '../api/market/marketApi'

export function useDashboardData() {
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState('')

  useEffect(() => {
    let isMounted = true

    async function fetchDashboardData() {
      try {
        setLoading(true)
        setError('')

        // Fetch all data in parallel
        const [globalStats, topMarketCoins, btcChart] = await Promise.all([
          getGlobalStats(),
          getTopMarketCoins(),
          getCoinPerformanceChart({ coinId: 'bitcoin', days: 30 }),
        ])

        if (!isMounted) return

        // Update state with fetched data
        setGlobalData(globalStats)
        setMarkets(topMarketCoins)
        setMainPerformanceSeries(btcChart.prices.map(item => item[1]))
      } catch (err) {
        if (!isMounted) return
        setError('Failed to load data')
      } finally {
        if (isMounted) setLoading(false)
      }
    }

    fetchDashboardData()

    return () => {
      isMounted = false
    }
  }, [])

  return { loading, error, /* ... data */ }
}

Best Practices

Fetch independent data in parallel with Promise.all():
// Good - parallel (faster)
const [data1, data2] = await Promise.all([
  getGlobalStats(),
  getTopMarketCoins()
])

// Bad - sequential (slower)
const data1 = await getGlobalStats()
const data2 = await getTopMarketCoins()
Don’t manually clear cache unless necessary. The 5-minute TTL balances freshness and API usage.
Always show loading indicators during async operations:
if (loading) return <SkeletonLoader />
if (error) return <ErrorMessage message={error} />
return <Data data={data} />
Use the isMounted pattern to prevent state updates after unmount:
useEffect(() => {
  let isMounted = true
  
  fetchData().then(data => {
    if (isMounted) setState(data)
  })
  
  return () => { isMounted = false }
}, [])

Testing API Endpoints

You can test CoinGecko endpoints directly:
curl 'https://api.coingecko.com/api/v3/global'

Next Steps

State Management

Learn how API data is managed in React

Project Structure

Understand where API modules are located

Build docs developers (and LLMs) love