DEV Community

Cover image for Send realtime data streams without using Socket.io
Hritique Rungta
Hritique Rungta

Posted on

Send realtime data streams without using Socket.io

Hi folks,
Ever wanted a progress bar that fetches the progress realtime from the server but never really done it coz its too much work?

In this article, I will show you how to implement SSE(Server-Sent Event) on both Back-end and Front-end which you can use to do so many cool things (and yes that progress bar tooπŸ˜€)

πŸ“œ A little context

So, A while back I was working on a MERN project and I needed to track a backend API progress on the client in realtime.

I found people using Socket.io for realtime data streams but I did not want to use a whole library (~43.3kB) just for this simple use case. I wanted something native which requires no extra setup + easy to use.

That's when I found EventSource API which is a native API to track event stream from the back-end.
It is very easy to use and requires no extra library.

πŸ’» How to implement it?

Sending the events from the server πŸ“‘

I am using Node.js & Typescript for the runtime & Express for handling server requests but feel free to use whatever backend env you are comfortable with.

  • Create an index.ts file and import the required modules
import express, { Response } from 'express';
import axios from 'axios';
  • Start the server and listening to any port of your choice. I am using 4000 in this example.
app.listen('4000', () => {
  console.log('Server listening to port 4000');
});
  • Now create a sendData function for sending the data to the client. I am using a fake API generator "JSONPlaceholder" for fetching random data. I also added a delay using a sleep function to make it a bit more realistic.
const sendData = async (id: number, res: Response) => {
  const { data } = await axios.get(
    'https://jsonplaceholder.typicode.com/todos/' + id
  );
  const dataString = JSON.stringify(data);

  await sleep(1000);

  res.write('event: message\n');

  res.write('data: ' + dataString);
  res.write('\n\n');
};

const sleep = async (ms: number) => {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
};

Now, there are few very important things to notice here. 🚨

  1. res.write('event: message\n') - This is used to specify the type of event for the client to listen to. The 'message' can be replaced by anything you like.
  2. res.write('data: ' + dataString) - This is where we specify the data we want to send under the 'message' event.
  3. The newline characters \n are very important for the client to properly parse the data and events
  • At last, just declare a route for sending the events to this route when requested and add the following headers to the response before writing the data event. Don't forget to end the response cycle using res.end()
app.get('/events', async (_, res) => {
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive',
  });
  for (let i = 1; i < 10; i++) {
    await sendData(i, res);
  }
  res.end();
});

And that's it.
That is all you need to send a real-time stream of data to the client using Express and Node.js

Receiving the events on the client side πŸ“₯

I will be using React for this, but feel free to use any JS framework or just vanilla JS, whatever you prefer.

  • Setup a simple react project using create-react-app and remove all the code from App.tsx inside the App function.
  • Create a data state and initialize it with an empty array.
const [data,setData] = useState<any>([]);
  • Implement a useEffect hook to write our event handling logic
useEffect(() => {
  // An instance of EventSource by passing the events URL
  const eventSource = new EventSource('http://localhost:4000/events');

  // A function to parse and update the data state
  const updateData = (messageEvent: MessageEvent) => {
    const parsedData = JSON.parse(messageEvent.data);
    setData((data: any) => [...data, parsedData]);
    if (parsedData.id === 9) {
      eventSource.close();
    }
  };

  // eventSource now listening to all the events named 'message'
  eventSource.addEventListener('message', updateData);

  // Unsubscribing to the event stream when the component is unmounted
  return () => eventSource.close();
}, []);
  • Now, simply return a list using the data array and see the list get updated as we receive data
return (
  <div>
    <ul>
      {data.map((_: any) => (
        <li>{_.title}</li>
      ))}
    </ul>
  </div>
);

If you did everything right, then you can see an output something like this on the browser when you open http://localhost:3000

SSE

πŸ˜€ Awesome!! You just implemented a simple server to send SSEs and a client to listen to those events.

Thank you so much for sticking to the end. Please drop a ❀ if you like this and follow me for more such articles.

Top comments (0)