In this tutorial, we'll build a complete news aggregator application using AllNewsAPI and React. By the end, you'll have a fully functional app that displays news from multiple countries and categories.

What We'll Build

Our news aggregator will feature:

  • Country-based news filtering (244 countries supported)
  • Category selection (business, technology, sports, entertainment, and more)
  • Search functionality
  • Responsive design
  • Real-time updates

Prerequisites

Before we start, make sure you have:

  • Node.js installed (v16 or higher)
  • An AllNewsAPI account and API key
  • Basic knowledge of React and JavaScript

Step 1: Project Setup

Create a new React app using Vite:

npm create vite@latest news-aggregator -- --template react
cd news-aggregator
npm install

Install additional dependencies:

npm install axios date-fns

Step 2: Create the API Service

Create a file src/services/newsApi.js:

import axios from 'axios';

const API_KEY = import.meta.env.VITE_NEWS_API_KEY;
const BASE_URL = 'https://api.allnewsapi.com';

const newsApi = axios.create({
  baseURL: BASE_URL,
  params: {
    apikey: API_KEY
  }
});

export const getHeadlines = async (country = 'us', category = null) => {
  try {
    const params = { country };
    if (category) params.category = category;
    
    const response = await newsApi.get('/headlines', { params });
    return response.data;
  } catch (error) {
    console.error('Error fetching headlines:', error);
    throw error;
  }
};

export const searchNews = async (query, country = 'us') => {
  try {
    const response = await newsApi.get('/headlines', {
      params: { q: query, country }
    });
    return response.data;
  } catch (error) {
    console.error('Error searching news:', error);
    throw error;
  }
};

export default newsApi;

Step 3: Create the News Card Component

Create src/components/NewsCard.jsx:

import { formatDistanceToNow } from 'date-fns';

function NewsCard({ article }) {
  return (
    <div className="news-card">
      {article.image && (
        <img 
          src={article.image} 
          alt={article.title}
          className="news-card__image"
        />
      )}
      <div className="news-card__content">
        <h3 className="news-card__title">{article.title}</h3>
        <p className="news-card__description">{article.description}</p>
        <div className="news-card__meta">
          <span className="news-card__source">{article.source.name}</span>
          <span className="news-card__time">
            {formatDistanceToNow(new Date(article.publishedAt), { addSuffix: true })}
          </span>
        </div>
        <a 
          href={article.url} 
          target="_blank" 
          rel="noopener noreferrer"
          className="news-card__link"
        >
          Read More →
        </a>
      </div>
    </div>
  );
}

export default NewsCard;

Step 4: Create the Filter Component

Create src/components/Filters.jsx:

function Filters({ country, category, onCountryChange, onCategoryChange }) {
  const countries = [
    { code: 'us', name: 'United States' },
    { code: 'gb', name: 'United Kingdom' },
    { code: 'ca', name: 'Canada' },
    { code: 'au', name: 'Australia' },
    { code: 'de', name: 'Germany' },
    { code: 'fr', name: 'France' },
    { code: 'jp', name: 'Japan' }
  ];

  const categories = [
    'business',
    'entertainment',
    'finance',
    'food',
    'health',
    'politics',
    'science',
    'sports',
    'technology',
    'world'
  ];

  return (
    <div className="filters">
      <div className="filter-group">
        <label htmlFor="country">Country:</label>
        <select 
          id="country"
          value={country} 
          onChange={(e) => onCountryChange(e.target.value)}
        >
          {countries.map(c => (
            <option key={c.code} value={c.code}>{c.name}</option>
          ))}
        </select>
      </div>

      <div className="filter-group">
        <label htmlFor="category">Category:</label>
        <select 
          id="category"
          value={category || ''} 
          onChange={(e) => onCategoryChange(e.target.value || null)}
        >
          <option value="">All Categories</option>
          {categories.map(cat => (
            <option key={cat} value={cat}>
              {cat.charAt(0).toUpperCase() + cat.slice(1)}
            </option>
          ))}
        </select>
      </div>
    </div>
  );
}

export default Filters;

Step 5: Create the Main App Component

Update src/App.jsx:

import { useState, useEffect } from 'react';
import { getHeadlines, searchNews } from './services/newsApi';
import NewsCard from './components/NewsCard';
import Filters from './components/Filters';
import './App.css';

function App() {
  const [articles, setArticles] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const [country, setCountry] = useState('us');
  const [category, setCategory] = useState(null);
  const [searchQuery, setSearchQuery] = useState('');

  useEffect(() => {
    fetchNews();
  }, [country, category]);

  const fetchNews = async () => {
    try {
      setLoading(true);
      setError(null);
      const data = await getHeadlines(country, category);
      setArticles(data.articles || []);
    } catch (err) {
      setError('Failed to fetch news. Please try again later.');
      console.error(err);
    } finally {
      setLoading(false);
    }
  };

  const handleSearch = async (e) => {
    e.preventDefault();
    if (!searchQuery.trim()) {
      fetchNews();
      return;
    }

    try {
      setLoading(true);
      setError(null);
      const data = await searchNews(searchQuery, country);
      setArticles(data.articles || []);
    } catch (err) {
      setError('Search failed. Please try again.');
      console.error(err);
    } finally {
      setLoading(false);
    }
  };

  return (
    <div className="app">
      <header className="app-header">
        <h1>News Aggregator</h1>
        <p>Powered by AllNewsAPI</p>
      </header>

      <div className="app-controls">
        <Filters
          country={country}
          category={category}
          onCountryChange={setCountry}
          onCategoryChange={setCategory}
        />

        <form onSubmit={handleSearch} className="search-form">
          <input
            type="text"
            placeholder="Search news..."
            value={searchQuery}
            onChange={(e) => setSearchQuery(e.target.value)}
            className="search-input"
          />
          <button type="submit" className="search-button">
            Search
          </button>
        </form>
      </div>

      <main className="app-content">
        {loading && <div className="loading">Loading news...</div>}
        
        {error && <div className="error">{error}</div>}
        
        {!loading && !error && articles.length === 0 && (
          <div className="no-results">No articles found</div>
        )}
        
        {!loading && !error && articles.length > 0 && (
          <div className="news-grid">
            {articles.map((article, index) => (
              <NewsCard key={index} article={article} />
            ))}
          </div>
        )}
      </main>
    </div>
  );
}

export default App;

Step 6: Add Styling

Create src/App.css with your preferred styles. Here's a basic example:

.app {
  max-width: 1200px;
  margin: 0 auto;
  padding: 20px;
}

.app-header {
  text-align: center;
  margin-bottom: 30px;
}

.news-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 20px;
}

.news-card {
  border: 1px solid #ddd;
  border-radius: 8px;
  overflow: hidden;
  transition: transform 0.2s;
}

.news-card:hover {
  transform: translateY(-4px);
  box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}

Step 7: Environment Variables

Create a .env file:

VITE_NEWS_API_KEY=your_api_key_here

Step 8: Run Your App

npm run dev

Visit http://localhost:5173 to see your news aggregator in action!

Next Steps

Enhance your app with:

  • Pagination for large result sets
  • Favorite articles feature
  • Dark mode toggle
  • Social sharing buttons
  • Article bookmarking

Conclusion

You've built a fully functional news aggregator! This foundation can be extended with more features and customization to suit your needs.

Check out our API documentation for more advanced features and endpoints.