Real-Time Crypto Price Tracker with Golang and Svelte
Introduction
This project demonstrates how to leverage the WebSocket API to access live order book data for the BTC/USDT trading pair on Binance. You will learn how to calculate the average prices of bids and asks, and display the results on a simple web interface.
By the end of this blog, you will know how to connect to WebSocket, process real-time data using Golang, and create a dynamic frontend using Svelte. We will also explore how WebSocket communication operates and how to manage data broadcasting between the backend and multiple frontend clients.
What You Will Learn
Through this project, you’ll get hands-on experience with a variety of technologies, covering both backend and frontend development. Specifically, you will learn:
Connecting to WebSocket API: You’ll learn how to subscribe to real-time WebSockets, in this case, the order book for the BTC/USDT pair, and receive updates as they happen.
Broadcasting Data via WebSocket: You’ll explore how to set up a WebSocket server in Golang that can broadcast the calculated average price to connected clients.
Frontend with Svelte: On the frontend, you’ll learn how to establish WebSocket connections to receive and display live data in a user-friendly manner. We’ll also handle WebSocket reconnections gracefully.
Real-Time Data Display: Finally, you’ll learn how to display real-time price updates dynamically in a simple table using Svelte’s reactive features.
This project is a great way to deepen your understanding of real-time communication between backend and frontend, and how to build a lightweight real-time application.
What is This Project?
This project integrates Golang for the backend and Svelte for the frontend to develop a real-time order book price display system. The backend connects to Binance’s WebSocket API, monitors depth updates for the BTC/USDT trading pair, and calculates the average price from the bids and asks.
After calculating the average price, it is transmitted to all connected clients using WebSocket. On the frontend, we utilize Svelte to create a dynamic user interface that displays the latest prices as they update in real time. The frontend automatically reconnects if the connection is lost, ensuring a smooth and uninterrupted experience for users.
This project simulates a real-time data pipeline that you might encounter while developing tools for financial markets. Whether you are monitoring cryptocurrency prices, building trading bots, or simply learning about WebSocket and real-time web applications, this project will provide you with a solid foundation.
Prerequisites
Before diving into the project, it's essential to ensure you have the necessary skills and tools. Here’s what you’ll need:
Basic knowledge of Golang.
Understanding of WebSockets.
Basic frontend development skills.
Tools and Environment:
Golang installed on your machine.
Node.js and npm (Node Package Manager) installed for running the frontend Svelte application.
A code editor like Visual Studio Code
Basic knowledge of the terminal/command line for installing dependencies and running the project.
Backend setup
This section will walk you through setting up the backend, connecting to Binance WebSocket for real-time data, calculating average prices from the order book, and broadcasting the results to connected WebSocket clients.
1. Setting Up the Backend Project
Start by setting up the basic structure of your backend in Go. If you haven't already, follow these steps to initialize your project.
Create the project directory: First, create a directory for your backend code.
mkdir crypto-price-stream-backend cd crypto-price-stream-backend
Initialize the Go module: Initialize your Go project using the following command:
go mod init crypto-price-stream-backend
Install dependencies: For this project, we’ll use the
gorilla/websocket
package to handle WebSocket communication. Install it using:go get github.com/gorilla/websocket
Now, your backend project is set up with the necessary structure and dependencies.
2. Connecting to Binance WebSocket
Next, we need to establish a WebSocket connection to Binance to receive real-time depth updates (order book data) for the BTC/USDT trading pair. This data will be processed later to calculate the average price.
Step 1: Define the Binance WebSocket URL and constants to use in the project.
const ( binanceWebSocketEndpoint = "wss://stream.binance.com:9443/ws/btcusdt@depth" responseStreamName = "depthUpdate" )
Step 2: Create a function to connect to Binance WebSocket and handle any connection errors.
func connectToBinance() *websocket.Conn { log.Printf("Connecting to Binance WebSocket at %s", binanceWebSocketEndpoint) conn, _, err := websocket.DefaultDialer.Dial(binanceWebSocketEndpoint, nil) if err != nil { log.Fatal("Error connecting to WebSocket:", err) } else { log.Println("Connected to Binance WebSocket successfully.") } return conn }
This function uses the
websocket.DefaultDialer.Dial()
method to establish the WebSocket connection with Binance.
3. Processing Binance Order Book Data and Calculating the Average Price
Once connected to Binance, we need to process the incoming WebSocket messages, parse the order book data, and calculate the average price of bids and asks.
Step 1: Define a struct to map Binance’s WebSocket message data. The
OrderBook
struct will hold bid and ask data.type OrderBook struct { LastUpdateID int64 `json:"u"` Bids [][]string `json:"b"` // [["price", "quantity"]] Asks [][]string `json:"a"` // [["price", "quantity"]] }
Step 2: Create a function to handle messages from the WebSocket. This function listens for incoming messages, checks if they belong to the desired data stream, and calculates the average price from the order book.
func processMessage(conn *websocket.Conn) { defer conn.Close() for { _, message, err := conn.ReadMessage() if err != nil { log.Println("Error reading message:", err) return } var data map[string]interface{} json.Unmarshal(message, &data) if stream, ok := data["e"].(string); ok && stream == responseStreamName { var orderBook OrderBook json.Unmarshal(message, &orderBook) averagePrice := calculateAverage(orderBook) log.Printf("Average Price: %.2f\n", averagePrice) broadcast <- averagePrice } } }
Step 3: Implement the function that calculates the average price from the bids and asks.
func calculateAverage(orderBook OrderBook) float64 { var totalPrice float64 var totalCount int for _, bid := range orderBook.Bids { price, _ := strconv.ParseFloat(bid[0], 64) totalPrice += price totalCount++ } for _, ask := range orderBook.Asks { price, _ := strconv.ParseFloat(ask[0], 64) totalPrice += price totalCount++ } return totalPrice / float64(totalCount) }
This function sums up all the bid and ask prices and divides the total by the number of entries to get the average price.
4. WebSocket to Broadcast Data
Finally, you’ll need to set up a WebSocket server in Go to broadcast the calculated average price to connected frontend clients.
Step 1: Set up a handler for incoming WebSocket connections. This will allow multiple clients to connect and receive real-time price updates.
func handleConnections(w http.ResponseWriter, r *http.Request) { ws, err := upgrader.Upgrade(w, r, nil) if err != nil { log.Fatal("Error upgrading to WebSocket:", err) } defer ws.Close() clients.Store(ws, true) for { _, _, err := ws.ReadMessage() if err != nil { clients.Delete(ws) break } } }
Step 2: Implement the function to broadcast the average price to all connected clients.
func handleMessages() { for { avgPrice := <-broadcast clients.Range(func(client, _ interface{}) bool { ws := client.(*websocket.Conn) err := ws.WriteJSON(avgPrice) if err != nil { log.Printf("Error writing message to client: %v", err) ws.Close() clients.Delete(client) } return true }) } }
Step 3: Start the WebSocket server in the
main()
function, and run the message processing and broadcasting concurrently.func main() { conn := connectToBinance() go processMessage(conn) go handleMessages() http.HandleFunc("/ws", handleConnections) log.Println("WebSocket server started on :8080") log.Fatal(http.ListenAndServe(":8080", nil)) }
Now, the backend is fully set up to connect to Binance WebSocket, calculate the average price of bids and asks, and broadcast the results to connected WebSocket clients.
Frontend: Step-by-Step Guide
In this section, we will set up the frontend using Svelte, which will connect to the backend WebSocket server to receive real-time price updates and display them in a table. We’ll start from setting up the project, move on to connecting the frontend to the WebSocket, and finally, implement a simple UI to display the price data.
1. Setting up the Frontend Project
To begin, we will create a new Svelte project using Vite.
Create the project with Vite: Run the following command to create a new project using Vite.
npm create vite@latest
Select
Svelte
when prompted for a framework. Name the project (e.g.,crypto-price-stream-frontend
).Install dependencies: After the project is created, navigate to the project directory and install the dependencies.
cd crypto-price-stream-frontend npm install
Run the development server: Start the development server to make sure everything is working.
npm run dev
You should now have a working Svelte project.
2. Setting up WebSocket Connection
Next, we will modify the App.svelte
file to connect to the backend WebSocket server, receive real-time average price data, and display it.
Step 1: Open
src/App.svelte
and replace the content with the following code to connect to the WebSocket and manage the state of price updates:<script lang="ts"> import { onMount, onDestroy } from "svelte"; let connectionStatus: string = "Connecting..."; let ws: WebSocket | null = null; let reconnectInterval: number | null = null; let prices: { price: number; time: string }[] = []; // array to store prices with time // function to create WebSocket connection function connect() { ws = new WebSocket("ws://localhost:8080/ws"); ws.onmessage = (event) => { const data = JSON.parse(event.data); addPrice(data); // adding price to prices array }; ws.onerror = (error) => { console.error("WebSocket Error:", error); connectionStatus = "Connection error"; // updating connection status }; ws.onclose = () => { connectionStatus = "Connection Lost, reconnecting..."; // updating connection status attemptReconnect(); // attempting reconnection }; connectionStatus = "Connected"; // setting initial connection status } // function to attempt reconnection function attemptReconnect() { if (reconnectInterval) return; // return if already reconnecting reconnectInterval = setInterval(() => { console.log("Attempting to reconnect..."); connect(); // reconnecting }, 5000); // reconnecting every 5 seconds } // function to add price to prices array function addPrice(price: number) { if (prices.length >= 10) { prices.pop(); // remove oldest price } const time = new Date().toLocaleTimeString(); prices = [{ price, time }, ...prices]; // add new price with time } onMount(() => { connect(); // creating initial connection }); onDestroy(() => { if (ws) { ws.close(); // close WebSocket connection on unmount } if (reconnectInterval) { clearInterval(reconnectInterval); // clear reconnect interval } }); </script> <main> <h1>Average Prices</h1> <p>Connection status: {connectionStatus}</p> {#if prices.length > 0} <!-- table to show prices with time --> <table> <thead> <tr> <th>Price</th> <th>Time</th> </tr> </thead> <tbody> {#each prices as { price, time }} <tr> <td>{price.toFixed(2)}</td> <td>{time}</td> </tr> {/each} </tbody> </table> {:else} <p>Loading...</p> {/if} </main> <style> main { text-align: center; padding: 1em; margin: 0 auto; } h1 { color: #c6ac8f; } p { font-size: 1.2em; } table { width: 100%; border-collapse: collapse; margin-top: 1em; } th, td { padding: 8px; border: 1px solid #c6ac8f; text-align: center; } th { background-color: #5e503f; } </style>
Step 2: Here’s what’s happening in the code:
The
connect()
function creates a WebSocket connection to the backend.WebSocket messages are received, parsed, and added to the
prices
array.If the WebSocket connection fails, it automatically tries to reconnect every 5 seconds.
The price data is displayed in a table, with the latest 10 prices.
3. Running the Frontend
Step 1: Make sure the backend WebSocket server is running on port
8080
. You can check the backend setup from the previous section.Step 2: In your terminal, start the frontend development server.
npm run dev
Step 3: Visit
http://localhost:5173
in your browser (or the URL provided by Vite), and you should see the average prices table being updated in real time.
GitHub Link
You can find the full source code for both the backend and frontend of this project on GitHub. The repository includes all the steps we've covered in this blog:
Feel free to explore the code, clone the repository, and modify it as you like!
Conclusion
In this project, we created a real-time WebSocket service that streams average prices from the Binance order book and broadcasts them to connected clients. We set up the backend using Golang to handle WebSocket connections and perform price calculations, and we built a simple Svelte frontend to display these prices to users in real time.
Subscribe to my newsletter
Read articles from Rabinson Thapa directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by