DEV Community

Cover image for Building a Real-Time Football Livescore App with React and Node.js
Qayyum Oladimeji
Qayyum Oladimeji

Posted on

Building a Real-Time Football Livescore App with React and Node.js

Building a Real-Time Football Livescore App with React and Node.js

As a self-taught developer from Nigeria, I wanted to build something that combined my passion for football with my growing skills in web development. The result? A real-time football livescore application that updates match scores, statistics, and events as they happen.

In this article, I'll walk you through the technical decisions, challenges, and solutions I encountered while building this project. Whether you're a beginner or intermediate developer, you'll find practical insights you can apply to your own real-time applications.

The Problem I Wanted to Solve

Football fans want instant updates. Refreshing a page every minute to check scores isn't good enough anymore. I needed to build an app that:

  • Displays live match scores as they happen
  • Shows multiple matches simultaneously
  • Updates without requiring page refreshes
  • Works smoothly on mobile devices (because most Nigerian football fans watch on mobile)
  • Handles poor internet connectivity gracefully

Tech Stack Decisions

Frontend:

  • React (for building interactive UI components)
  • CSS3 (for responsive design and animations)
  • Axios (for API requests)

Backend:

  • Node.js with Express.js (for the server)
  • REST API integration (football data provider)
  • WebSocket/Server-Sent Events for real-time updates

Why React? Component-based architecture makes it easy to create reusable match cards, scoreboards, and stat displays. The virtual DOM ensures smooth updates when scores change.

Next.js component

Why Node.js? JavaScript on both frontend and backend means I could share code and move faster. Plus, Node's event-driven architecture is perfect for real-time applications.

Node.js caching implementation

Architecture Overview

Here's how the system works:

  1. Frontend (React) requests initial match data from my backend
  2. Backend (Node.js) fetches data from a third-party football API
  3. Real-time updates push score changes to connected clients
  4. State management in React keeps the UI in sync with live data

Key Features I Built

1. Live Score Updates

The core feature. Matches update in real-time without refreshing:

// Simplified real-time update logic
useEffect(() => {
  const eventSource = new EventSource('/api/live-updates');

  eventSource.onmessage = (event) => {
    const updatedMatch = JSON.parse(event.data);
    setMatches(prevMatches => 
      prevMatches.map(match => 
        match.id === updatedMatch.id ? updatedMatch : match
      )
    );
  };

  return () => eventSource.close();
}, []);
Enter fullscreen mode Exit fullscreen mode

This approach uses Server-Sent Events (SSE) instead of WebSockets because:

  • SSE is simpler to implement for one-way data flow (server → client)
  • Automatically reconnects if connection drops
  • Works over standard HTTP (no special server configuration needed)

2. Match Filtering and Search

Users can filter by:

  • League (Premier League, La Liga, Serie A, etc.)
  • Match status (Live, Upcoming, Finished)
  • Favorite teams

I used React's useMemo hook to optimize filtering performance:

const filteredMatches = useMemo(() => {
  return matches.filter(match => {
    const leagueMatch = selectedLeague === 'all' || match.league === selectedLeague;
    const statusMatch = selectedStatus === 'all' || match.status === selectedStatus;
    const searchMatch = match.homeTeam.toLowerCase().includes(searchTerm.toLowerCase()) ||
                       match.awayTeam.toLowerCase().includes(searchTerm.toLowerCase());

    return leagueMatch && statusMatch && searchMatch;
  });
}, [matches, selectedLeague, selectedStatus, searchTerm]);
Enter fullscreen mode Exit fullscreen mode

3. Responsive Match Cards

Each match displays:

  • Team names and logos
  • Current score
  • Match time/status
  • Key events (goals, cards, substitutions)

The card design adapts seamlessly from mobile to desktop using CSS Grid and Flexbox:

.match-card {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  gap: 1rem;
  padding: 1.5rem;
  background: #ffffff;
  border-radius: 12px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
  transition: transform 0.2s ease;
}

.match-card:hover {
  transform: translateY(-4px);
  box-shadow: 0 4px 16px rgba(0,0,0,0.15);
}

@media (max-width: 768px) {
  .match-card {
    padding: 1rem;
    gap: 0.5rem;
  }
}
Enter fullscreen mode Exit fullscreen mode

4. Match Statistics Modal

Clicking on any match opens a detailed view with:

  • Possession percentage
  • Shots on target
  • Corner kicks
  • Fouls
  • Player lineups

I used React portals to render the modal outside the main DOM hierarchy, preventing z-index issues.

Technical Challenges and Solutions

Challenge 1: API Rate Limiting

Most free football APIs have strict rate limits (e.g., 100 requests/day). Making a request every second for live updates would exhaust the limit in minutes.

Solution: Implemented a caching layer in my backend:

// Cache structure
const matchCache = {
  data: null,
  timestamp: null,
  TTL: 30000 // 30 seconds
};

