TypeScript Tutorial: Building a Simple Interactive Cryptocurrency News Aggregator

In the fast-paced world of cryptocurrencies, staying informed about the latest news and developments is crucial for making informed decisions. Wouldn’t it be great to have a single, convenient place where you could access all the latest news from various sources, all in one place? This tutorial will guide you through building a simple, interactive cryptocurrency news aggregator using TypeScript. This project is designed for beginners to intermediate developers, offering a practical way to learn TypeScript while building something useful.

Why Build a Cryptocurrency News Aggregator?

Cryptocurrency news is scattered across numerous websites, blogs, and social media platforms. Manually checking each source is time-consuming and inefficient. A news aggregator solves this problem by collecting news from multiple sources and presenting it in a consolidated, easy-to-read format. This allows you to:

  • Save time by accessing all news from a single location.
  • Stay updated on the latest market trends and developments.
  • Make more informed trading and investment decisions.
  • Learn and practice TypeScript concepts in a real-world scenario.

Prerequisites

Before we begin, make sure you have the following:

  • Node.js and npm (Node Package Manager) installed on your system.
  • A code editor (e.g., VS Code, Sublime Text, Atom).
  • Basic understanding of JavaScript and HTML.

Setting Up the Project

Let’s start by setting up our project. Open your terminal or command prompt and navigate to the directory where you want to create your project. Then, run the following commands:

mkdir crypto-news-aggregator
cd crypto-news-aggregator
npm init -y
npm install typescript --save-dev
npx tsc --init

These commands do the following:

  • mkdir crypto-news-aggregator: Creates a new directory for your project.
  • cd crypto-news-aggregator: Navigates into the project directory.
  • npm init -y: Initializes a new Node.js project. The -y flag accepts all default options.
  • npm install typescript --save-dev: Installs TypeScript as a development dependency.
  • npx tsc --init: Generates a tsconfig.json file, which configures the TypeScript compiler.

Next, open the tsconfig.json file and configure it to your liking. Some important settings include:

  • target: Specifies the JavaScript version to compile to (e.g., "es6").
  • module: Specifies the module system to use (e.g., "commonjs").
  • outDir: Specifies the output directory for the compiled JavaScript files (e.g., "./dist").
  • sourceMap: Enables the generation of source map files for easier debugging (set to true).

For this project, a basic configuration will suffice. Here’s an example:

