DEV Community

Cover image for Real-Time Crypto Tracker using Webhooks with React, Next.js and Node.js
CodingTechMan
CodingTechMan

Posted on

Real-Time Crypto Tracker using Webhooks with React, Next.js and Node.js

Real-Time Crypto Tracker using Webhooks with React, Next.js and Node.js

In this article we explore building a crypto tracker using webhooks with a Frontend built with React, Next.js which is connected to a Backend. Now typically a frontend is the one pulling all the data, but with websockets we can push data from a backend to the frontend.

Webhooks can be useful any time you need real-time updates on your frontend. Since Cryptocurrency prices change in real time this is a great use case for it. You can have a real-time dashboard that simply tracks crypto in real time without refreshing.

Prerequisites

  • CoinMarketCap API Key (Free Version is OK) — Required as this is our data source for cryptocurrency prices.

  • Node.js installed

  • Git for cloning

Repo Link

The code overview below covers the main part of the code, however to run the entire project I recommend using git clone on the repo link below to follow along.

https://github.com/EMChamp/cryptotracker-reactnode

Code Overview — Frontend

This code overview will look at the major pieces of the code and give an idea of their responsibilities.

frontend/App.js is the main page of our Next.js/React App where it will actively track a set of cryptocurrency symbols. We also have a websocket connection setup to our backend where we actively listen for price updates.

We also have a method for adding new cryptocurrency symbols to track. By default we are only tracking Bitcoin (BTC) and Ethereum Classic (ETC).

import React, { useEffect, useState } from 'react';
import axios from 'axios';
import './App.css';

