Socket.io is a javascript library that makes it really easy for developers to create single-page web- and mobile-applications that employ 'bi-directional communication.' Simply put, it allows servers to push data to to all client browsers and devices instead of waiting for a request from the client.
The resulting real-time functionality is everywhere. Social media applications have feeds which are updated every time another user likes one of your photos or adds you as a friend. Messaging apps don't require you to refresh the page to see your new messages.
Socket.io uses the websocket protocol (ws or wss) to transport data between client and server. HTTP is a stateless protocol. After a message is sent the response is received, the connection is closed. The websocket protocol, however, is a stateful protocol. The connection persists until it is closed.
HTTP is unidirectional, clients make requests to servers and servers respond with the requested data. Websocket protocol travels in both directions. The client can request data from the server and the server can push data, unrequested, to the client.
Demo
Here's how to implement a socket.io server and client. I'm going to use express for my web server and react js for my client.
First, I ran the following command to create a react app. I cd'd into socket-io-demo and ran 'npm start' to open up the project in a browser.
npx create-react-app socket-io-demo
Next, I ran the following command to install express, socket.io, and socket.io-client. Socket.io is used to set up your express server as a socket.io server and socket.io-client is used on the client side to connect to the socket.io server.
npm i express socket.io socket.io-server
Server
Let's get started on the server side. First, I created a folder called server and added an index.js file. I'm going to put all server code in index.js to simplify things. Normally, I would put socket.io logic in its own file and run this file from the entry point to the server.
Now, I'm going to create an instance of app by requiring express, invoking it, and capturing the return value in a const called app.
I'll create an HTTP server object by invoking the createServer method of the http module. No need to import http cause it's built into node. Pass app into the createServer method.
Next, I'll create a new socket.io server by requiring socket.io and extracting Server from the return value. Then, I instantiate a socket.io server with the new keyword and pass in my HTTP server object in as an argument. I used the identifier 'io' to keep it consistent with the documentation.
Lastly, instead invoking the listen method on app like you do if you're creating typical express web server, I invoke listen on the socket.io server.
const express = require('express');
const app = express();
const { createServer } = require('http');
const httpServer = createServer(app);
const { Server } = require('socket.io');
const io = new Server(httpServer);
const port = 8080;
httpServer.listen(port, () => {
console.log(`socket.io server listening on port ${port}`);
});
Add an npm script to start the server and run it and you should see the message in the console:
"server": "node server"
Client
On to the client side. When I created my react project, an App.js file was created. This is my root component. I'm going to connect to the socket.io server in this component because if I connect in a sub-component, every time the component re-renders, a connection will be re-created. You could also wrap your app in a socket context component to ensure a connection is made one time.
I'm going to import 'io' from 'socket.io-client' with the following line:
import { io } from 'socket.io-client';
Next, I'll connect to the server by invoking io and passing in the url of the server. Socket.io uses websocket protocol by default. If websocket protocol does not work, it falls back to a backup protocol
const socket = io('http://localhot:8080');
I'm creating a super simple chat application. When you learn how to use a component-based front end library or framework, you inevitably build a todo or grocery list. When you learn how to use a real-time library like socket.io, you build a messaging app.
I'll create three components: App, ChatInput, and Feed. App renders the ChatInput and the Feed. It looks like this. I'll need to pass the socket constant down to the sub-components cause they use socket to send data to and receive data from the server.
const App = () => {
const socket = io('http://localhost:8080');
return (
<main>
<ChatInput socket={socket} />
<Feed socket={socket} />
</main>
);
};
The ChatInput component should look very familiar. There is a state value, a controlled input, and a submit handler. Here's where things get interesting. When you hit the submit button, instead of updating the state of the application. You're going to invoke the emit method on the socket (which was passed down s a prop from the App component). Emit will send data from the client to the server.
The emit method has two parameters: a string which is the name of the event and the thing you want to send to the server. I'm sending the state value text.
const ChatInput = ({ socket }) => {
const [text, setText] = useState('');
const handleSubmit = () => {
socket.emit('sendMessage', text);
setText('');
};
return (
<section>
<input type="text" value={text} onChange={setText} />
<button type="button" onClick={handleSubmit}>
Send
</button>
</section>
);
};
Next, I'll go back in the server and respond to the event emitted by the client. After creating the socket.io server and before invoking the listen method on the http server object. I'll invoke the on method on the socket.io server. This method takes two parameters, a string and a callback. I'll pass 'connection' in as the first argument and a callback with one parameter 'socket' in as the second argument.
I like to log a message to the console to confirm the client connected to the server. Next I'll invoke the on method on the socket. I'll pass in a string which is the name of the event emitted from the client and a callback with one parameter. Call the parameter whatever is send from the client. In my case, it's text.
Now all you have to do is run the emit method on the socket io server. You can name the event whatever you'd like. I called mine the same name as the event emitted by the client. I passed in the text send by the client as the second argument.
io.on('connection', (socket) => {
console.log(`client ${socket.id} connected to socket.io server`);
socket.on('addMessage', (text) => {
io.emit('addMessage', text);
});
});
Next, in the Feed, invoke the on method on socket. Pass in the name of the event emitted by the server and a callback. Update the state of the component accordingly.
const Feed = ({ socket }) => {
const [messages, setMessages] = useState([]);
socket.on('addMessage', (text) => {
setMessages([...messages, text]);
});
return (
<section>
{messages.map((message) => (
<article key={Math.random()}>message</article>
))}
</section>
);
};
Those are the basics of socket.io. There are a lot of options. You can broadcast events from one client to every other client. The server can iterate over all clients and emit messages to some of them. The documentation is really helpful and easy to understand.
Top comments (0)