Streaming Data in Chunks: A Comprehensive Guide

Piyush KumarPiyush Kumar
4 min read

Streaming data in chunks is a powerful technique for managing long-running processes and delivering real-time updates to clients. By sending data incrementally while maintaining an open connection, you can keep users informed and engaged without waiting for the entire process to complete. This guide will walk you through how to set up and handle streaming data both on the server and client sides, using popular tools and libraries.

Introduction to Streaming Data

Streaming data involves sending pieces of information to the client as they become available, rather than sending a complete response all at once. This method is especially useful for scenarios like:

  • Real-time progress updates: Inform users about the status of a long-running operation.

  • Large file downloads: Provide data in chunks to reduce memory usage and improve performance.

  • Live feeds: Deliver continuous updates, such as live scores or stock prices.

Server-Side: Setting Up Streaming Responses

To stream data from the server, you need to set up your server to handle and send data incrementally. Here's how you can do it using Express.js, a popular Node.js framework.

  1. Setting Up an Express Server

    First, ensure you have Express installed:

     npm install express
    

    Create a basic Express server that streams data:

     const express = require('express');
     const app = express();
    
     app.get('/progress', (req, res) => {
       res.setHeader('Content-Type', 'text/plain');
    
       let progress = 0;
    
       function sendProgress() {
         if (progress >= 100) {
           res.write('Process complete.\n');
           res.end();
           return;
         }
    
         res.write(`Progress: ${progress}%\n`);
         progress += 10;
    
         setTimeout(sendProgress, 1000); // Simulate work by delaying
       }
    
       sendProgress();
     });
    
     app.listen(3000, () => console.log('Server running on port 3000'));
    

    In this example:

    • res.setHeader('Content-Type', 'text/plain'): Sets the response content type.

    • res.write(data): Sends data chunks to the client.

    • res.end(): Ends the response once the process is complete.

  2. Handling Errors and Connection Issues

    Ensure you handle potential issues such as errors or client disconnections. Add error handling and ensure the connection is properly closed:

     app.get('/progress', (req, res) => {
       res.setHeader('Content-Type', 'text/plain');
    
       let progress = 0;
    
       function sendProgress() {
         if (progress >= 100) {
           res.write('Process complete.\n');
           res.end();
           return;
         }
    
         if (res.headersSent) {
           return; // Stop if the response is already sent
         }
    
         res.write(`Progress: ${progress}%\n`);
         progress += 10;
    
         setTimeout(sendProgress, 1000);
       }
    
       req.on('close', () => {
         console.log('Client disconnected');
         res.end();
       });
    
       sendProgress();
     });
    

Client-Side: Receiving and Displaying Streaming Data

On the client side, you need to handle the streamed data and update the user interface accordingly. Here’s how to achieve this using different methods.

  1. Using fetch API (Browser)

    The fetch API can handle streaming responses and process data chunks as they arrive:

     <!DOCTYPE html>
     <html lang="en">
     <head>
       <meta charset="UTF-8">
       <meta name="viewport" content="width=device-width, initial-scale=1.0">
       <title>Streaming Progress</title>
     </head>
     <body>
       <h1>Process Progress</h1>
       <pre id="progress"></pre>
    
       <script>
         async function fetchProgress() {
           const response = await fetch('/progress');
           const reader = response.body.getReader();
           const decoder = new TextDecoder();
           const progressElement = document.getElementById('progress');
    
           let done = false;
           while (!done) {
             const { value, done: readerDone } = await reader.read();
             done = readerDone;
             if (value) {
               progressElement.textContent += decoder.decode(value);
             }
           }
         }
    
         fetchProgress();
       </script>
     </body>
     </html>
    

    In this example:

    • response.body.getReader(): Reads the stream of data as it arrives.

    • TextDecoder: Decodes the text data from binary chunks.

  2. Using axios (Node.js)

    For server-side streaming with axios, configure it to handle response streams:

     const axios = require('axios');
    
     async function fetchProgress() {
       try {
         const response = await axios({
           url: 'http://localhost:3000/progress',
           method: 'GET',
           responseType: 'stream'
         });
    
         response.data.on('data', chunk => {
           console.log(chunk.toString());
         });
    
         response.data.on('end', () => {
           console.log('Stream ended.');
         });
    
         response.data.on('error', err => {
           console.error('Stream error:', err);
         });
       } catch (error) {
         console.error('Request error:', error);
       }
     }
    
     fetchProgress();
    

    In this Node.js example:

    • responseType: 'stream': Configures axios to handle streaming data.

    • response.data.on('data', chunk => { ... }): Processes each data chunk as it arrives.

Best Practices

  • Manage Memory Usage: Ensure your streaming logic handles memory efficiently, especially for large datasets.

  • Handle Errors Gracefully: Implement robust error handling to deal with network issues or unexpected client disconnections.

  • Test with Real Data: Test streaming with real or simulated data to ensure performance and accuracy.

Conclusion

Streaming data in chunks allows for efficient handling of large or long-running processes, providing real-time updates to users and improving overall performance. By setting up your server to send data incrementally and using client-side techniques to handle and display the data, you can create a seamless and engaging experience. Whether you’re using Express.js with fetch for the browser or axios in a Node.js environment, mastering data streaming is a valuable skill for modern web development.

0
Subscribe to my newsletter

Read articles from Piyush Kumar directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Piyush Kumar
Piyush Kumar