Relaxing app background

Stock Portfolio Application Using Trading212 API & MongoDB

5m 14s

TypeScript
API Endpoints
React
NextJS
MongoDB
Full-Stack

Building a Stock Portfolio Application Using Trading212 API, MongoDB Atlas, and Next.js

Visit Live Project View code on GitHub

Introduction

In this blog post, I will discuss the technical journey of developing a full-stack application for showing the current stock positions in a Trading212 portfolio.

The project begins with setting up a development environment that includes TypeScript, Next.js, and the shadcn/ui library, and progresses to the the development of API endpoints for fetching stock data. The project displays a wide range of skills, from API integration and database management with MongoDB Atlas to frontend development using React and Next.js, including features such as real-time data refresh and currency conversion components.


Setting Up the Environment

In this project, we're going to use TypeScript, Next.js, shadcn/ui library, MongoDB Atlas, and Vercel for hosting. I will begin by setting up my development environment and installing any necessary packages and dependencies.

Step 1: Initializing a Next.js Project using the shadcn/ui template

ShadCN's UI library caught my attention due to its fast, customizable nature, and it significantly reduces the need for UI boilerplate. To start, open your terminal and run:

npx create-next-app -e https://github.com/shadcn/next-template trading-portfolio
cd trading-portfolio

Step 2: Installing Vercel CLI

Since we're hosting our application on Vercel, install the Vercel CLI:

npm i -g vercel

Step 3: Setting Up MongoDB Atlas

  1. Log in to your MongoDB Atlas account and set up a new cluster.
  2. Copy the connection string for later.

Step 4: Installing Mongoose

Mongoose makes it easier to interact with our MongoDB database. Install it by running:

npm install mongoose

Step 5: Configuring Environment Variables

  1. Create a .env.local file in the root of your Next.js project.
  2. Add your MongoDB URI and Trading212 API key:
   MONGO_DB_URI=mongodb_connection_string
   TRADING212_API_KEY=trading212_api_key

Step 6: Running the Development Server

Finally, let's run our development server to see if everything is set up correctly:

npm run dev

Visit http://localhost:3000 in your browser to check if the app is running as expected.


This setup prepares your development environment, integrating ShadCN's UI library for a sleek frontend, MongoDB Atlas for database management, and Vercel CLI for easy deployment.


Developing the 'getNewData' API Endpoint

Before getting started on the frontend, I am going to build an API endpoint to retrieve the data server-side. This endpoint will fetch the current positions in my portfolio from the Trading212 API and store them in a MongoDB database.

The endpoint getNewData is instrumental in fetching the trading data from the Trading212 API. Here's how we built it.

Overview

The getNewData endpoint resides within the app > api directory of our project. This endpoint is structured in two main files: getNewData.ts and route.ts. Each plays a vital role in the data retrieval and processing pipeline. The structure of the getNewData endpoint is as follows:

app
└── api
    └── getNewTrades
        ├── getNewData.ts
        └── route.ts

Fetching and Storing Data: getNewData.ts

  • Purpose: This file is responsible for fetching new data from the Trading212 API and storing it in MongoDB.
  • Functionality:
    • Database Connection: Establishes a connection to MongoDB using Mongoose.
    • Data Retrieval and Update: It fetches open positions from the Trading212 API and updates the Trading212 collection in MongoDB.


import { ITrading212, Trading212 } from "interfaces/ITrading212"
import mongoose from "mongoose"

export async function fetchAndStoreOpenPositions() {
  await mongoose.connect('$ {process.env.MONGO_DB_URL}tradingData')

  // Fetch open positions from Trading212 API
  const response = await fetch(
    "https://live.trading212.com/api/v0/equity/portfolio",
    {
      method: "GET",
      headers: {
        Authorization: '$ {process.env.T212_API_KEY}',
      },
    }
  )
  if (!response.ok) {
    throw new Error('Failed to fetch data: $ {response.statusText}')
  }

  const data: ITrading212 = await response.json()

  // create new document for each position
  for (const position of data) {
    const newPosition = new Trading212({
      ticker: position.ticker,
      quantity: position.quantity,
      averagePrice: position.averagePrice,
      currentPrice: position.currentPrice,
      ppl: position.ppl,
      fxPpl: position.fxPpl,
      initialFillDate: position.initialFillDate,
      frontend: position.frontend,
      maxBuy: position.maxBuy,
      maxSell: position.maxSell,
      pieQuantity: position.pieQuantity,
    })

    // store the data in MongoDB (Atlas)
    // update the document in MongoDB or create it if it doesn't exist
    await Trading212.findOneAndUpdate({ _id: searchData.ticker }, newPosition, {upsert: true})
  }

  console.log("New data stored successfully in MongoDB Atlas")
  return response.status
}
        