{
  "compilerOptions": {
    "target": "es6",
    "module": "commonjs",
    "outDir": "./dist",
    "sourceMap": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*"]
}

Create a src directory and a file named index.ts inside it. This is where we’ll write our TypeScript code.

Fetching Cryptocurrency News

To fetch cryptocurrency news, we’ll use a news API. There are several free and paid APIs available. For this tutorial, we’ll use the CryptoCompare API. You’ll need to sign up for a free API key at CryptoCompare.

First, install the node-fetch package to make HTTP requests:

npm install node-fetch

Now, let’s write the code to fetch news. Open index.ts and add the following code:

import fetch from 'node-fetch';

interface NewsArticle {
  id: string;
  guid: string;
  published_on: number;
  imageurl: string;
  title: string;
  url: string;
  source: string;
  body: string;
  tags: string;
  categories: string;
}

async function getNews(): Promise<NewsArticle[]> {
  const apiKey = 'YOUR_API_KEY'; // Replace with your actual API key
  const apiUrl = `https://min-api.cryptocompare.com/news?api_key=${apiKey}`;

  try {
    const response = await fetch(apiUrl);
    const data = await response.json();

    if (data.Data) {
      return data.Data as NewsArticle[];
    } else {
      console.error('Error fetching news:', data);
      return [];
    }
  } catch (error) {
    console.error('Error fetching news:', error);
    return [];
  }
}

async function main() {
  const news = await getNews();
  if (news.length > 0) {
    console.log('Cryptocurrency News:');
    news.forEach((article) => {
      console.log(`- ${article.title} (${article.source})`);
    });
  } else {
    console.log('Could not fetch news.');
  }
}

main();

Let’s break down this code:

  • We import the node-fetch module to make HTTP requests.
  • We define an interface NewsArticle to represent the structure of a news article.
  • The getNews() function fetches news from the CryptoCompare API. It uses your API key (replace 'YOUR_API_KEY' with your actual key).
  • The main() function calls getNews() and logs the titles and sources of the fetched news articles to the console.

Replace 'YOUR_API_KEY' with your actual API key. Then, compile your TypeScript code by running npx tsc in your terminal. This will create a dist directory containing the compiled JavaScript files.

To run the code, execute node dist/index.js in the terminal. You should see a list of cryptocurrency news headlines in your console.

Displaying News in HTML

Now, let’s display the news in a simple HTML page. Create an index.html file in the root directory of your project with the following content:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Cryptocurrency News Aggregator</title>
    <style>
        body {
            font-family: sans-serif;
            margin: 20px;
        }
        .news-item {
            border: 1px solid #ccc;
            padding: 10px;
            margin-bottom: 10px;
        }
        .news-item h3 {
            margin-top: 0;
        }
        .news-item a {
            color: #007bff;
            text-decoration: none;
        }
    </style>
</head>
<body>
    <h1>Cryptocurrency News</h1>
    <div id="news-container">
        <p>Loading news...</p>
    </div>

    <script src="dist/index.js"></script>
</body>
</html>

This HTML file includes a basic structure for displaying the news. It has a title, a container for the news items, and a script tag that loads the compiled JavaScript file (dist/index.js). The initial content in the news-container is a simple “Loading news…” message.

Next, modify your index.ts file to update the HTML with the fetched news. Add the following code to your index.ts file, replacing the existing main() function:

import fetch from 'node-fetch';

interface NewsArticle {
  id: string;
  guid: string;
  published_on: number;
  imageurl: string;
  title: string;
  url: string;
  source: string;
  body: string;
  tags: string;
  categories: string;
}

async function getNews(): Promise<NewsArticle[]> {
  const apiKey = 'YOUR_API_KEY'; // Replace with your actual API key
  const apiUrl = `https://min-api.cryptocompare.com/news?api_key=${apiKey}`;

  try {
    const response = await fetch(apiUrl);
    const data = await response.json();

    if (data.Data) {
      return data.Data as NewsArticle[];
    } else {
      console.error('Error fetching news:', data);
      return [];
    }
  } catch (error) {
    console.error('Error fetching news:', error);
    return [];
  }
}

async function displayNews() {
  const news = await getNews();
  const newsContainer = document.getElementById('news-container');

  if (newsContainer) {
    newsContainer.innerHTML = ''; // Clear the loading message

    if (news.length > 0) {
      news.forEach((article) => {
        const newsItem = document.createElement('div');
        newsItem.classList.add('news-item');

        const title = document.createElement('h3');
        title.textContent = article.title;

        const source = document.createElement('p');
        source.textContent = `Source: ${article.source}`;

        const link = document.createElement('a');
        link.href = article.url;
        link.textContent = 'Read more';
        link.target = '_blank'; // Open in a new tab

        newsItem.appendChild(title);
        newsItem.appendChild(source);
        newsItem.appendChild(link);

        newsContainer.appendChild(newsItem);
      });
    } else {
      const errorMessage = document.createElement('p');
      errorMessage.textContent = 'Could not fetch news.';
      newsContainer.appendChild(errorMessage);
    }
  }
}

// Call displayNews when the page loads
window.onload = displayNews;

Here’s what changed:

  • We added a displayNews() function that fetches the news, clears the loading message, and dynamically creates HTML elements to display each news article.
  • We use document.getElementById('news-container') to get a reference to the news container in the HTML.
  • We iterate through the fetched news articles and create HTML elements (div, h3, p, and a) to display the title, source, and a link to the full article.
  • We set the href attribute of the link to the article’s URL and add target="_blank" to open the link in a new tab.
  • We call displayNews() when the page has loaded using window.onload.

Compile the TypeScript code again (npx tsc) and open index.html in your browser. You should now see the cryptocurrency news headlines displayed on the page, with links to the full articles.

Adding Error Handling

Error handling is crucial for creating a robust application. Let’s add some error handling to our code to handle potential issues, such as API errors or network problems. We already have some basic error handling in the getNews() function, but let’s expand on it and improve the user experience by displaying more informative error messages.

Modify the displayNews() function in index.ts to include more comprehensive error handling:

import fetch from 'node-fetch';

interface NewsArticle {
  id: string;
  guid: string;
  published_on: number;
  imageurl: string;
  title: string;
  url: string;
  source: string;
  body: string;
  tags: string;
  categories: string;
}

async function getNews(): Promise<NewsArticle[]> {
  const apiKey = 'YOUR_API_KEY'; // Replace with your actual API key
  const apiUrl = `https://min-api.cryptocompare.com/news?api_key=${apiKey}`;

  try {
    const response = await fetch(apiUrl);
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    const data = await response.json();

    if (data.Data) {
      return data.Data as NewsArticle[];
    } else {
      throw new Error('Invalid data format from API');
    }
  } catch (error: any) {
    console.error('Error fetching news:', error);
    return [];
  }
}

async function displayNews() {
  const news = await getNews();
  const newsContainer = document.getElementById('news-container');

  if (newsContainer) {
    newsContainer.innerHTML = ''; // Clear the loading message

    if (news.length > 0) {
      news.forEach((article) => {
        const newsItem = document.createElement('div');
        newsItem.classList.add('news-item');

        const title = document.createElement('h3');
        title.textContent = article.title;

        const source = document.createElement('p');
        source.textContent = `Source: ${article.source}`;

        const link = document.createElement('a');
        link.href = article.url;
        link.textContent = 'Read more';
        link.target = '_blank'; // Open in a new tab

        newsItem.appendChild(title);
        newsItem.appendChild(source);
        newsItem.appendChild(link);

        newsContainer.appendChild(newsItem);
      });
    } else {
      const errorMessage = document.createElement('p');
      errorMessage.textContent = 'Could not fetch news. Please check your API key or network connection.';
      newsContainer.appendChild(errorMessage);
    }
  } else {
    const errorMessage = document.createElement('p');
    errorMessage.textContent = 'Error: Could not find news container.';
    document.body.appendChild(errorMessage);
  }
}

// Call displayNews when the page loads
window.onload = displayNews;

The changes include:

  • In getNews(), we added a check for the HTTP response status using response.ok. If the status is not ok (e.g., 404 Not Found, 500 Internal Server Error), we throw an error.
  • We added a check for invalid data format from the API.
  • In displayNews(), we check if the newsContainer element exists before attempting to manipulate it. If it doesn’t exist, we display an error message directly in the body of the HTML.
  • The error message in displayNews() has been enhanced to be more informative, guiding the user on potential issues.

After compiling and running, test the error handling by intentionally introducing an error, such as an incorrect API key or a network outage. You should see the appropriate error message displayed in the browser.

Adding User Interaction (Filtering News)

To make the news aggregator more interactive, let’s add the ability to filter news articles based on keywords. This will allow users to focus on specific topics of interest.

First, add an input field and a button to your index.html file above the news-container div:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Cryptocurrency News Aggregator</title>
    <style>
        body {
            font-family: sans-serif;
            margin: 20px;
        }
        .news-item {
            border: 1px solid #ccc;
            padding: 10px;
            margin-bottom: 10px;
        }
        .news-item h3 {
            margin-top: 0;
        }
        .news-item a {
            color: #007bff;
            text-decoration: none;
        }
        #filter-container {
            margin-bottom: 10px;
        }
    </style>
