TypeScript Tutorial: Building a Simple Interactive Cryptocurrency Portfolio

In the fast-paced world of cryptocurrencies, staying informed about your investments is crucial. Manually tracking your portfolio across multiple exchanges can be a tedious and error-prone process. Wouldn’t it be great to have a simple, interactive application that allows you to see your holdings, their current values, and overall performance at a glance? This tutorial will guide you through building precisely that: a straightforward cryptocurrency portfolio tracker using TypeScript, a language that brings structure and maintainability to your JavaScript projects.

Why TypeScript?

Before we dive in, let’s address the elephant in the room: why TypeScript? TypeScript is a superset of JavaScript that adds static typing. This means you can define the types of variables, function parameters, and return values. This offers several benefits:

  • Early Error Detection: TypeScript catches type-related errors during development, before you even run your code. This saves you time and frustration later on.
  • Improved Code Readability: Type annotations make your code easier to understand and maintain, especially in large projects.
  • Enhanced Code Completion: TypeScript-aware editors provide better code completion and suggestions, boosting your productivity.
  • Refactoring Safety: When you refactor your code, TypeScript helps ensure that your changes don’t introduce unexpected errors.

In essence, TypeScript helps you write more robust, maintainable, and scalable JavaScript code. For beginners, it might seem like an extra layer of complexity, but the benefits quickly become apparent as your projects grow.

Setting Up Your Environment

To get started, you’ll need the following:

  • Node.js and npm (Node Package Manager): These are essential for running JavaScript code and managing project dependencies. You can download them from nodejs.org.
  • A Code Editor: Visual Studio Code (VS Code) is a popular choice, with excellent TypeScript support. You can download it from code.visualstudio.com.
  • Basic Familiarity with JavaScript: While this tutorial aims to be beginner-friendly, some prior knowledge of JavaScript fundamentals will be helpful.

Let’s set up a new project:

  1. Create a Project Directory: Open your terminal or command prompt and create a new directory for your project. For example: mkdir crypto-portfolio
  2. Navigate to the Directory: Change your current directory to the newly created project directory: cd crypto-portfolio
  3. Initialize npm: Run the following command to initialize an npm project: npm init -y. This creates a package.json file, which will manage your project’s dependencies.
  4. Install TypeScript: Install TypeScript globally or locally. For local installation, run: npm install typescript --save-dev. This command installs TypeScript as a development dependency.
  5. Create a TypeScript Configuration File: Run the following command to generate a tsconfig.json file. This file configures the TypeScript compiler: npx tsc --init.

Your project structure should now look something like this:

crypto-portfolio/
├── node_modules/
├── package.json
├── tsconfig.json
└──

Project Structure and Core Concepts

Before we start coding, let’s outline the structure of our application. We’ll keep it simple for this tutorial, focusing on the core functionality.

  • Data Fetching: We’ll use a public API (like CoinGecko or CoinMarketCap) to fetch real-time cryptocurrency prices.
  • Data Storage: We’ll store the portfolio data (cryptocurrencies, quantities, and purchase prices) in a simple data structure. For simplicity, we’ll hardcode the portfolio data initially, but you can later expand this to include user input or data persistence.
  • Calculation: We’ll calculate the current value of each holding, the total portfolio value, and potentially profit/loss.
  • Display: We’ll display the portfolio data in a user-friendly format, likely in the console for this tutorial. In a real-world application, you’d use HTML and CSS for a visual interface.

Let’s define some core concepts in TypeScript:

1. Interfaces

Interfaces define the structure of objects. They specify the properties and their types. This is crucial for ensuring that your data is consistent. Let’s define an interface for a cryptocurrency:

interface Cryptocurrency {
  id: string; // e.g., "bitcoin"
  name: string; // e.g., "Bitcoin"
  symbol: string; // e.g., "BTC"
  current_price: number; // Current price in USD
}

2. Data Types

