A step by step guide to build & deploy an extensible historical token data chart.
End Result
Play with it here, or skip the tutorial and clone the github repo
The Tech
- React
- NodeJS w/ Express Server
- recharts graphing library
- Heroku for deployment
Requirements
- To use the historical token price endpoint, you will need a professional amberdata API key. You can purchase the professional key here, or you can use the historical token supply endpoint with the free developer API key.
1. Serving the data
To keep our API Key secret, we will use a server to retrieve the historical token prices.
In the root project directory run,
npm install express --save
and create an empty file server.js
,
in server.js
, add the following code to set up a basic express server
const express = require('express')const app = express()const port = 3001app.get('/', (req, res) => res.send('Hello World!'))app.listen(port, () => console.log(`Example app listening on port ${port}!`))
Make sure it’s working properly by running node server.js
in your console. You should see: “Example app listening on port 3001!”
To make http requests, let’s install axios
npm install axios --save
and remember to import axios inserver.js
const axios = require('axios')
To request the data, we will create an endpoint called /tokenPrices
. For now, we will hardcode the endpoint for BNB token. If you do not have a professional API key, you can use the historical token supply endpoint with a developer API key.
app.get('/tokenPrices', async (req, res) => { try { const response = await axios.get('https://web3api.io/api/v1/market/tokens/prices/0xb8c77482e45f1f44de1745f52c74426c631bdd52/historical?timeFormat=ms&timeInterval=d', { headers: { 'x-api-key': 'YOUR_API_KEY_HERE' }}) res.send(response.data) } catch (err) { res.status(500).send(err) }})
Ensure the endpoint is working properly by restarting the server and opening a browser to localhost:3001/tokenPrices
. You should see the raw response:
{ “status”: 200, “title”: “OK”, “description”: “Successful request”, “payload”: { “metadata”: {…} ...}`
Looking closely at the payload data, the columns are arranged like so:
["timestamp","priceUSD","priceETH","hourlyPercentChangeETH","hourlyPercentChangeUSD","dailyPercentChangeETH","dailyPercentChangeUSD","dailyVolumeETH","dailyVolumeUSD","weeklyPercentChangeETH","weeklyPercentChangeUSD","circulatingSupply","totalSupply","marketCapETH","marketCapUSD"]
Since our graph only needs timestamp and priceUSD, let’s filter each data point after our endpoint retrieves the data:
const response = await axios.get(...)const data = response.data.payload.dataconst result = []data.forEach(item => { result.push([item[0], item[1]])})res.send(result)
Restart the server and open your browser to localhost:3001
, verify the data is an array of two element arrays,
[[1557100800000,22.8102451874],[1557187200000,22.2135644982],[1557273600000,19.9713649466]...],
Now we are ready to plot the token price data.
2. Building the frontend
Run create-react-app in your terminal to generate the React application skeleton. Replace the html inApp.js
with an empty <div/>
for now. For the charting library I chose recharts for its simplicity, but d3, chartJS, and highcharts are also great options.
npm install recharts --save
For formatting the timestamps, moment
is an excellent library,
npm install moment --save
NOTE: At this point, it may be a good idea to ensure everything installed properly. I followed the tutorial here http://recharts.org/en-US/examples/SimpleLineChart to render a simple line chart on the frontend.
3. Connecting the frontend to the backend
There are some things to take into account before the frontend can interact with the backend. First, we need to allow cross origin requests (CORS) from the server. Second, we need to make the frontend component stateful to handle asynchronous data requests.
In server.js
, add the following code to allow CORS:
const allowCrossDomain = function(req, res, next) { res.header('Access-Control-Allow-Origin', '*') res.header('Access-Control-Allow-Methods', 'GET') next()}app.use(allowCrossDomain)
To handle async data, we need to add state to App.js
:
import React, { Component } from 'react';class App extends Component {constructor() { super(); this.state = { data: [] }; }componentDidMount() { axios.get('http://localhost:3001/tokenPrices').then(res => { console.log('res', res) }).catch(err => { console.log('err', err) })}render() { return ( <div> <LineChart width={400} height={400} data={this.state.data} > ... );}}export default App;
Open the chrome console and make sure data is successfully coming through from the server, you should see a response that looks like
res {data: Array(31), status: 200, statusText: "OK", headers: {…}, config: {…}, …}
Now to get real data to show on the chart, adjust componentDidMount to set state once it receives the data
componentDidMount() { axios.get('http://localhost:3001/tokenPrices').then(res => { const result = [] res.data.forEach(point => { result.push({ value: point[1], time: point[0] }) }) this.setState({ data: result })}).catch(err => { console.log('err', err)})}
You now have a react application that visualizes ethereum market data provided by amberdata. However, it only works locally. The last step is deploying this application so the world can use it.
4. Deploying with Heroku
To deploy with Heroku, we will need to serve the react frontend from the /
route and allow Heroku to set the port. Make the following changes to server.js
to enable this:
const path = require('path');const port = process.env.PORT || 3001...app.use(express.static(path.join(__dirname, 'build')));app.get('/', async (req, res) => { res.sendFile(path.join(__dirname, 'build', 'index.html'));})
Our frontend will require a minor modification to interact with the server now that they’re running on the same port. If the NODE_ENV is production, we can use the origin route /
to interact with the server instead of http://localhost:3001
. Update the componentDidMount method to handle our production environment
const root = process.env.NODE_ENV === 'development' ? `http://localhost:${process.env.PORT || 3001}` : ''axios.get(`${root}/tokenPrices?address=${address}`).then(res => {
...
Lastly, create a Procfile
in the root directory and add this line:
web: node server.js
Commit and push everything to remote, then in the terminal run
heroku create
git push heroku master
heroku open
Voila! You now have a React application with a NodeJS backend that’s retrieving up to date token data from amberdata.
Your application will not look exactly like the end result shown at the beginning of the tutorial. I styled the page, added another endpoint, added an API Key input, and added buttons to quickly select some popular tokens. For the rest of the code check out the github repo.
5. Allowing the user to specify their own API key
The API key should be passed as a header from the frontend during the request. In order to allow this, add this to the allowCrossDomain function in server.js
:
res.header('Access-Control-Allow-Headers', 'x-api-key')
Also, it makes sense to use a .env
file to the server to allow a default API key
npm install --save-dev dotenv
insideserver.js
add this,
require(‘dotenv’).config()
and inside the /tokenPrices
endpoint, add an apiKey variable which is passed as a header in the response
const apiKey = req.get(‘x-api-key’) || process.env.APIKEY
...
const response = await axios.get(`https://web3api.io/api/v1/market/tokens/prices/${address}/historical?timeFormat=ms&timeInterval=d`, { headers: { 'x-api-key': apiKey }})
Now there are three steps remaining to call the program complete:
- Add a
.env
file and set a default APIKEY. You will also have to set this configuration variable on the Heroku instance. - Add an input box for the user to input their own API key
- When making the request from the frontend, include
x-api-key
in the header.
Refer to the github repo for implementation details ;).
6. Conclusion
This was built for an internal hackathon at amberdata. The frontend at amberdata.io is built with Vue and it was fun to writing a similar application with different tech. Vue and React accomplish the same goal, but the development experience is quite different. I want to compare and contrast the development experience here.
I like React because…
React has a much larger community, which means every issue you encounter someone else has probably also encountered. There are also many more developers creating tools and libraries for other developers, which may save you time.
I like Vue because…
The code style of Vue is so much cleaner than React. Vue maintains a very clear separation of HTML, JS, and CSS in each component. Additionally, Vue has much less boilerplate. If you realize you need state on a certain React component you need to completely change the component’s structure. With Vue, you just throw in different bits as you need them (data, methods, computed properties, etc).
Compare the two structures below:
React
import React, { Component } from ‘react’;import './App.css'class App extends Component {constructor() { super();
// bind methods, state data, etc}
render() {
return (
<div>HTML goes here</div> );
}
Vue
<template> <div>HTML goes here</div></template><script> // JavaScript goes here</script><style> /* CSS goes here */</style>
I much prefer Vue for smaller, proof of concept type projects. However, you cannot deny the power of React’s massive community when building more complex applications.
I hope you found this tutorial helpful. There’s a lot of exciting products coming from Amberdata so make sure to sign up to stay on top of it by creating an account at amberdata.io.