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.
