This project (and the documentation) is a work in progress.
Bull is a web app showing real-time stocks and allows users to sign up, watch stocks, and see how they grow over time.
Bull leverages the Finnhub API, an amazing free API for financial data.
The server is written in Node/Typescript using Express as a framework. A Mongo database is connected to the server to store users and their financial data.
Axios is used for HTTP requests and an instance is created for connections to Finnhub.
export const finnhub = axios.create({
baseURL: 'https://finnhub.io/api/v1/',
headers: { 'X-Finnhub-Token': <your-api-token> },
});
This instance can be used, for instance, to fetch Apple stock data with:
const response = await finnhub.get('quote?symbol=AAPL');
When a user creates an account, a JWT is returned as a session cookie for future authenticated requests.
// create the JWT after assigning the new user an ID
const token = jwt.sign({ id }, '<your-jwt-secret>', {
expiresIn: '1 hour',
});
// send the cookie in the response
res.cookie('jwt', token, { httpOnly: true, expires: new Date() });
res.status(201).send({ id: user._id.toString() });
// parse jwt cookie from request
const token = req.cookies.jwt;
// decode the payload from the JWT
const decoded = jwt.verify(token, '<your-jwt-secret>');
// validate user with this ID and token exists
const user = await User.findOne({
_id: decoded.id,
token,
});
Integration tests are performed using Jest and Supertest.
The web client is written in React/Typescript using Redux for managing global state in the app.
The client uses React Intl to create internationalized text and numbers.
A live stock ticker (during trading hours) is created using websockets.
Websocket connections can be established in a few lines of code using React hooks.
React.useEffect(() => {
const url = `wss://ws.finnhub.io?token=${your-api-token}`;
ws.current = new WebSocket(url);
// Subscribe to AAPL stock when the ws connection opens
ws.current.onopen = () => {
ws.current.send(JSON.stringify({ type: 'subscribe', 'AAPL' }));
};
// Handling any logic when the connection closes
ws.current.onclose = () => console.log('Connection closed');
// Listen for messages
ws.current.onmessage = (e: MessageEvent) => {
const { data } = JSON.parse(e.data);
const { s: symbol, p: price, t: timestamp } = data[0];
console.log(`${s}: $${price} at time ${t}`);
};
// Unsubscribe to the trades when the component unmounts
return () => {
ws.current.send(JSON.stringify({ type: 'unsubscribe', 'AAPL' }));
};
}, []);
Unit tests are performed using Jest and React Testing Library.
To develop Bull locally, first make sure that you have Docker and docker-compose installed.
Then clone and enter the repository.
git clone https://github.com/neil-berg/bull.git
cd bull
Bring up the web client, node server, and Mongo DB with
docker-compose up --build
The web client is served at http://localhost:8000
, and the
node server is at http://localhost:3000
.
To bring down the containers:
docker-compose down
Enter into the client
folder:
cd bull/client
Build the client:
yarn build
Preview the deployment:
netlify deploy
When prompted about which directory to publish, enter dist
.
Verify deployment looks good and then:
netlify deploy --prod
NOTE: It is important to have a netlify.toml
file created in the client's root.
Inside of netlify.toml
, we specify the redirect rule suitable for an SPA:
[[redirects]]
from = "/*"
to = "/index.html"
status = 200