TypeScript’s type system is fundamental. We’ll use types like string, number, and array to define the types of our variables and function parameters. For example:

let cryptoName: string = "Bitcoin";
let price: number = 50000;
let holdings: Cryptocurrency[] = []; // An array of Cryptocurrency objects

3. Functions

Functions are blocks of reusable code. We’ll use functions to fetch data, calculate values, and display results. TypeScript allows you to specify the types of function parameters and the return type:

function calculateTotalValue(portfolio: Cryptocurrency[], quantities: { [id: string]: number }): number {
  let total = 0;
  for (const crypto of portfolio) {
    const quantity = quantities[crypto.id] || 0;
    total += crypto.current_price * quantity;
  }
  return total;
}

Fetching Cryptocurrency Data

We’ll use the CoinGecko API for fetching cryptocurrency prices. CoinGecko provides a free API with a generous rate limit. First, install the node-fetch library to make API requests:

npm install node-fetch

Now, let’s create a function to fetch the current prices of cryptocurrencies. Create a file named index.ts in your project directory:

// index.ts
import fetch from 'node-fetch';

interface Cryptocurrency {
  id: string;
  name: string;
  symbol: string;
  current_price: number;
}

async function fetchCryptoPrices(cryptoIds: string[]): Promise {
  const ids = cryptoIds.join('%2C'); // CoinGecko requires comma-separated IDs
  const url = `https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&ids=${ids}&order=market_cap_desc&per_page=100&page=1&sparkline=false&price_change_percentage=24h`;

  try {
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    const data = await response.json() as any[]; // CoinGecko returns an array
    return data.map(item => ({
      id: item.id,
      name: item.name,
      symbol: item.symbol.toUpperCase(),
      current_price: item.current_price,
    }));
  } catch (error: any) {
    console.error("Error fetching data:", error.message);
    return [];
  }
}

// Example usage
async function main() {
  const cryptoIds = ["bitcoin", "ethereum", "litecoin"];
  const prices = await fetchCryptoPrices(cryptoIds);
  console.log(prices);
}

main();

Let’s break down this code:

  • Import node-fetch: This line imports the fetch function, which we’ll use to make HTTP requests to the CoinGecko API.
  • Interface Cryptocurrency: This interface defines the structure of the data we expect from the API.
  • fetchCryptoPrices Function: This asynchronous function takes an array of cryptocurrency IDs as input and returns a Promise that resolves to an array of Cryptocurrency objects.
  • API Request: The function constructs the API URL using the provided crypto IDs.
  • Error Handling: The try...catch block handles potential errors during the API request.
  • Data Mapping: The .map() function transforms the API response into an array of Cryptocurrency objects, extracting the necessary data.
  • Example Usage (main function): This function demonstrates how to call fetchCryptoPrices and log the results to the console.

To run this code, open your terminal and run the following command:

npx ts-node index.ts

This command uses ts-node, a TypeScript execution environment, to run your index.ts file directly without needing to compile it first. You should see an array of cryptocurrency prices printed to the console.

Defining Your Portfolio

Next, let’s define your portfolio. For simplicity, we’ll hardcode the cryptocurrencies and their quantities. In a real-world application, you’d likely allow users to input this data or retrieve it from a database.

Add the following code to your index.ts file, below the fetchCryptoPrices function:


interface PortfolioItem {
  id: string;
  quantity: number;
  purchasePrice: number; // Optional, to calculate profit/loss
}

const portfolio: PortfolioItem[] = [
  { id: "bitcoin", quantity: 0.5, purchasePrice: 30000 },
  { id: "ethereum", quantity: 5, purchasePrice: 2000 },
  { id: "litecoin", quantity: 20, purchasePrice: 100 },
];

Here, we’ve defined a PortfolioItem interface to represent each item in your portfolio. The portfolio array holds your holdings, including the cryptocurrency ID, quantity, and purchase price.

Calculating Portfolio Value

