A web application for reading the latest news stories as they happen around the world. Built with React, TypeScript, and SCSS, pulling from both the New York Times API and NewsAPI.
- Node.js 18+
- npm 9+
git clone https://github.com/YOUR_USERNAME/news-reader.git
cd news-readernpm installNote: @testing-library/dom is required as a peer dependency and must be installed explicitly:
npm install -D @testing-library/domCreate a .env file in the project root using .env.example as a template:
cp .env.example .envFill in your API keys:
VITE_NYT_KEY=your_nyt_api_key_here
VITE_NEWSAPI_KEY=your_newsapi_key_here
Getting API keys:
- NYT API: https://developer.nytimes.com — create an app and enable "Top Stories API" and "Article Search API"
- NewsAPI: https://newsapi.org/register — free tier, 100 requests per 24 hours
npm run devApp runs at http://localhost:5173
npm run build| Script | Description |
|---|---|
npm run dev |
Start development server |
npm run build |
Build for production |
npm run lint |
Run ESLint |
npm test |
Run tests |
npm run preview |
Preview production build |
npm testVitest runs in watch mode by default — it re-runs tests on file changes.
Tests cover three areas:
Normalization (src/api/normalize.test.ts) — verifies that raw API responses from both NYT and NewsAPI are correctly transformed into the app's Article type. Example:
test('does not double-prefix absolute image URLs', () => {
const raw = {
...rawNYT,
multimedia: [
{ type: 'image', url: 'https://static01.nyt.com/images/test.jpg' },
],
};
const result = normalizeNYT(raw);
expect(result.imageUrl).toBe('https://static01.nyt.com/images/test.jpg');
});Bookmark hook (src/hooks/useBookmarks.test.ts) — verifies add, remove, and localStorage persistence behavior.
ArticleCard component (src/components/ArticleCard/ArticleCard.test.tsx) — verifies rendering and bookmark toggle interaction.
NewsAPI free tier is limited to 100 requests per 24 hours. The app implements sessionStorage caching with a 5-minute expiry to minimize API calls. If you hit the limit, the app will display a "Too many requests" message and serve cached content where available.
NYT API has separate rate limiting and is generally more permissive.
The task required bookmarking functionality but explicitly left the UI approach open. The design provided no bookmark interface, stating: "There is no design for bookmarking the articles. Feel free to choose an approach you think is most useful."
On desktop, the bookmark icon appears on hover in the top right corner of each article card — discoverable without cluttering the UI. On mobile, hover states don't exist, so the icon is always visible. A one-time tooltip appears on first visit explaining the feature, dismissed by the user via a "Got it" button and never shown again (stored in localStorage).
For navigation, Favorites was added to the sidebar alongside the category links on desktop, and as a "Saved" tab on the mobile homepage tab bar — keeping it one tap away without requiring a separate navigation action. It was also added to the mobile menu overlay as a grid item consistent with the category items.
The implementation stores complete Article objects in localStorage rather than just IDs. The initial approach stored only IDs and re-fetched all categories on the Favorites page to find matching articles — this failed for articles discovered through search, which might not appear in any category feed. Storing the full article on bookmark means the Favorites page reads directly from localStorage with no API calls, and every bookmarked article is always findable regardless of how it was discovered.