</head>
<body>
    <h1>Cryptocurrency News</h1>
    <div id="filter-container">
        <input type="text" id="filter-input" placeholder="Filter by keyword">
        <button id="filter-button">Filter</button>
    </div>
    <div id="news-container">
        <p>Loading news...</p>
    </div>

    <script src="dist/index.js"></script>
</body>
</html>

Next, modify your index.ts file to add the filtering logic:

import fetch from 'node-fetch';

interface NewsArticle {
  id: string;
  guid: string;
  published_on: number;
  imageurl: string;
  title: string;
  url: string;
  source: string;
  body: string;
  tags: string;
  categories: string;
}

async function getNews(): Promise<NewsArticle[]> {
  const apiKey = 'YOUR_API_KEY'; // Replace with your actual API key
  const apiUrl = `https://min-api.cryptocompare.com/news?api_key=${apiKey}`;

  try {
    const response = await fetch(apiUrl);
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    const data = await response.json();

    if (data.Data) {
      return data.Data as NewsArticle[];
    } else {
      throw new Error('Invalid data format from API');
    }
  } catch (error: any) {
    console.error('Error fetching news:', error);
    return [];
  }
}

async function displayNews(filterKeyword: string = '') {
  const news = await getNews();
  const newsContainer = document.getElementById('news-container');

  if (newsContainer) {
    newsContainer.innerHTML = ''; // Clear the loading message

    const filteredNews = news.filter(article => {
        const lowerCaseKeyword = filterKeyword.toLowerCase();
        return article.title.toLowerCase().includes(lowerCaseKeyword) ||
               article.body.toLowerCase().includes(lowerCaseKeyword) ||
               article.tags.toLowerCase().includes(lowerCaseKeyword);
    });

    if (filteredNews.length > 0) {
      filteredNews.forEach((article) => {
        const newsItem = document.createElement('div');
        newsItem.classList.add('news-item');

        const title = document.createElement('h3');
        title.textContent = article.title;

        const source = document.createElement('p');
        source.textContent = `Source: ${article.source}`;

        const link = document.createElement('a');
        link.href = article.url;
        link.textContent = 'Read more';
        link.target = '_blank'; // Open in a new tab

        newsItem.appendChild(title);
        newsItem.appendChild(source);
        newsItem.appendChild(link);

        newsContainer.appendChild(newsItem);
      });
    } else {
      const errorMessage = document.createElement('p');
      errorMessage.textContent = 'No news found matching your criteria.';
      newsContainer.appendChild(errorMessage);
    }
  } else {
    const errorMessage = document.createElement('p');
    errorMessage.textContent = 'Error: Could not find news container.';
    document.body.appendChild(errorMessage);
  }
}