Now, let’s calculate the total value of your portfolio. We’ll modify the main function to incorporate the portfolio data and the fetched prices. Add the following code inside the main function, after fetching the prices:


  const cryptoIds = portfolio.map(item => item.id);
  const prices = await fetchCryptoPrices(cryptoIds);

  // Create a map of crypto prices for easy lookup
  const priceMap: { [id: string]: number } = {};
  prices.forEach(crypto => {
    priceMap[crypto.id] = crypto.current_price;
  });

  let totalValue = 0;
  portfolio.forEach(item => {
    const price = priceMap[item.id] || 0; // Handle cases where price might be missing
    totalValue += price * item.quantity;
    console.log(`${item.id.toUpperCase()}: ${item.quantity} x $${price.toFixed(2)} = $${(price * item.quantity).toFixed(2)}`);
  });

  console.log("------------------------");
  console.log(`Total Portfolio Value: $${totalValue.toFixed(2)}`);

Let’s break down this code:

  • Get Crypto IDs: We extract the cryptocurrency IDs from the portfolio array.
  • Fetch Prices: We fetch the current prices for the cryptocurrencies in your portfolio.
  • Create Price Map: We create a map (object) to store the prices, making it easier to look up the price of a specific cryptocurrency by its ID.
  • Calculate Total Value: We iterate through the portfolio array, look up the current price for each cryptocurrency, and calculate the value of each holding. We also handle the case where a price might be missing.
  • Display Results: We print the value of each holding and the total portfolio value to the console.

Your complete index.ts file should now look like this:


// index.ts
import fetch from 'node-fetch';

interface Cryptocurrency {
  id: string;
  name: string;
  symbol: string;
  current_price: number;
}

interface PortfolioItem {
  id: string;
  quantity: number;
  purchasePrice: number; // Optional, to calculate profit/loss
}

const portfolio: PortfolioItem[] = [
  { id: "bitcoin", quantity: 0.5, purchasePrice: 30000 },
  { id: "ethereum", quantity: 5, purchasePrice: 2000 },
  { id: "litecoin", quantity: 20, purchasePrice: 100 },
];

async function fetchCryptoPrices(cryptoIds: string[]): Promise {
  const ids = cryptoIds.join('%2C'); // CoinGecko requires comma-separated IDs
  const url = `https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&ids=${ids}&order=market_cap_desc&per_page=100&page=1&sparkline=false&price_change_percentage=24h`;

  try {
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    const data = await response.json() as any[]; // CoinGecko returns an array
    return data.map(item => ({
      id: item.id,
      name: item.name,
      symbol: item.symbol.toUpperCase(),
      current_price: item.current_price,
    }));
  } catch (error: any) {
    console.error("Error fetching data:", error.message);
    return [];
  }
}

async function main() {
  const cryptoIds = portfolio.map(item => item.id);
  const prices = await fetchCryptoPrices(cryptoIds);

  // Create a map of crypto prices for easy lookup
  const priceMap: { [id: string]: number } = {};
  prices.forEach(crypto => {
    priceMap[crypto.id] = crypto.current_price;
  });

  let totalValue = 0;
  portfolio.forEach(item => {
    const price = priceMap[item.id] || 0; // Handle cases where price might be missing
    totalValue += price * item.quantity;
    console.log(`${item.id.toUpperCase()}: ${item.quantity} x $${price.toFixed(2)} = $${(price * item.quantity).toFixed(2)}`);
  });

  console.log("------------------------");
  console.log(`Total Portfolio Value: $${totalValue.toFixed(2)}`);
}

main();

Run the code again using npx ts-node index.ts, and you should see the value of each holding and the total portfolio value printed to the console.

Adding Profit/Loss Calculation (Optional)

To calculate profit/loss, we need the purchase price of each cryptocurrency. We’ve already included the purchasePrice property in the PortfolioItem interface. Let’s add the profit/loss calculation to the code.