function App() {
  // Track symbols and prices
  const [prices, setPrices] = useState({});
  const [newSymbol, setNewSymbol] = useState('');
  const [trackingSymbols, setTrackingSymbols] = useState(['BTC', 'ETC']);

  // Listen for new webhook events from backend and update prices accordingly.
  useEffect(() => {
    const ws = new WebSocket('ws://localhost:5000');

    ws.onmessage = (event) => {
      const data = JSON.parse(event.data);
      setPrices(data);
    };

    return () => ws.close();
  }, []);

  // Handles adding new symbols to the tracking list
  const addSymbol = () => {
    if (newSymbol && !trackingSymbols.includes(newSymbol.toUpperCase())) {
      axios.post('http://localhost:5000/add-symbol', { symbol: newSymbol })
        .then(response => {
          setTrackingSymbols(response.data.symbols);
          setNewSymbol('');
        })
        .catch(error => {
          console.error('Error adding symbol:', error.response.data.message);
        });
    }
  };

  // UI/Frontend of the page
  return (
    <div className="App">
      <header className="App-header">
        <h1>CryptoTracker</h1>
      </header>
      <main>
        <div className="symbol-input">
          <input
            type="text"
            placeholder="Enter crypto symbol (e.g., BTC)"
            value={newSymbol}
            onChange={(e) => setNewSymbol(e.target.value)}
          />
          <button onClick={addSymbol}>Add Symbol</button>
        </div>
        <div className="symbol-tracker">
          {trackingSymbols.map((symbol) => (
            <div key={symbol} className="symbol-card">
              <h2>{symbol}</h2>
              <p>{prices[symbol] ? `$${prices[symbol].toFixed(2)}` : 'Loading...'}</p>
            </div>
          ))}
        </div>
      </main>
      <footer className="App-footer">
        <p>&copy; 2024 CryptoTracker. All rights reserved.</p>
      </footer>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

frontend/index.js is responsible for rendering App.js’ App component onto the browser.

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
Enter fullscreen mode Exit fullscreen mode

frontend/App.css styles the HTML in the page above in a clean and modern look.

body {
  margin: 0;
  font-family: Arial, sans-serif;
  background-color: #f4f4f4;
}

.App {
  text-align: center;
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

.App-header {
  background-color: #1a1a1a;
  color: white;
  padding: 20px;
  text-align: center;
  font-size: 1.5em;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  position: sticky;
  top: 0;
  z-index: 1000;
}

.symbol-input {
  margin: 20px auto;
  display: flex;
  justify-content: center;
}

.symbol-input input {
  padding: 10px;
  font-size: 1em;
  width: 250px;
  margin-right: 10px;
  border: 1px solid #ccc;
  border-radius: 4px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.symbol-input button {
  padding: 10px 20px;
  font-size: 1em;
  background-color: #007bff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.symbol-input button:hover {
  background-color: #0056b3;
}

.symbol-tracker {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 20px;
  padding: 20px;
}

.symbol-card {
  background-color: white;
  border: 1px solid #ccc;
  border-radius: 8px;
  padding: 20px;
  width: 200px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  transition: transform 0.2s;
}

.symbol-card h2 {
  margin: 0;
  font-size: 1.2em;
}

.symbol-card p {
  margin: 10px 0 0;
  font-size: 1.1em;
  color: #333;
}

.symbol-card:hover {
  transform: translateY(-5px);
}

.App-footer {
  background-color: #1a1a1a;
  color: white;
  padding: 10px;
  text-align: center;
  margin-top: auto;
  box-shadow: 0 -2px 4px rgba(0, 0, 0, 0.1);
}
Enter fullscreen mode Exit fullscreen mode

Code Overview — Backend

backend/server.js is our backend server code. It is setup to poll CoinMarketCap’s API every 10 seconds for price changes and then push those changes via websocket to any connected frontend clients.

The endpoint /add-symbol takes requests from the frontend for any new symbols to track and appends it to the list of currently tracked symbols when we make the next API call to CoinMarketCap’s API for price changes.

We use the websocket library to handle the heavy lifting of managing websocket connections where it actively listens for any HTTPS connections that should be upgraded to websocket.

const express = require('express');
const axios = require('axios');
const WebSocket = require('ws');
const cors = require('cors');

const app = express();
app.use(cors());
const PORT = 5000;
const API_KEY = ''; // Replace with your CoinMarketCap API key
const INTERVAL = 10000; // Poll every 10 seconds

// List of cryptocurrencies to track
let symbols = ['BTC', 'ETC'];

// Create a WebSocket server
const wss = new WebSocket.Server({ noServer: true });

// Function to fetch cryptocurrency prices
const fetchPrices = async () => {
    try {
        const response = await axios.get('https://pro-api.coinmarketcap.com/v1/cryptocurrency/quotes/latest', {
            params: {
                symbol: symbols.join(','),
                convert: 'USD'
            },
            headers: {
                'X-CMC_PRO_API_KEY': API_KEY
            }
        });

        const prices = symbols.reduce((acc, symbol) => {
            acc[symbol] = response.data.data[symbol].quote.USD.price;
            return acc;
        }, {});

        return prices;
    } catch (error) {
        console.error('Error fetching data from CoinMarketCap:', error);
        return null;
    }
};

// Broadcast prices to all clients
const broadcastPrices = (prices) => {
    wss.clients.forEach(client => {
        if (client.readyState === WebSocket.OPEN) {
            client.send(JSON.stringify(prices));
        }
    });
};

// Periodically poll CoinMarketCap API and broadcast prices
setInterval(async () => {
    const prices = await fetchPrices();
    if (prices) {
        broadcastPrices(prices);
    }
}, INTERVAL);

// Handle HTTP and WebSocket connections
const server = app.listen(PORT, () => {
    console.log(`Server running on http://localhost:${PORT}`);
});

server.on('upgrade', (request, socket, head) => {
    wss.handleUpgrade(request, socket, head, (ws) => {
        wss.emit('connection', ws, request);
    });
});

// Endpoint for tracking new symbols
app.post('/add-symbol', (req, res) => {
    const { symbol } = req.body;
    if (!symbols.includes(symbol.toUpperCase())) {
        symbols.push(symbol.toUpperCase());
        res.status(200).send({ success: true, symbols });
    } else {
        res.status(400).send({ success: false, message: 'Symbol already being tracked' });
    }
});
Enter fullscreen mode Exit fullscreen mode

Running the application

The code overview goes over the main parts of the code however it is not complete, so we would still recommend to clone the code from our GitHub repository in order to run it locally.

First you will have to replace API_KEY with your CoinMarketCap API Key.

Afterwards we can run the following commands which will go to each portion of the application, install their packages and then start the server.

cd frontend
npm install
npm start

cd ../backend
npm install
npm start
Enter fullscreen mode Exit fullscreen mode

Output of above commands

Now we should have our frontend server running on http://localhost:3000 and backend server on http://localhost:5000.

Going to our frontend on http://localhost:3000 will let us access this page where you can add Cryptocurrency symbols and then watch the application update in real time.

Top comments (0)