WebSockets with RedwoodJS [experiment]

Experiments

The Redwood Core team would like to start doing more experiments with things that could go into the framework if there is enough interest and excitement in the community.

Experiments are quick and dirty. Super rough around the edges. Just to play around with what’s possible. Please try them out in a throwaway project or on a throwaway branch in your real project.

WebSocket experiment

The first experiment is WebSockets support for realtime capabilities.

Please let us know if this is something you’d use. And when you’ve tried it, tell us what you loved about the experience and what you hated :smile:

How to try

Quick steps to try the WebSockets experiment

yarn create redwood-app --ts --git rw-ws-experiment
cd rw-ws-experiment
yarn dlx rw-setup-ws
yarn rw g page WebSocket

Replace all content of the newly created page with this:

import { useState } from 'react'

import { useWsContext } from 'src/components/WsContext/WsContext'

const WebSocketPage = () => {
  const [name, setName] = useState('')
  const [message, setMessage] = useState('')
  const ws = useWsContext()

  return (
    <>
      <label>
        Name:{' '}
        <input
          value={name}
          onChange={(event) => {
            setName(event.target.value)
          }}
        />
      </label>
      <br />
      <br />
      <label>
        Message:{' '}
        <input
          value={message}
          onChange={(event) => {
            const message = event.target.value

            // Set the message in component state to make the input
            // value update immediately
            setMessage(message)

            // Send to the server to update all clients (including
            // this one)
            ws.sendMessage(name, message)
          }}
        />
      </label>
      <hr />
      <ul>
        {Object.entries(ws.clients).map(([clientId, clientMessage]) => (
          <li key={clientId}>
            {clientId}: {clientMessage}
          </li>
        ))}
      </ul>
    </>
  )
}

export default WebSocketPage

Run yarn rw dev and go to http://localhost:8910/web-socket in two different browser windows and see the text everyone is typing update in realtime.

Note

This is mostly for localhost experiments for now. It’s not exactly straightforward to deploy this unfortunately. For a production app I recommend Pusher Channels | Build Realtime Real Fast or Realtime | Supabase Docs.

Read more

I got into a little more detail on my blog. (The blog post was written before I wrote the rw-setup-ws command, so it also includes all the steps the setup command now does for you.)

3 Likes

Thank you for the article, very useful.
Is that p[possible to authenticate webSocket request?

1 Like

@gydrocasper I never tried getting auth working with websockets, but please take a look at this StackOverflow post and see if it’s helpful javascript - HTTP headers in Websockets client API - Stack Overflow

1 Like

I am doing some basic research that might result in a wide-spanning application connecting the IoT cloud and a “classic” distributed app living in a different cloud:

In this schema, the RW app would maintain real-time communications with the “Notehub” cloud maintained by Blues Wireless. Building such a system (targeting Healthcare space initially) takes a lot of my time, so I a not sure how responsive can I be to your needs, @Tobbe.

Let me know, please

Would you roll your own websockets implementation for the real-time communication?

Why would I? I reacted to your “experiment” post so happily as I understand to have something to use in my prototype (the yellow rectangle) below:

image

If I participate as your “user & tester” providing a different context, I would avoid “rolling my own” simply because it would save me time and I have more faith in your networking expertise than my own :grinning:

I’m sorry, I should have been more clear. When I said “roll your own”, I meant “do what I did in my experiment”, because I wrote my own WS code, and, perhaps more importantly, ended up setting up my own hosting solution to get it all working when deployed. I never wrote about that last part in my blog post, because I was planning on doing a follow-up post on that. Haven’t had time for that yet though. But the unfortunate truth is that it’s really really difficult to host a WS server that can handle a big load and that is resilient to failures.

So my suggestion is you look into something like Pusher Channels | Build Realtime Real Fast, i.e. not rolling it on your own :slight_smile:

Sure - that is a part of my fear of networking - I am happier to roam around the browser and node

So my suggestion is you look into something like Pusher Channels | Build Realtime Real Fast, i.e. not rolling it on your own.

Good suggestion that I am happy to take to heart. As I am traveling to Croatia (read the Adriatic Sea) to stay a month, I expect to be silent on my communication channels

I’m about to take down the live running example. But before I do, I wanted to dump some hints about deployment.

I deployed the example using Baremetal

I needed to add this to my nginx config

  location ~ /.redwood/functions/ws {
    rewrite ^/.redwood/functions(.*) $1 break;
    proxy_http_version 1.1;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    proxy_pass http://redwood_server;
    proxy_read_timeout 1d;
    proxy_send_timeout 1d;
  }

I also forced pm2 to only run one instance of the api server so that all connections always hit the same server. The default is to run two instances of the server to get some load balancing. But that was a problem for websockets as the connection only lives on one of the servers.

I think the way I fixed my pm2 config was setting instances: 1 and exec_mode: 'fork' in ecosystem.config.js

module.exports = {
  apps: [
    {
      name: 'api',
      cwd: 'current',
      script: 'node_modules/.bin/rw',
      args: 'serve api',
      // instances: 'max',
      instances: 1,
      // exec_mode: 'cluster',
      exec_mode: 'fork',
      wait_ready: true,
      listen_timeout: 10000,
    },
  ],
}

And that right there is another reason you shouldn’t be setting this up yourself like this. For a proper production system you need to be able to scale your servers and not just force it to be a single instance like that.

1 Like