// Add event listener for the filter button
window.onload = () => {
  displayNews(); // Initial display

  const filterButton = document.getElementById('filter-button');
  const filterInput = document.getElementById('filter-input');

  if (filterButton && filterInput) {
    filterButton.addEventListener('click', () => {
      const keyword = (filterInput as HTMLInputElement).value;
      displayNews(keyword);
    });
  }
};

Here’s what the changes do:

  • We added an input field and a button with the IDs filter-input and filter-button, respectively, in the HTML.
  • In displayNews(), we now accept an optional filterKeyword parameter.
  • We filter the news articles using the filter() method. We convert both the keyword and the article title, body, and tags to lowercase for case-insensitive matching.
  • We added an event listener to the filter button in window.onload. When the button is clicked, it gets the value from the input field and calls displayNews() with the keyword.
  • We also updated the initial display to call displayNews() without a filter keyword to display all news initially.

Compile the TypeScript code (npx tsc) and refresh your browser. You should now be able to enter a keyword in the filter input and click the filter button to see only the news articles that match your search criteria. If no matching articles are found, a “No news found matching your criteria.” message will be displayed.

Common Mistakes and How to Fix Them

Here are some common mistakes beginners often make when working with TypeScript and how to fix them:

  • Incorrect TypeScript Configuration: A common issue is misconfiguring the tsconfig.json file. This can lead to compilation errors or unexpected behavior.
    • Fix: Double-check your tsconfig.json settings. Ensure that the target is set to the appropriate JavaScript version (e.g., "es6" or higher). Also, verify the module setting (e.g., "commonjs" or "esnext"). Use a code editor with TypeScript support to get suggestions and identify errors in your tsconfig.json file.
  • Ignoring Type Errors: TypeScript’s primary benefit is its type checking. Ignoring type errors defeats this purpose.
    • Fix: Pay close attention to the TypeScript compiler’s error messages. They provide valuable information about type mismatches and other issues. Use your code editor’s features (like inline error highlighting) to identify and fix type errors promptly. Understand the error messages; they’re designed to help you write more robust code.
  • Incorrect API Key Handling: Exposing your API key directly in your client-side code is a security risk.
    • Fix: Never hardcode your API key in your client-side JavaScript or TypeScript code. Consider using environment variables or a backend server to securely store and retrieve your API key. This tutorial is simplified for demonstration purposes; in a real-world application, you would never expose your API key directly in the client-side code.
  • Not Handling Asynchronous Operations Correctly: When working with APIs, you’re dealing with asynchronous operations (e.g., fetching data from the API). Not handling these operations correctly can lead to unexpected results.
    • Fix: Use async/await to handle asynchronous operations. This makes your code more readable and easier to understand. Ensure that you handle potential errors within your try...catch blocks.
  • Incorrect DOM Manipulation: When working with HTML, make sure you’re properly selecting elements and updating their content.
    • Fix: Double-check your element selectors (e.g., using document.getElementById() or document.querySelector()). Ensure that the elements exist before attempting to manipulate them. Use the browser’s developer tools to inspect the HTML and verify that your changes are being applied correctly.
  • Forgetting to Compile: After making changes to your TypeScript code, you need to recompile it to generate the updated JavaScript files.
    • Fix: Run npx tsc in your terminal after making changes to your TypeScript files. Consider setting up a build process that automatically recompiles your code whenever you save changes (e.g., using a task runner like Webpack or Parcel).

