Implement HTTP Streaming with Node.js and Fetch API

Do you know that you can stream HTTP data for efficient visualization? Learn more about how you can stream data with HTTP using Node.js and Fetch API.
guest-post,http-streaming-with-nodejs-and-fetch-api
Table of Contents

Introduction

When your webapp has a large amount of data to visualize, you don't want your users to wait 10 seconds before seeing something.

One technique that is often overlooked is HTTP streaming. It's broadly supported, works well, and doesn't require fancy libraries.

We're going to go through how we can use HTTP streaming in our applications and what to consider when we do so.

Introduction

When building web applications, we typically have a REST API with GET endpoints that do something like this:

  1. Parse the request (URL, query params, etc..)
  2. Query data from a database
  3. Convert the database results into JSON
  4. Send the JSON response back

The API will typically wait for each step to complete before going on to the next one, and by step 4, the database result and the JSON objects are all in memory before the request is handled, and everything can be cleaned up.

This works, and there is nothing wrong with it (KISS, right?) as long as your database query results are small and quickly available.

But let's say you want to render a chart with 10k data points. Querying will not be as smooth anymore. The simple way will work, but ideally, you don't want to accumulate all the data in memory before sending the response.

With HTTP streaming, you can start rendering the chart even before your query is complete.

To make it happen:

  1. Your API should use HTTP streaming to send its response.
  2. Your webapp should use the Fetch API to make the request so that it can process the streaming response.

Create a Streaming API

In this example, we use Koa for the API, but you can use other libraries like Express or plain Node.js. Most will have support for streaming.

Let's create an API:

This code creates an API with 1 endpoint that will respond with the contents of a JSON file with 10k measurements.

The file looks like this:

We're creating an HTTP/1.1 server with Node's module. By setting to a stream, data will be sent to the requester as soon as it is loaded using a mechanism called chunked transfer encoding.

This saves time and memory of your API because it doesn't have to accumulate the whole result in memory before sending the response.

In a real application, you're probably using a database instead of a pre-created JSON file. If you're using MongoDB, you can create a stream with the cursor's method.

Consume a Streaming API From a Webapp

The endpoint we created can be consumed with any HTTP client, but you have to use the Fetch API to take advantage of streaming.

will set to a ReadableStream for streaming responses.

Here's how you can read a streaming response:

If you run the API that we created and then run the code above in a web browser, you'll see several times in the console with varying x:

The console showing "received chunk of size x"

To see it more clearly, open up Developer Tools (F12) and set network throttling to 3G. It will take longer for the file to download, so you can see that the chunks are being processed gradually.

That's all very nice, but the chunks themselves are not very useful. You want to process the measurements that are in these chunks. Ideally, you would have an async iterable that gradually yields the JSON objects as they come in so that you can use to iterate over the measurements instead of the chunks.

JavaScript doesn't have a JSON parser that can deal with streams. Let's assume we have a function that can do this called . More details and a simple implementation can be found in "Streaming Considerations" below.

We would then be able to do this:

This is the result:

The console showing "measurement with id ... has value ..."

This is a very powerful mechanism. Instead of statements, imagine a line chart where the measurements gradually become visible as more data comes in.

This is an example created with Apache ECharts:

A line chart gradually becoming visible as data comes in

Before the Fetch API, it was impossible to do this because the alternative, , will load the whole response into memory before providing it to your code.

Modern applications don't use directly, but a lot of libraries like Axios or Angular's HttpClient rely on it to make requests.

People would rely on more advanced technologies like WebSockets to stream data.

HTTP/2 for Streaming

You might have heard that HTTP/2 has a more efficient mechanism for streaming, and you would be right. So let's see if we can use HTTP/2.

From the side of your webapp, the browser will automatically determine if it can communicate with the server over HTTP/2 and use it if it's available. The Fetch API will do this transparently, so you do not need to make any changes to your webapp.

What you need to do is make your API available over HTTP/2:

