Complete React examples with backend proxy patterns
// This exposes your API key to everyone
const apiKey = 'byul_api_key';
fetch('https://api.byul.ai/api/v2/news', {
headers: { 'X-API-Key': apiKey } // Not recommended: Exposed in browser
});
// React calls your backend, backend calls Byul API
fetch('/api/news') // Recommended: Your backend handles API key
// server/server.js
const express = require('express');
const fetch = require('node-fetch');
const cors = require('cors');
const app = express();
app.use(cors());
app.use(express.json());
// Proxy endpoint
app.get('/api/news', async (req, res) => {
try {
const { limit, minImportance, symbol, q } = req.query;
const params = new URLSearchParams({
limit: limit || '20',
minImportance: minImportance || '6'
});
if (symbol) params.append('symbol', symbol);
if (q) params.append('q', q);
const response = await fetch(`https://api.byul.ai/api/v2/news?${params}`, {
headers: { 'X-API-Key': process.env.BYUL_API_KEY }
});
if (!response.ok) {
return res.status(response.status).json({ error: 'API request failed' });
}
const data = await response.json();
res.json(data);
} catch (error) {
res.status(500).json({ error: 'Internal server error' });
}
});
app.listen(3001, () => {
console.log('Proxy server running on port 3001');
});
import React, { useState, useEffect } from 'react';
function NewsComponent() {
const [news, setNews] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetchNews();
}, []);
const fetchNews = async () => {
try {
setLoading(true);
// Call your backend, not Byul API directly
const response = await fetch('/api/news?limit=10&minImportance=7');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setNews(data.items);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
if (loading) return <div>Loading news...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div className="news-container">
<h2>Financial News</h2>
{news.map(article => (
<div key={article._id} className="news-item">
<h3>
<a href={article.url} target="_blank" rel="noopener noreferrer">
{article.title}
</a>
</h3>
<p>
Importance: {article.importanceScore}/10 |
Category: {article.category} |
{article.symbols && article.symbols.length > 0 && (
<span> Symbols: {article.symbols.join(', ')}</span>
)}
</p>
<small>{new Date(article.date).toLocaleString()}</small>
</div>
))}
</div>
);
}
export default NewsComponent;
import { useState, useEffect } from 'react';
function useNews(filters = {}) {
const [news, setNews] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const fetchNews = async (newFilters = {}) => {
setLoading(true);
setError(null);
try {
const params = new URLSearchParams({
limit: '20',
minImportance: '6',
...filters,
...newFilters
});
const response = await fetch(`/api/news?${params}`);
if (!response.ok) {
throw new Error(`Failed to fetch news: ${response.status}`);
}
const data = await response.json();
setNews(data.items);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchNews();
}, []);
return {
news,
loading,
error,
refetch: fetchNews
};
}
// Usage
function NewsApp() {
const { news, loading, error, refetch } = useNews();
const [symbol, setSymbol] = useState('');
const handleSymbolFilter = (e) => {
e.preventDefault();
refetch({ symbol });
};
return (
<div>
<form onSubmit={handleSymbolFilter}>
<input
type="text"
placeholder="Filter by symbol (e.g., AAPL)"
value={symbol}
onChange={(e) => setSymbol(e.target.value)}
/>
<button type="submit">Filter</button>
</form>
{loading && <p>Loading...</p>}
{error && <p>Error: {error}</p>}
<div>
{news.map(article => (
<div key={article._id}>
<h3>{article.title}</h3>
<p>Importance: {article.importanceScore}/10</p>
</div>
))}
</div>
</div>
);
}
import React, { useState, useEffect, useRef } from 'react';
function NewsDashboard() {
const [articles, setArticles] = useState([]);
const [connected, setConnected] = useState(false);
const [error, setError] = useState(null);
const intervalRef = useRef(null);
useEffect(() => {
startPolling();
return () => stopPolling();
}, []);
const startPolling = () => {
setConnected(true);
// Fetch immediately
fetchLatestNews();
// Then poll every 30 seconds
intervalRef.current = setInterval(fetchLatestNews, 30000);
};
const stopPolling = () => {
setConnected(false);
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
};
const fetchLatestNews = async () => {
try {
const response = await fetch('/api/news?limit=10&minImportance=7');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
// Update articles, avoiding duplicates
setArticles(prevArticles => {
const existingIds = new Set(prevArticles.map(a => a._id));
const newArticles = data.items.filter(a => !existingIds.has(a._id));
return [...newArticles, ...prevArticles].slice(0, 50); // Keep latest 50
});
setError(null);
} catch (err) {
setError(err.message);
setConnected(false);
}
};
return (
<div className="dashboard">
<header className="dashboard-header">
<h1>Byul Financial News Dashboard</h1>
<div className={`status ${connected ? 'connected' : 'disconnected'}`}>
{connected ? 'Connected' : 'Disconnected'}
</div>
</header>
{error && (
<div className="error-banner">
Error: {error}
<button onClick={startPolling}>Retry</button>
</div>
)}
<div className="news-grid">
{articles.map(article => (
<div
key={article._id}
className={`news-card importance-${article.importanceScore}`}
>
<div className="news-header">
<span className="importance-badge">
{article.importanceScore}/10
</span>
<span className="category">{article.category}</span>
</div>
<h3>
<a href={article.url} target="_blank" rel="noopener noreferrer">
{article.title}
</a>
</h3>
{article.symbols && article.symbols.length > 0 && (
<div className="symbols">
{article.symbols.map(symbol => (
<span key={symbol} className="symbol-tag">{symbol}</span>
))}
</div>
)}
<div className="news-meta">
<small>{new Date(article.date).toLocaleString()}</small>
{article.sentiment && (
<span className={`sentiment ${article.sentiment}`}>
{article.sentiment}
</span>
)}
</div>
</div>
))}
</div>
{articles.length === 0 && !error && (
<div className="no-news">
{connected ? 'Waiting for news updates...' : 'Not connected'}
</div>
)}
</div>
);
}
export default NewsDashboard;
import React, { useState, useEffect } from 'react';
function PortfolioTracker() {
const [portfolio, setPortfolio] = useState(['AAPL', 'GOOGL', 'MSFT']);
const [news, setNews] = useState([]);
const [newSymbol, setNewSymbol] = useState('');
const [loading, setLoading] = useState(false);
useEffect(() => {
if (portfolio.length > 0) {
fetchPortfolioNews();
}
}, [portfolio]);
const fetchPortfolioNews = async () => {
setLoading(true);
try {
// Fetch news for each symbol in parallel
const promises = portfolio.map(symbol =>
fetch(`/api/news?symbol=${symbol}&minImportance=6&limit=20`)
.then(res => res.json())
);
const results = await Promise.all(promises);
// Combine and deduplicate
const allNews = [];
const seenIds = new Set();
results.forEach(data => {
data.items?.forEach(article => {
if (!seenIds.has(article._id)) {
allNews.push(article);
seenIds.add(article._id);
}
});
});
// Sort by importance and date
allNews.sort((a, b) => {
if (b.importanceScore !== a.importanceScore) {
return b.importanceScore - a.importanceScore;
}
return new Date(b.date) - new Date(a.date);
});
setNews(allNews);
} catch (error) {
console.error('Failed to fetch portfolio news:', error);
} finally {
setLoading(false);
}
};
const addSymbol = (e) => {
e.preventDefault();
const symbol = newSymbol.trim().toUpperCase();
if (symbol && !portfolio.includes(symbol)) {
setPortfolio(prev => [...prev, symbol]);
setNewSymbol('');
}
};
const removeSymbol = (symbolToRemove) => {
setPortfolio(prev => prev.filter(s => s !== symbolToRemove));
};
return (
<div className="portfolio-tracker">
<h2>Portfolio News Tracker</h2>
<div className="portfolio-management">
<div className="portfolio-symbols">
<h3>Portfolio ({portfolio.length} symbols)</h3>
<div className="symbols-list">
{portfolio.map(symbol => (
<div key={symbol} className="symbol-item">
<span>{symbol}</span>
<button
onClick={() => removeSymbol(symbol)}
className="remove-btn"
>
×
</button>
</div>
))}
</div>
</div>
<form onSubmit={addSymbol} className="add-symbol-form">
<input
type="text"
placeholder="Add symbol (e.g., TSLA)"
value={newSymbol}
onChange={(e) => setNewSymbol(e.target.value)}
pattern="[A-Z]{1,5}"
title="Enter 1-5 uppercase letters"
/>
<button type="submit">Add Symbol</button>
</form>
</div>
<div className="news-section">
<div className="news-header">
<h3>Portfolio News ({news.length} articles)</h3>
<button onClick={fetchPortfolioNews} disabled={loading}>
{loading ? 'Loading...' : 'Refresh'}
</button>
</div>
{loading && <div className="loading">Loading portfolio news...</div>}
<div className="news-list">
{news.map(article => (
<div key={article._id} className="portfolio-news-item">
<div className="news-symbols">
{article.symbols?.join(', ') || 'General'}
</div>
<h4>
<a href={article.url} target="_blank" rel="noopener noreferrer">
{article.title}
</a>
</h4>
<div className="news-details">
<span className="importance">
Importance: {article.importanceScore}/10
</span>
<span className="category">{article.category}</span>
<span className="date">
{new Date(article.date).toLocaleDateString()}
</span>
</div>
</div>
))}
</div>
</div>
</div>
);
}
export default PortfolioTracker;
// app/api/news/route.js
import { NextResponse } from 'next/server';
export async function GET(request) {
const { searchParams } = new URL(request.url);
const limit = searchParams.get('limit') || '20';
const minImportance = searchParams.get('minImportance') || '6';
const symbol = searchParams.get('symbol');
const q = searchParams.get('q');
try {
const params = new URLSearchParams({ limit, minImportance });
if (symbol) params.append('symbol', symbol);
if (q) params.append('q', q);
const response = await fetch(`https://api.byul.ai/api/v2/news?${params}`, {
headers: {
'X-API-Key': process.env.BYUL_API_KEY
},
// Next.js caching
next: { revalidate: 300 } // 5 minutes
});
if (!response.ok) {
return NextResponse.json(
{ error: 'Failed to fetch news' },
{ status: response.status }
);
}
const data = await response.json();
return NextResponse.json(data);
} catch (error) {
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}
// app/news/page.js
async function getNews() {
const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/news`, {
next: { revalidate: 300 } // 5 minute cache
});
if (!response.ok) {
throw new Error('Failed to fetch news');
}
return response.json();
}
export default async function NewsPage() {
const newsData = await getNews();
return (
<div>
<h1>Latest Financial News</h1>
{newsData.items.map(article => (
<div key={article._id} className="news-item">
<h2>{article.title}</h2>
<p>Importance: {article.importanceScore}/10</p>
<a href={article.url} target="_blank" rel="noopener noreferrer">
Read more
</a>
</div>
))}
</div>
);
}
# Install dependencies
npm install
# Create .env.local file
echo "REACT_APP_API_URL=http://localhost:3001" > .env.local
# Start development server
npm start
# Create .env.local file
echo "BYUL_API_KEY=byul_api_key" > .env.local
echo "NEXT_PUBLIC_API_URL=http://localhost:3000" >> .env.local
# Start development server
npm run dev
# Test backend proxy
curl http://localhost:3001/api/news
# Test React app
npm test
# Check for security issues
npm audit
# Backend server
BYUL_API_KEY=byul_api_key
# Frontend (if needed for API URL)
REACT_APP_API_URL=https://your-backend-api.com
.news-container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.news-item {
border: 1px solid #ddd;
border-radius: 8px;
padding: 16px;
margin-bottom: 16px;
background: white;
}
.news-item h3 {
margin: 0 0 8px 0;
}
.news-item h3 a {
color: #2563eb;
text-decoration: none;
}
.news-item h3 a:hover {
text-decoration: underline;
}
.importance-badge {
background: #f3f4f6;
padding: 4px 8px;
border-radius: 4px;
font-size: 0.875rem;
font-weight: 500;
}
.symbol-tag {
background: #3b82f6;
color: white;
padding: 2px 6px;
border-radius: 4px;
font-size: 0.75rem;
margin-right: 4px;
}
.status.connected {
color: #059669;
}
.status.disconnected {
color: #dc2626;
}
.sentiment.positive {
color: #059669;
}
.sentiment.negative {
color: #dc2626;
}
.sentiment.neutral {
color: #6b7280;
}