View code on GitHub

API Routing: route.ts

  • Purpose: Defines the API route for the getNewData endpoint.
  • Functionality:
    • HTTP Method Handling: Manages GET requests to the /api/getNewData endpoint.
    • Response Management: On successful operation, it returns the newly fetched data as a JSON response. In case of errors, it responds with an error message.

import { NextResponse } from "next/server"
import { fetchAndStoreOpenPositions } from "./getNewData"

// GET request handler for /api/getNewData
export async function GET(req: any) {
  try {
    // fetch data from Trading212 API and store it in MongoDB
    const data = await fetchAndStoreOpenPositions()
    // return the data as a JSON response
    return NextResponse.json(data, {
      status: 200,
    })
    // handle errors
  } catch (error) {
    return NextResponse.json(
      { error: "Failed to get data from MongoDB" },
      {
        status: 500,
      }
    )
  }
}
        

View code on GitHub

Testing the API Endpoint

  • Working as expected. Response status 200 indicates that the data was successfully fetched and stored in MongoDB Atlas.
  • Retreving the data from MongoDB Atlas will be handled by another API endpoint.
Trading212 API

Frontend Development

Overview of the Frontend Architecture

The frontend is designed to display stock position data, fetched from the backend API endpoints created earlier.

Building the UI with React and Next.js

I constructed the user interface using React components, with Next.js for server-side rendering and routing. The core of the frontend is in the page.tsx file within the app > dashboard directory. This file will contain the main page for the dashboard.

Key Features in page.tsx

  • State Management: Uses React's useState and useEffect hooks for managing and fetching data.
  • Data Fetching: The page fetches stock position data and ticker metadata from our API endpoints using fetch.
  • Components: Utilises custom components like ConvertCurrency, and RefreshDataButton for a modular and clean experience.

// app > dashboard > page.tsx

import OpenTradeTable from "@/components/clientComponents/OpenTradeTable"

export default async function IndexPage() {
  return (
    <section className="container grid items-center justify-center gap-6 pb-8 pt-6 md:py-10">
      <div className="jus flex min-w-[700px] max-w-[980px] flex-col items-center gap-2">
        // Renders the OpenTradeTable component
        <OpenTradeTable />
        // Will handle the rest of the UI inside React components for now
      </div>
    </section>
  )
}

View code on GitHub

Integrating the Frontend with API Endpoints

  • The frontend seamlessly integrates with backend API endpoints for data retrieval.
  • This integration enables the display of real-time stock position data fetched from Trading212 API, as well as metadata and currency information.

Using two useEffect hooks, we fetch the data from our API endpoints and store it in state variables. The useEffect hook will run after the component is rendered. It is used to fetch data from the API endpoints and store it in state variables.


// Fetches the basic stock position data from the API endpoint
          useEffect(() => {
    fetch("/api/getTradeData")
      .then((response) => response.json())
      .then((data) => setData(data))
      .catch((error) => console.error("Error fetching data:", error))
  }, [])

// Fetches ticker metadata from the API endpoint (name, currency code, etc.)
  useEffect(() => {
    fetch("/api/getTickerMetadata")
      .then((response) => response.json())
      .then((data) => setMetadata(data))
      .catch((error) => console.error("Error fetching data:", error))
  }, [])        

View code on GitHub

Additional Components

Currency.tsx: Handles Currency Conversion

  • This component is responsible for converting stock prices (provided by the API in their base currency) into the user's own currency.
  • It dynamically fetches and applies the latest currency exchange rates using freecurrencyapi.com.

// Fetch currency conversion json data from freecurrencyapi.com
  fetch(
      'https://api.freecurrencyapi.com/v1/latest?currencies=GBP%2CEUR%2CUSD%2CCAD%2CAUD&base_currency=$ {baseCurrency}',
      options
    )
      .then((response) => response.json())
      .then((data) => setCurrencyData(data))
      .catch((error) => console.error("Error fetching data:", error))
  }, [baseCurrency])
        
