Four Key Reasons to Learn Markdown
Back-End Leveling UpWriting documentation is fun—really, really fun. I know some engineers may disagree with me, but as a technical writer, creating quality documentation that will...
In parts one and two of this series, we set up a frontend and backend to view notifications from third-party services like GitHub, Netlify, and Heroku. It works like this:
Now our client is set up to view our messages, but we need to quit and restart the app to get any updates. We could add pull-to-refresh functionality, but it’d be much nicer if we could automatically receive updates from the server when a new message is received. Let’s build out WebSockets functionality to accomplish these live updates. Here’s an illustration of how the flow of data will work:
If you like, you can download the completed server project and the completed client project for part 3.
There are a few different libraries that can provide WebSocket functionality to Node apps. For the sake of this tutorial, we’ll use websocket
:
$ yarn add websocket
In our worker, after we handle a message on the incoming
queue and save the message to the database, we’ll send a message out on another queue indicating that we should deliver that message over the WebSocket. We’ll call that new queue socket
. Make the following change in workers/index.js
:
const handleIncoming = message => repo .create(message) .then(record => { console.log('Saved ' + JSON.stringify(record)); + return queue.send('socket', record); }); queue .receive('incoming', handleIncoming)
Note the following sequence:
incoming
queue;socket
queue.Note that we haven’t yet implemented the WebSocket code to send the response to the client yet; we’ll do that next. So far, we’ve just sent a message to a new queue that the WebSocket code will watch.
Now let’s implement the WebSocket code. In the web
folder, create a file socket.js
and add the following:
const WebSocketServer = require('websocket').server; const configureWebSockets = httpServer => { const wsServer = new WebSocketServer({ httpServer }); }; module.exports = configureWebSockets;
We create a function configureWebSockets
that allows us to pass in a Node httpServer
and creates a WebSocketServer
from it.
Next, let’s add some boilerplate code to allow a client to establish a WebSocket connection:
const configureWebSockets = httpServer => { const wsServer = new WebSocketServer({ httpServer }); + + let connection; + + wsServer.on('request', function(request) { + connection = request.accept(null, request.origin); + console.log('accepted connection'); + + connection.on('close', function() { + console.log('closing connection'); + connection = null; + }); + }); };
All we do is save the connection
in a variable and add a little logging to indicate when we’ve connected and disconnected. Note that our server is only allowing one connection; if a new one comes in, it’ll be overwritten. In a production application you would want to structure your code to handle multiple connections. Some WebSocket libraries will handle multiple connections for you.
Next, we want to listen on the socket
queue we set up before, and send an outgoing message on our WebSocket connection when we get one:
const WebSocketServer = require('websocket').server; +const queue = require('../lib/queue'); const configureWebSockets = httpServer => { ... wsServer.on('request', function(request) { ... }); + + queue + .receive('socket', message => { + if (!connection) { + console.log('no WebSocket connection'); + return; + } + connection.sendUTF(JSON.stringify(message)); + }) + .catch(console.error); }
When a message is sent on the socket
queue and if there is no WebSocket client connection, we do nothing. If there is a WebSocket client connection we send the message we receive out over it.
Now, we just need to call our configureWebSockets
function, passing our HTTP server to it. Open web/index.js
and add the following:
const listRouter = require('./list'); +const configureWebSockets = require('./socket'); const app = express(); ... const server = http.createServer(app); +configureWebSockets(server);
By calling our function, which in turn calls new WebSocketServer()
, we enable our server to accept requests for WebSocket connections.
Now we need to update our Expo client to make that WebSocket connection to the backend and accept messages it sends, updating the screen in the process. On the frontend we don’t need to add a dependency to handle WebSockets; the WebSocket
API is built-in to React Native’s JavaScript runtime.
Open src/MessageList.js
and add the following:
const httpUrl = Platform.select({ ios: 'http://localhost:3000', android: 'http://10.0.2.2:3000', }); +const wsUrl = Platform.select({ + ios: 'ws://localhost:3000', + android: 'ws://10.0.2.2:3000', +}); + +let socket; + +const setUpWebSocket = addMessage => { + if (!socket) { + socket = new WebSocket(wsUrl); + console.log('Attempting Connection...'); + + socket.onopen = () => { + console.log('Successfully Connected'); + }; + + socket.onclose = event => { + console.log('Socket Closed Connection: ', event); + socket = null; + }; + + socket.onerror = error => { + console.log('Socket Error: ', error); + }; + } + + socket.onmessage = event => { + addMessage(JSON.parse(event.data)); + }; +}; const loadInitialData = async setMessages => {
This creates a function setUpWebSocket
that ensures our WebSocket is ready to go. If the WebSocket is not already opened, it opens it and hooks up some logging. Whether or not it was already open, we configure the WebSocket to pass any message it receives along to the passed-in addMessage
function.
Now, let’s call setUpWebSocket
from our component function:
useEffect(() => { loadInitialData(setMessages); }, []); + useEffect(() => { + setUpWebSocket(newMessage => { + setMessages([newMessage, ...messages]); + }); + }, [messages]); + return ( <View style={{ flex: 1 }}>
We call setUpWebSocket
in a useEffect
hook. We pass it a function allowing it to append a new message to the state. This effect depends on the messages
state.
As a result of these dependencies, when the messages
are changed, we create a new addMessage
callback that appends the message to the updated messages
array and then we call setUpWebsocket
again with that updated addMessage
callback. This is why we wrote setUpWebsocket
to work whether or not the WebSocket is already established; it will be called multiple times.
With this, we’re ready to give our WebSockets a try! Make sure you have both Node services running in different terminals:
$ node web
$ node workers
Then reload our Expo app:
In yet another terminal, send in a new message:
$ curl http://localhost:3000/webhook -d "this is for WebSocketyness"
You should see the message appear in the Expo app right away, without any action needed by the user. We’ve got live updates!
Now that we’ve proven out that we can get live updates to our app, we should move beyond our simple webhook and get data from real third-party services. In the next part, we’ll set up a webhook to get notifications from GitHub about pull request events.
Writing documentation is fun—really, really fun. I know some engineers may disagree with me, but as a technical writer, creating quality documentation that will...
Humanity has come a long way in its technological journey. We have reached the cusp of an age in which the concepts we have...
Go 1.18 has finally landed, and with it comes its own flavor of generics. In a previous post, we went over the accepted proposal and dove...