Modify the main function to include the following calculations:


  portfolio.forEach(item => {
    const price = priceMap[item.id] || 0;
    const value = price * item.quantity;
    const cost = item.purchasePrice * item.quantity;
    const profitLoss = value - cost;
    totalValue += value;

    console.log(`${item.id.toUpperCase()}: ${item.quantity} x $${price.toFixed(2)} = $${value.toFixed(2)} (Profit/Loss: $${profitLoss.toFixed(2)})`);
  });

Now, in the console output, you’ll see the profit or loss for each holding.

Error Handling and Common Mistakes

Effective error handling is crucial for any application. Let’s look at some common mistakes and how to fix them:

  • API Errors: The CoinGecko API might be unavailable or return errors. Always include error handling in your fetchCryptoPrices function. Make sure to check the response status (response.ok) and handle potential exceptions.
  • Type Mismatches: TypeScript helps prevent type mismatches, but you still need to be careful. Double-check your types and use type assertions (e.g., as any[]) when necessary, but use them cautiously.
  • Missing Data: The API might not return data for all cryptocurrencies. Handle cases where the price of a cryptocurrency might be missing by providing a default value (e.g., 0).
  • Incorrect API Usage: Carefully read the API documentation (CoinGecko’s in this case) to understand the required parameters and rate limits. Incorrect usage can lead to errors or your API key being blocked.

Example of handling a missing price (already included in the code):


  const price = priceMap[item.id] || 0; // Use 0 if the price is not found

Improving the Application

This tutorial provides a basic foundation for a cryptocurrency portfolio tracker. Here are some ways to enhance it:

  • User Interface: Instead of console output, create a user-friendly interface using HTML, CSS, and JavaScript (or a framework like React, Angular, or Vue.js).
  • Data Persistence: Implement a way to store the portfolio data, either locally (e.g., using local storage) or on a server (e.g., using a database).
  • User Input: Allow users to add, edit, and delete cryptocurrencies in their portfolio.
  • Real-time Updates: Use WebSockets or server-sent events to get real-time price updates.
  • Advanced Calculations: Calculate more advanced metrics like profit/loss percentages, average purchase price, and portfolio diversification.
  • Error Handling: Implement more robust error handling and user feedback.
  • Testing: Write unit tests to ensure the reliability of your code.
  • API Key Management: If the API requires an API key, securely store and manage the API key.

Key Takeaways

  • TypeScript adds static typing to JavaScript, improving code quality, readability, and maintainability.
  • Interfaces define the structure of objects, ensuring data consistency.
  • Asynchronous functions (using async/await) are essential for handling API requests.
  • Error handling is crucial for building robust applications.
  • You can expand this simple application with a user interface, data persistence, and more advanced features.

FAQ

Here are some frequently asked questions:

  1. What is TypeScript? TypeScript is a superset of JavaScript that adds static typing. It helps you write more organized and maintainable code.
  2. Why should I use TypeScript? TypeScript catches errors early, improves code readability, and enhances developer productivity.
  3. Where can I find cryptocurrency price data? You can use public APIs like CoinGecko or CoinMarketCap.
  4. How do I handle API errors? Always check the response status (response.ok) and use try...catch blocks to handle potential errors.
  5. Can I use this for real-time tracking? Yes, you can enhance the application to get real-time price updates using WebSockets or server-sent events.

Building a cryptocurrency portfolio tracker in TypeScript provides a solid foundation for understanding both the language and the world of cryptocurrencies. By applying the principles of static typing, API interaction, and data manipulation, you can create a useful and informative tool to manage your digital assets. As you delve deeper, consider exploring more advanced features like user interfaces, data persistence, and real-time updates to truly bring your portfolio tracker to life. The skills you gain will be valuable not only in the context of cryptocurrencies but also in broader software development scenarios. With the structure and safety of TypeScript, you’ll be well-equipped to tackle increasingly complex projects and navigate the ever-evolving landscape of software engineering with confidence.