Key Takeaways

In this tutorial, we’ve covered the fundamentals of building a cryptocurrency news aggregator using TypeScript. You’ve learned how to:

  • Set up a TypeScript project.
  • Fetch data from a news API using node-fetch.
  • Define interfaces to represent data structures.
  • Display data in an HTML page.
  • Handle errors and improve the user experience.
  • Add user interaction with filtering.

This project provides a solid foundation for building more complex web applications with TypeScript. You can extend this project by adding features such as:

  • More sophisticated filtering options (e.g., filtering by source, date range).
  • User authentication and personalization.
  • Data visualization (e.g., charts of news sentiment).
  • Integration with other cryptocurrency APIs (e.g., price data).

FAQ

  1. How do I get an API key for the CryptoCompare API? You can sign up for a free API key at CryptoCompare. Follow the instructions on their website to create an account and obtain your API key.
  2. Why am I getting an “HTTP error”? This error usually indicates a problem with your API request. Check the following:
    • Make sure you have a valid API key and that it’s correctly entered in your code.
    • Verify that you have a stable internet connection.
    • Check the CryptoCompare API documentation to ensure you’re using the correct API endpoint and parameters.
  3. How can I deploy this application? You can deploy this application using various methods, such as:
    • Static Site Hosting: Deploy the compiled JavaScript and HTML files to a static site hosting service like Netlify or GitHub Pages.
    • Node.js Server: Run the compiled JavaScript on a Node.js server.
    • Containerization (Docker): Package your application into a Docker container for easier deployment.
  4. Can I use a different news API? Yes, absolutely! The principles we’ve covered apply to any news API. You’ll need to adapt the code to the specific API’s endpoints and data structures. Consider APIs like NewsAPI.org or others that offer cryptocurrency news.

Building this cryptocurrency news aggregator is more than just a coding exercise; it’s a journey into the practical application of TypeScript. By understanding the core concepts and working through the steps outlined, you’ve not only created a functional application but have also strengthened your grasp of TypeScript fundamentals. Remember to always prioritize clean code, error handling, and user experience. This project serves as a starting point; the possibilities for enhancement and expansion are as vast and dynamic as the cryptocurrency market itself. Embrace the learning process, experiment with new features, and continue to refine your skills. The knowledge you’ve gained here will undoubtedly serve you well in future projects, paving the way for more sophisticated and impactful applications.