Node.jsFetch APIWeb StreamsPerformanceStandards

Node.js Native Fetch & Web Streams: Goodbye Axios

B
Backend Lead
Featured Guide 18 min read

Universal I/O.

For a decade, Node.js text handling and HTTP requests were proprietary (http.request, fs.createReadStream).

Now, Node.js implements the Web Standards. Code you write for the browser runs on the server.

02. ReadableStream vs Class Stream

Node.js streams (.pipe()) are legendary but complex. Web Streams (.pipeTo(), .pipeThrough()) are the modern standard, used by fetch and supported by Edge runtimes (Cloudflare, Deno).

Deep Dive: Duplex Streams

Since Node.js 20, Duplex.from() and Readable.fromWeb() allow you to convert between Node.js Streams and Web Streams instantly.

This means you can pipe a native Node fs.createReadStream into a Web Standard fetch body effortlessly.

// fetch-and-stream.js (Zero Dependencies)
const
response = await fetch('https://huge.file.zip');

await response.body
  .pipeThrough(new DecompressionStream('gzip'))
  .pipeTo(Writable.toWeb(fs.createWriteStream('./out.txt')));

04. The Senior Engineer's Take

Interoperability is king.

By adopting Web Streams, you make your code portable. The same utility function that processes CSVs on your Node.js backend can now handle file uploads in the user's browser, sharing the exact same logic.

Interactive Playground

import React, { useState, useEffect } from 'react';

// 🌊 Stream Visualizer

export default function StreamsDemo() {
    const [isStreaming, setIsStreaming] = useState(false);
    const [chunks, setChunks] = useState([]);
    
    const startStream = () => {
        setIsStreaming(true);
        setChunks([]);
        
        let count = 0;
        const interval = setInterval(() => {
            if (count > 5) {
                clearInterval(interval);
                setIsStreaming(false);
                return;
            }
            setChunks(prev => [...prev, { id: count, size: Math.floor(Math.random() * 50) + 10 + 'kb' }]);
            count++;
        }, 800);
    };

    return (
        <div className="bg-slate-50 dark:bg-slate-950 p-8 rounded-3xl border border-slate-200 dark:border-slate-800 shadow-xl">
             <div className="flex justify-between items-center mb-10">
                <h3 className="text-2xl font-black text-gray-900 dark:text-white flex items-center gap-3">
                    <span className="text-cyan-500">🌊</span> Web Streams
                </h3>
                <button 
                    onClick={startStream}
                    disabled={isStreaming}
                    className="bg-cyan-500 hover:bg-cyan-600 disabled:opacity-50 text-white font-bold px-6 py-2 rounded-xl transition-all"
                >
                    {isStreaming ? 'Streaming...' : 'Start Pipeline'}
                </button>
            </div>

            <div className="flex flex-col md:flex-row items-center gap-4 relative min-h-[200px] justify-between px-8 bg-slate-100 dark:bg-black/20 rounded-2xl border border-dashed border-slate-300 dark:border-slate-800">
                
                {/* Source */}
                <div className="flex flex-col items-center gap-2 z-10">
                    <div className="w-16 h-16 bg-white dark:bg-slate-800 rounded-2xl flex items-center justify-center shadow-lg border border-slate-200 dark:border-slate-700">
                        <span className="text-cyan-600 text-2xl">🗄️</span>
                    </div>
                    <div className="font-bold text-xs">Response.body</div>
                </div>

                {/* The Pipe */}
                <div className="flex-1 h-3 bg-slate-300 dark:bg-slate-700 rounded-full relative overflow-hidden">
                    <div className="absolute inset-0 flex items-center px-2">
                        {chunks.map((chunk) => (
                             <div 
                                key={chunk.id}
                                className="absolute bg-cyan-400 w-8 h-2 rounded-full animate-stream-move shadow-[0_0_10px_rgba(34,211,238,0.8)]"
                                style={{ animationDuration: '3s', animationDelay: `${chunk.id * 800}ms` }}
                             />
                        ))}
                    </div>
                </div>

                {/* Transform */}
                 <div className="flex flex-col items-center gap-2 z-10">
                    <div className="w-16 h-16 bg-white dark:bg-slate-800 rounded-2xl flex items-center justify-center shadow-lg border border-slate-200 dark:border-slate-700">
                         <span className="text-purple-500 text-2xl">🔗</span>
                    </div>
                    <div className="font-bold text-xs text-center">Transform<br/>Stream</div>
                </div>
                
                 {/* The Pipe 2 */}
                <div className="flex-1 h-3 bg-slate-300 dark:bg-slate-700 rounded-full relative overflow-hidden">
                    <div className="absolute inset-0 flex items-center px-2">
                        {chunks.length > 2 && chunks.map((chunk) => (
                             <div 
                                key={chunk.id + '_2'}
                                className="absolute bg-purple-400 w-6 h-2 rounded-full animate-stream-move shadow-[0_0_10px_rgba(168,85,247,0.8)]"
                                style={{ animationDuration: '3s', animationDelay: `${(chunk.id * 800) + 1500}ms` }}
                             />
                        ))}
                    </div>
                </div>

                {/* Sink */}
                 <div className="flex flex-col items-center gap-2 z-10">
                    <div className="w-16 h-16 bg-white dark:bg-slate-800 rounded-2xl flex items-center justify-center shadow-lg border border-slate-200 dark:border-slate-700">
                        <span className="text-green-600 text-2xl">💾</span>
                    </div>
                    <div className="font-bold text-xs">Writable</div>
                </div>

            </div>
            
             <div className="mt-8 bg-black p-4 rounded-xl font-mono text-xs text-green-400 min-h-[100px]">
                {chunks.length === 0 ? <span className="text-gray-500 opacity-50">Waiting for stream...</span> : (
                    chunks.map(c => (
                        <div key={c.id}>
                            [Stream] Received Chunk #{c.id} ({c.size}) -> Piping...
                        </div>
                    ))
                )}
                {chunks.length >= 6 && <div className="text-blue-400 font-bold mt-2">✨ Pipeline Closed. File saved successfully.</div>}
             </div>
        </div>
    );
}