// Checks the base currency is not GBX (not availible on freecurrencyapi.com)
// Converts price to users currency and sets it to state variable 'adjustedPrice'
  useEffect(() => {
    if (formatOnly) {
      return
    } else {
      if (currencyCode === "GBX") {
        setAdjustedPrice((currentPrice ?? 0) / 100)
      } else {
        const currencyMultiple = currencyCode
          ? currencyData.data[currencyCode]
          : undefined
        setAdjustedPrice((currentPrice ?? 0) / currencyMultiple)
      }
    }
  }, [currencyData, currencyCode, currentPrice, quantity, formatOnly])

// Formats the price to currency format (e.g. £1,000.00)
  const formattedPrice = adjustedPrice.toLocaleString("en-GB", {
    style: "currency",
    currency: baseCurrency,
  })

// Returns the formatted price in original currency if 'formatOnly' is true
// Returns the formatted price in users currency if 'total' is true
// This is to allow this single component to be used in multiple contexts
  if (formatOnly) {
    return (
      <>
        {currencyCode} {currentPrice}
      </>
    )
  } else if (total) {
    const tickerData = documents.find((item) => item.ticker === ticker)
    const totalAssetOwned = (tickerData?.quantity ?? 0) * adjustedPrice
    const formattedTotal = totalAssetOwned.toLocaleString("en-GB", {
      style: "currency",
      currency: baseCurrency,
    })
    return <>{formattedTotal}</>
  } else {
    return <>{formattedPrice}</>
  }       

View code on GitHub

RefreshDataButton.tsx: Refreshing Data

  • The RefreshDataButton component allows users to refresh stock position data in real-time.
  • This updates the trade data stored in MongoDB, so that we can then fetch the most recent data from the /getTradeData API endpoint.
  • It's a crucial feature for keeping the displayed information up-to-date with the latest market changes.



interface RefreshDataButtonProps {
  // A callback function to be executed after the data is refreshed.
  response: () => void
}
const RefreshDataButton: React.FC<RefreshDataButtonProps> = ({ response }) => {
  const [status, setStatus] = useState(0)
  const [isLoading, setIsLoading] = useState(false)
  const [success, setSuccess] = useState(false)

  const refreshData = async () => {
    // For conditional rendering in the TSX (loading icon, success icon, etc.)
    setIsLoading(true)
    // Fetch the data from the API endpoint
    const responseStatus = await fetch("/api/getNewTrades")
    const data = responseStatus.status
    console.log("API Success", data)
    setStatus(data)
    response()
    // For conditional rendering
    setIsLoading(false)
    // Wait 5 seconds before hiding success icon (to allow for API Rate Limit)
    if (data === 200) {
      setSuccess(true)
      setTimeout(() => {
        setSuccess(false)
      }, 5000)
    }
  }
         

View code on GitHub

Conclusion

The development of the stock portfolio was useful for technical learning and practical application. This project highlights the power of these technologies in creating dynamic, data-driven web applications.

Technologies Used:

  1. Trading212 API: Enabled real-time data fetching of stock positions.
  2. MongoDB Atlas: Provided robust data storage and efficient retrieval capabilities.
  3. Next.js: Allowed for the development of API endpoints and efficient client-server communication.
  4. React: Used in the frontend for building interactive UI components.
  5. TypeScript: Ensured type safety and enhanced code quality.
  6. Vercel: Used for hosting the application, providing a seamless deployment process.

Skills Learned:

  1. API Integration: Mastered the art of connecting and fetching data from external APIs.
  2. Database Management: Gained proficiency in using MongoDB for handling complex datasets and queries.
  3. Frontend Development: Enhanced skills in creating responsive and dynamic user interfaces with React and Next.js.
  4. State Management in React: Efficiently managing application state using hooks like useState and useEffect.
  5. Environment Configuration: Set up and manage development environments, incorporating various tools and libraries.
  6. Data Processing and Presentation: Process and present data in a user-friendly manner, including features like currency conversion and real-time data refresh.

This project not only expanded my technical knowledge but also my problem-solving skills. It shows a solid practical application of modern web development technologies and practices.