If you ever had to build a realtime web app and you've built yourself a REST backend (or you need to use same legacy REST backend), you most likely stumbled upon a pretty common issue: how do I stream a bunch of data from the backend to make it seem it's updated in realtime?

There's a bunch of ways to do this:

  1. You can use long polling; but this may add some networking overhead (creating and tearing down connections every time you need data is not very efficient)
  2. You can switch to WebSockets; but this means you need yo reimplement your API
  3. You can use the Streams API; you only need to update your API implementation to support it

Do note that the streams API is an experimental technology and currently a LS (living standard), but browser support is pretty good.

For the purpose of illustrating how the server and client implementations will look like, we'll create a simple counter app that get's a new value every second from the backend.


We'll use echo to expose our counter endpoint:

package main

import (


type Counter struct {
  Count int `json:"count"`

func main() {
  e := echo.New()
  e.GET("/counter", getCounter)

func getCounter(c echo.Context) error {
  ctx := c.Request().Context()

  c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSON)

  enc := json.NewEncoder(c.Response())

  var i = 0
  for {
    select {
    case <-ctx.Done():
      return c.NoContent(http.StatusOK)
      counter := Counter{i}
      if err := enc.Encode(counter); err != nil {
        return err

To start it just run:

go run ./main.go

And if you run:

curl http://localhost:9000/counter

You should get a bunch of JSON messages:



We'll use create-react-app to setup a simple app:

create-react-app streams-api

Now let's make a simple react hook that will just fetch and stream data from a URL (so we can reuse it with any endpoint):

import {
} from 'react';

 * Stream react hook
 * @param {object} params
 * @param {string} params.url
export function useStream(params) {
  const [data, setData] = useState(null);
  const streamRef = useRef();
  const url = useRef();

  const stop = useCallback(() => {
    if (streamRef.current) {
  }, []);

  useEffect(() => {
    if (url.current !== params.url) {
      if (streamRef.current) {
      streamRef.current = new AbortController();
      startStream(params.url, data => setData(data), streamRef.current.signal);

    return () => { };
  }, [params.url]);

  return {data, stop};

 * Use this function to start streaming data from an URL
 * @param {string} url
 * @param {Function} cb
 * @param {AbortSignal} sig
export async function startStream(url, cb, signal) {
  const res = await fetch(url, {
    method: 'GET'

  const reader = res.body.getReader();

  while (true) {
    const {done, value} = await reader.read();
    const res = new Response(value);
    try {
      const data = await res.json();
      if (typeof cb === 'function') {
    } catch (e) {

    if (done) {

NOTE: You can find this hook on NPM too (checkout rolandjitsu/react-fetch-streams).

Now let's use this hook with our counter endpoint:

import {useMemo} from 'react';
import {useStream} from './stream';

function App(props) {
  const {data} = useStream({url: 'http://localhost:9000/counter'});
  const count = useMemo(() => data !== null ? data.count : 0, [data]);

  return (

NOTE: Make sure the server is running.

And now run the app:

yarn start

It won't look pretty, but you should be seeing a counter going up incrementally in the UI, similar to the curl call.

And that's about it. I hope this simple example made it a little more clear how to use the streams API. Thanks for reading.

For more code examples checkout rolandjitsu/streams-api.