Building Crypto Candlestick Charts with Chart.js and Moralis
Learn how to create professional cryptocurrency candlestick charts using Chart.js and Moralis' OHLCV API. This tutorial will guide you through building interactive financial charts that work across multiple blockchains.
Prerequisites
- Node.js installed on your system
 - Basic understanding of React
 - A Moralis account for API access
 
Step 1: Project Setup
- Create a new React project:
 
npx create-react-app chartjs-crypto
cd chartjs-crypto
- Install required dependencies:
 
npm install chart.js react-chartjs-2 axios react-spinners
Get your Moralis API key:
- Sign up at Moralis
 - Go to "Web3 APIs" section
 - Create a new API key
 - Copy your API key
 
Create a
.envfile in your project root:
REACT_APP_MORALIS_API_KEY=YOUR_API_KEY
Step 2: Setup Chart.js Components
First, create a new file for the candlestick chart component (src/components/CandlestickChart.js):
import React from "react";
import {
  Chart as ChartJS,
  CategoryScale,
  LinearScale,
  PointElement,
  LineElement,
  Title,
  Tooltip,
  Legend,
} from "chart.js";
import { Line } from "react-chartjs-2";
import "chart.js/auto";
// Register Chart.js components
ChartJS.register(
  CategoryScale,
  LinearScale,
  PointElement,
  LineElement,
  Title,
  Tooltip,
  Legend
);
const CandlestickChart = ({ candlestickData }) => {
  const formatData = () => {
    const labels = candlestickData.map((data) =>
      new Date(data.time * 1000).toLocaleDateString()
    );
    return {
      labels,
      datasets: [
        {
          label: "High",
          data: candlestickData.map((data) => data.high),
          borderColor: "rgba(75, 192, 192, 1)",
          borderWidth: 1,
          fill: false,
        },
        {
          label: "Low",
          data: candlestickData.map((data) => data.low),
          borderColor: "rgba(255, 99, 132, 1)",
          borderWidth: 1,
          fill: false,
        },
        {
          label: "Open",
          data: candlestickData.map((data) => data.open),
          borderColor: "rgba(54, 162, 235, 1)",
          borderWidth: 1,
          fill: false,
        },
        {
          label: "Close",
          data: candlestickData.map((data) => data.close),
          borderColor: "rgba(255, 206, 86, 1)",
          borderWidth: 1,
          fill: false,
        },
      ],
    };
  };
  const options = {
    responsive: true,
    plugins: {
      legend: {
        position: "top",
      },
      title: {
        display: true,
        text: "Cryptocurrency Price Chart",
      },
      tooltip: {
        mode: "index",
        intersect: false,
      },
    },
    scales: {
      y: {
        type: "linear",
        display: true,
        position: "left",
      },
    },
    interaction: {
      mode: "index",
      intersect: false,
    },
  };
  return (
    <div className="chart-container">
      <Line options={options} data={formatData()} />
    </div>
  );
};
export default CandlestickChart;
Step 3: Create Custom Candlestick Plugin
To create true candlestick charts, we need to create a custom plugin. Create a new file src/plugins/candlestickPlugin.js:
const candlestickPlugin = {
  id: "candlestick",
  beforeDatasetsDraw(chart, args, options) {
    const {
      ctx,
      data,
      scales: { x, y },
    } = chart;
    ctx.strokeStyle = options.borderColor || "rgba(0, 0, 0, 0.8)";
    ctx.lineWidth = options.borderWidth || 1;
    const candleWidth = x.getPixelForValue(1) - x.getPixelForValue(0);
    const wickWidth = candleWidth / 10;
    data.datasets[0].data.forEach((point, i) => {
      const open = y.getPixelForValue(data.datasets[2].data[i]);
      const close = y.getPixelForValue(data.datasets[3].data[i]);
      const high = y.getPixelForValue(point);
      const low = y.getPixelForValue(data.datasets[1].data[i]);
      const x1 = x.getPixelForValue(i);
      // Draw the wicks
      ctx.beginPath();
      ctx.moveTo(x1, high);
      ctx.lineTo(x1, Math.min(open, close));
      ctx.moveTo(x1, Math.max(open, close));
      ctx.lineTo(x1, low);
      ctx.stroke();
      // Draw the candle body
      ctx.fillStyle = close > open ? "#26a69a" : "#ef5350";
      ctx.fillRect(
        x1 - candleWidth / 3,
        Math.min(open, close),
        (candleWidth * 2) / 3,
        Math.abs(close - open)
      );
    });
  },
};
export default candlestickPlugin;
Step 4: Update Chart Component with Plugin
Update your CandlestickChart component to use the custom plugin:
import candlestickPlugin from "../plugins/candlestickPlugin";
// ... previous imports ...
const CandlestickChart = ({ candlestickData }) => {
  const options = {
    responsive: true,
    plugins: {
      legend: {
        display: false,
      },
      candlestick: {
        borderColor: "rgba(0, 0, 0, 0.8)",
        borderWidth: 1,
      },
    },
    scales: {
      y: {
        type: "linear",
        position: "left",
      },
    },
  };
  const formatData = () => {
    const labels = candlestickData.map((data) =>
      new Date(data.time * 1000).toLocaleDateString()
    );
    return {
      labels,
      datasets: [
        {
          data: candlestickData.map((data) => data.high),
          yAxisID: "y",
        },
        {
          data: candlestickData.map((data) => data.low),
          yAxisID: "y",
        },
        {
          data: candlestickData.map((data) => data.open),
          yAxisID: "y",
        },
        {
          data: candlestickData.map((data) => data.close),
          yAxisID: "y",
        },
      ],
    };
  };
  return (
    <div className="chart-container">
      <Line
        options={options}
        data={formatData()}
        plugins={[candlestickPlugin]}
      />
    </div>
  );
};
Step 5: Implement Main App Component
Update your App.js:
import React, { useState, useEffect } from "react";
import axios from "axios";
import CandlestickChart from "./components/CandlestickChart";
import ClipLoader from "react-spinners/ClipLoader";
const App = () => {
  const [candlestickData, setCandlestickData] = useState([]);
  const [loading, setLoading] = useState(false);
  const fetchOHLCVData = async () => {
    setLoading(true);
    try {
      const apiKey = process.env.REACT_APP_MORALIS_API_KEY;
      const currentTime = Math.floor(Date.now() / 1000);
      const fromDate = currentTime - 30 * 24 * 60 * 60; // 30 days
      const response = await axios.get(
        `https://deep-index.moralis.io/api/v2.2/pairs/0xa478c2975ab1ea89e8196811f51a7b7ade33eb11/ohlcv`,
        {
          params: {
            chain: "eth",
            timeframe: "1d",
            currency: "usd",
            fromDate,
            toDate: currentTime,
            limit: 1000,
          },
          headers: {
            "X-API-Key": apiKey,
          },
        }
      );
      const formattedData = response.data.result.map((item) => ({
        time: Math.floor(new Date(item.timestamp).getTime() / 1000),
        open: item.open,
        high: item.high,
        low: item.low,
        close: item.close,
      }));
      setCandlestickData(formattedData);
    } catch (error) {
      console.error("Error fetching OHLCV data:", error);
    } finally {
      setLoading(false);
    }
  };
  useEffect(() => {
    fetchOHLCVData();
  }, []);
  return (
    <div className="app-container">
      <h1>Crypto Candlestick Chart</h1>
      {loading ? (
        <div className="loading-spinner">
          <ClipLoader color="#2196f3" size={50} />
        </div>
      ) : (
        candlestickData.length > 0 && (
          <div className="chart-container">
            <CandlestickChart candlestickData={candlestickData} />
          </div>
        )
      )}
    </div>
  );
};
export default App;
Step 6: Add Styling
Create src/styles.css:
.app-container {
  max-width: 1200px;
  margin: 2rem auto;
  padding: 2rem;
  background: white;
  border-radius: 12px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
}
.chart-container {
  margin-top: 2rem;
  padding: 1.5rem;
  background: white;
  border-radius: 12px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
  height: 500px;
}
.loading-spinner {
  display: flex;
  justify-content: center;
  margin: 2rem 0;
}
h1 {
  text-align: center;
  color: #1a237e;
  margin-bottom: 2rem;
}
Step 7: Final Setup and Running the Application
- Import the styles in your 
App.js: 
import "./styles.css";
- Start the development server:
 
npm start
Additional Features to Consider
Add Volume Display:
- Create a separate bar chart below the candlesticks
 - Use volume data from the OHLCV API
 
Implement Technical Indicators:
- Add moving averages
 - Include RSI or MACD indicators
 - Add Bollinger Bands
 
Add Time Range Selection:
- Create buttons for different time periods
 - Implement custom date range picker
 
Enhance Interactivity:
- Add zoom functionality
 - Implement price annotations
 - Add crosshair feature
 
Common Issues and Solutions
Chart Not Rendering:
- Check if data is properly formatted
 - Verify Chart.js registration
 - Check console for errors
 
API Issues:
- Verify API key is correct
 - Check request parameters
 - Ensure proper error handling
 
Performance Issues:
- Limit data points displayed
 - Implement data decimation
 - Use appropriate timeframe intervals