This code is mostly the same as for HTTP/1.1 with two notable differences:

  • We're using Node's module instead of .
  • We're using instead of plain because this is mandatory for HTTP/2. You can set up HTTPS and get the and files using mkcert. Or, use one of the other mechanisms described in this article: Use HTTPS for Local Development

That's it!

If you restart the API and check the network tab in Developer Tools, you'll see that your application will now stream over HTTP/2 (don't forget to update the URL in your webapp, start with instead of ).

HTTP Streaming Considerations

In the code above, we assumed that there was a function that would parse a containing JSON into an async iterable of objects.

The difficulty is that reading from a will give you chunks of data that don't necessarily correspond to anything meaningful. To illustrate, let's take a look at this example:

We could receive this JSON in chunks like this:

You need some way to determine where one measurement object starts and ends. To simplify this, we created a JSON file where each line contains precisely one object. Parsing the stream becomes manageable when we can make this assumption.

Here is the implementation of

If you have control over the API you're calling, you can use the "1 object per line" formatting as part of the contract, but know that it could be prone to breaking. For robust JSON support, we need a real streaming parser.

Other options include using a format with one object per line by default, like CSV, or a more advanced format with built-in support for streaming like Apache Arrow.

Advantages

  1. Snappy User Experience: You can start showing data as soon as it's available.
  2. Scalable API: No memory usage spikes from accumulating results in memory.
  3. Uses plain HTTP and a standard JavaScript API. There are no connections to manage or complicated frameworks that might become obsolete in a few years.

Disadvantages

  1. Implementation is slightly more involved than using regular API calls.

  2. Error handling becomes more difficult because HTTP status code 200 will be sent as soon as streaming starts. What do we do when something goes wrong in the middle of the stream?

    When something goes wrong, your API should close the stream. Your webapp can then determine if the stream was complete and show a fitting message to the user if it's not. For example: when using a JSON response, as we discussed, you can check that the last line contains only "]".

  3. No streaming JSON parser is currently available. Needs formatting assumptions as part of the contract or a more unconventional format.

Conclusion

You have learned how to stream HTTP data efficiently using Node.js and HTTP without congesting or burdening the memory. You have also understood the advantages and disadvantages of HTTP streaming.

Nick Van Nieuwenhuyse
By Nick Van NieuwenhuyseNick is a self-employed software engineer with 10+ years of experience, specialized in building full-stack web applications. Recently, he's been active in smart cities and mobility. He loves the challenge of translating technology into software that people can understand.
Featured Posts

How to Implement JWT Authentication for CRUD APIs in Deno

Multi-Factor Authentication (MFA) with Redis Cache and OTP

Introduction to SolidJS

Build a Modern Login/Signup Form with Tailwind CSS and React

Implement HTTP Streaming with Node.js and Fetch API

NestJS: How to Implement Session-Based User Authentication

NestJS User Authentication with LoginRadius API

How to Authenticate Svelte Apps

Flutter Authentication: Implementing User Signup and Login

How to Secure Your LoopBack REST API with JWT Authentication

Node.js User Authentication Guide

Your Ultimate Guide to Next.js Authentication

Local Storage vs. Session Storage vs. Cookies

How to Secure a PHP API Using JWT

Using JWT Flask JWT Authentication- A Quick Guide

Build Your First Smart Contract with Ethereum & Solidity

What are JWT, JWS, JWE, JWK, and JWA?

How to Build an OpenCV Web App with Streamlit

32 React Best Practices That Every Programmer Should Follow

How to Build a Progressive Web App (PWA) with React

Bootstrap 4 vs. Bootstrap 5: What is the Difference?

JWT Authentication — Best Practices and When to Use

What Are Refresh Tokens? When & How to Use Them

How to Upgrade Your Vim Skills

How to Implement Role-Based Authentication with React Apps

How to Authenticate Users: JWT vs. Session

How to Use Azure Key Vault With an Azure Web App in C#

How to Implement Registration and Authentication in Django?

11 Tips for Managing Remote Software Engineering Teams

Implementing User Authentication in a Python Application

Add Authentication to Play Framework With OIDC and LoginRadius

Share On:
Share on TwitterShare on LinkedIn