app.get('/api/matches', async (req, res) => {
  const now = Date.now();

  // Return cached data if still fresh
  if (matchCache.data && (now - matchCache.timestamp) < matchCache.TTL) {
    return res.json(matchCache.data);
  }

  // Fetch fresh data
  const matches = await fetchFromFootballAPI();
  matchCache.data = matches;
  matchCache.timestamp = now;

  res.json(matches);
});
Enter fullscreen mode Exit fullscreen mode

This reduced API calls by 95% while still providing near-real-time updates.

Challenge 2: Handling Network Failures

In Nigeria, internet connectivity can be unstable. The app needed to handle disconnections gracefully.

Solution: Implemented automatic retry logic with exponential backoff:

const fetchWithRetry = async (url, retries = 3, delay = 1000) => {
  try {
    const response = await axios.get(url);
    return response.data;
  } catch (error) {
    if (retries === 0) throw error;

    await new Promise(resolve => setTimeout(resolve, delay));
    return fetchWithRetry(url, retries - 1, delay * 2);
  }
};
Enter fullscreen mode Exit fullscreen mode

Users see a friendly "Reconnecting..." message instead of a broken app.

Challenge 3: Performance with Many Matches

During busy football weekends, 20+ matches might be live simultaneously. Re-rendering all components on every update caused lag.

Solution: Two optimizations:

  1. React.memo to prevent unnecessary re-renders:
const MatchCard = React.memo(({ match }) => {
  // Component logic
}, (prevProps, nextProps) => {
  return prevProps.match.score === nextProps.match.score &&
         prevProps.match.status === nextProps.match.status;
});
Enter fullscreen mode Exit fullscreen mode
  1. Virtualization for long lists using a simple windowing technique (only rendering visible matches).

Challenge 4: Mobile Data Consumption

Real-time updates can consume significant mobile data, which is expensive in Nigeria.

Solution: Added a "Data Saver" mode:

  • Reduces update frequency from 30s to 2 minutes
  • Compresses images
  • Disables auto-playing videos
  • Shows a data usage indicator

Backend Structure

My Express.js backend is organized like this:

server/
├── routes/
│   ├── matches.js
│   └── leagues.js
├── services/
│   ├── footballAPI.js
│   └── cache.js
├── middleware/
│   ├── errorHandler.js
│   └── rateLimiter.js
└── server.js
Enter fullscreen mode Exit fullscreen mode

The footballAPI.js service abstracts all third-party API calls, making it easy to switch providers if needed.

Lessons Learned

1. Start Simple, Iterate Later

I initially tried to implement WebSockets, Redis caching, and a complex state management system. I was overwhelmed. I simplified to SSE and in-memory caching, got it working, then improved incrementally.

2. Real-Time Doesn't Always Mean Instant

A 30-second delay for score updates is perfectly acceptable. Users care more about reliability than seeing a goal exactly when it happens.

3. Mobile-First Is Non-Negotiable

Over 80% of users accessed the app on mobile. Designing for mobile first, then scaling up to desktop, was the right call.

4. Error States Matter

Showing "Loading...", "No matches today", or "Connection lost" messages dramatically improved user experience. Don't ignore error states.

Performance Metrics

After optimization:

  • Initial load time: 1.8s (on 3G)
  • Update latency: 30-45s from real event
  • Bundle size: 180KB gzipped
  • Lighthouse score: 92/100

What I'd Do Differently

  1. Use TypeScript from the start - Would have caught many bugs during development
  2. Implement proper testing - I wrote tests after building everything, which was harder
  3. Add PWA features earlier - Offline support and install prompts would increase engagement
  4. Better analytics - Understanding what features users actually use would guide development

Future Improvements

  • Push notifications for favorite teams' goals
  • Live commentary integration
  • Social features (reactions, comments)
  • Match predictions using simple ML models
  • Video highlights integration (if I can find an affordable API)

Try It Yourself

Building a real-time app is more accessible than you think. Start with:

  1. A simple Node.js server with Express
  2. One React component that fetches data
  3. Add Server-Sent Events for updates
  4. Deploy on free platforms (Vercel for frontend, Render for backend)

You don't need expensive infrastructure or advanced knowledge. You just need to start and iterate.

Conclusion

This project taught me more than any tutorial could. I dealt with real-world constraints: API limits, poor connectivity, performance issues, and mobile optimization.

As a self-taught developer without a computer science degree, building projects like this is how I prove my skills to potential employers. It's not just about the code—it's about solving real problems that real users face.

If you're learning web development, build something you care about. For me, it was football. For you, it might be something else. The technical skills you gain along the way will transfer to any project.


About the Author: I'm Qayyum Oladimeji, a Full-Stack Developer from Nigeria specializing in React, Next.js, and Node.js. I'm passionate about building applications that work well even in challenging network conditions. Connect with me on LinkedIn or check out my portfolio.

GitHub Repository: Frontend Backend

If you found this helpful, leave a reaction and follow me for more articles about building real-world applications with React, Next.js and Node.js.

Top comments (0)