Important Security Note

Never use API keys directly in React. API keys will be exposed to all website visitors in the browser. Always use a backend proxy.

Insecure Pattern (Don’t do this)

// 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

Backend Proxy Setup

First, create a backend API that proxies requests to Byul:

Node.js/Express Backend

// 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');
});

React Examples

Basic News Component

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;

News Hook with Filtering

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>
  );
}

Real-time News Dashboard

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;

Portfolio News Tracker

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;

Next.js Integration

API Route (Next.js 13+ App Router)

// 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 }
    );
  }
}

React Server Component (Next.js 13+)

// 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>
  );
}

Environment Setup

Create React App

# Install dependencies
npm install

# Create .env.local file
echo "REACT_APP_API_URL=http://localhost:3001" > .env.local

# Start development server
npm start

Next.js

# 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

Testing

# Test backend proxy
curl http://localhost:3001/api/news

# Test React app
npm test

# Check for security issues
npm audit

Production Deployment

Environment Variables:
# Backend server
BYUL_API_KEY=byul_api_key

# Frontend (if needed for API URL)
REACT_APP_API_URL=https://your-backend-api.com
Security Checklist:
  • API key only in backend environment
  • CORS properly configured
  • Rate limiting on proxy endpoints
  • Input validation on all parameters
  • Error messages don’t expose internal details

CSS Styling Example

.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;
}