The Socket.IO is a simple and elegant open source library that can be used to create real-time applications like chat applications etc. Today I will show you how to make a simple real-time online reader badge react component for your blog post by Injecting React component in your existing, Laravel blade template. I am also making a demo application that will list all online uses count based on the blog post. Remember we are not using any database to store information, instead, we use a simple Node js server with Socket.IO to act as a broadcast server for all our user visit events. You can also user Pusher instead of Socket.IO and Vue.js instead of React to make this frontend component. I choose Socket.IO because it is free and it is extremely easy to setup Nodejs in a LEMP stack with Nginx reverse proxy when we deploy it to production.
First thing is to include React in our application. In the case of Laravel, you can easily include React using Laravel Mix.
Laravel Mix provides a fluent API for defining Webpack build steps for your Laravel application using several common CSS and JavaScript pre-processors. All you need to do is install Nodejs and NPM in your development system and run
npm install
to install all javascript dependencies in your application. By default jQuery, Bootstrap, Vuejs, Axios and Lodash are already included in this setup. Now just run
npm run watch
to compile your assets. Don't forget to include app.css and app.js files in the public folders to your Laravel View Layouts.
Now there is a little twist. If you are not using Vue.js in your laravel projects then you can replace the Vue.js with React by just running the following commands.
php artisan preset react
This command will swap out the Vue scaffolding with React scaffolding. The default Mix configuration, components, and all other related files will be updated accordingly. Don't forget to run npm install
and npm run watch
after executing this command. Please note that this will work only in Laravel 5.5 and above.
Now if you want to include React to an existing Laravel project (5.4 and above) without removing Vue.js then you can follow below instructions to include React without disrupting your old Vue components.
Replace your webpack.mix.js file with following snippets.
let mix = require('laravel-mix');
/*
|--------------------------------------------------------------------------
| Mix Asset Management
|--------------------------------------------------------------------------
|
| Mix provides a clean, fluent API for defining some Webpack build steps
| for your Laravel application. By default, we are compiling the Sass
| file for the application as well as bundling up all the JS files.
|
*/
mix.js('resources/assets/js/app.js', 'public/js')
.react('resources/assets/js/react.js', 'public/js')
.sass('resources/assets/sass/app.scss', 'public/css');
As you can see that I am bundling my React components into a separate output file ie to public/js/react.js
file. Don't forget to include this file in your blades where you use React components.
After modifying webpack.mix.js file install the latest version or React and ReactDOM as your development dependency by running the following command.
npm install react react-dom --save-dev
Then run,
npm run watch
This command will install babel-preset-react
for the first time and now everything is done and you successfully included React in your Laravel project.
The next thing you need to do is download and set up a simple node js server using Express and integrate Socket.IO in it. You can install Express and Socket.IO as your dependencies by running the following command.
npm install express socket.io --save
Now the complete code for the index.js file is given below.
const app = require('express')();
const http = require('http').Server(app);
const io = require('socket.io')(http);
io.set('transports', ['websocket']);
if (process.env.NODE_ENV === 'production') {
io.set('origins', 'https://yourdomainname.com:* ');
}
let users = [];
io.on('connection', socket => {
socket.on('visit', data => {
console.log('a user connected');
if (data) {
const found = users.find(user => {
return user.id === socket.id && user.post === data;
});
if (!found) {
users.push({ id: socket.id, post: data });
}
}
io.emit('onlineUsers', users);
});
socket.on('disconnect', () => {
console.log('user disconnected');
users = users.filter(user => {
return user.id !== socket.id;
});
io.emit('onlineUsers', users);
});
});
http.listen(3000, () => {
console.log('listening on *:3000');
});
Our express server will always run of localhost:3000. When a user visits our blog post a Visit Event will be triggered by our frontend client react component in the blog page. The post URL is also passed data for this event. Now we will store post and its associated unique user (Here we use socket.id as unique user id) as an object into the array of users. After storing this user post information we will immediately broadcast an onlineUsers Event to all our clients to notify about the new users. The client React component will automatically update the user count in the badge.
When a user closed the page or visit another page, a disconnect Event will be automatically triggered by Socket.IO. In this event, we removed the disconnected user from our array by filtering out using socket id. After the user is removed we again send an onlineUsers Event to all our clients to notify about the user disconnection.
Now let's create a simple React component to show online reader badge at the top right corner of the blog page. But first, we need to install Socket.IO client. You can install it by running the following command.
npm install socket.io-client --save-dev
After installing Socket.IO client, Include our React Component for the Online Reader Badge in your app.js file as follows.
require('./components/OnlineVisitors/OnlineVisitorsBadge');
The complete code for OnlineVisitorsBadge component is given below.
import React from 'react';
import ReactDOM from 'react-dom';
import io from 'socket.io-client';
export default class OnlineVisitorsBadge extends React.Component {
constructor(props) {
super(props);
this.state = {
usersCount: 0,
post: window.location.href
};
this.updateUserCount = this.updateUserCount.bind(this);
}
componentDidMount() {
const socket = io(this.props.url, { transports: ['websocket'] });
socket.emit('visit', this.state.post);
socket.on('onlineUsers', users => this.updateUserCount(users));
}
updateUserCount(users) {
const post = this.state.post;
let found = users.filter(user => {
return user.post === post;
});
this.setState(() => ({ usersCount: found.length }));
}
render() {
const usersCount = this.state.usersCount;
return (
<div className="uk-align-right uk-badge visitors-badge">
{usersCount > 1 ? `${usersCount} readers` : `${usersCount} reader`}
</div>
);
}
}
if (document.getElementById('online-visitors-badge')) {
const el = document.getElementById('online-visitors-badge');
const props = Object.assign({}, el.dataset);
ReactDOM.render(<OnlineVisitorsBadge {...props} />, el);
}
First, we initialize the Socket.IO client with WebSocket as transport policy in componentDidMount life cycle hook. Now immediately we emit a user Visit Event with URL of the blog post. Then we always listen to onlineUsers Broadcast Event from our socket server to update the user count state.
I am using UIKit 3 instead of Bootstrap for styling my website. It already has some syles for the badge. Now add some styles to change its colour and position.
.visitors-badge {
background-color: #7c4dff !important;
position: absolute;
right: 16px;
}
Now inject this component in your laravel blade file as follows.
<!-- Inject React Online Badge Table Component -->
<span id="online-visitors-badge"
data-url="{{ env('SOCKET','http://localhost:3000/') }}">
</span>
That is it. Now run the node js server by typing node index.js
and visit your blade file to view the component. Duplicate the page to see an increase in readers count.
You can see a demo of this Online Readers Count Badge by visiting the top of this page. It is there at top right corner.
Now let's create a demo application that shows all visitors count based on individual blog posts. It is also very easy since we already broadcasting entire user data in onlineUsers Event which has all the information we needed.
Add the following route to your web.php file
Route::get('demos/online-visitors','DemoController@viewOnlineVisitors');
Now Let's create our DemoController by running the following command.
php artisan make:controller DemoController
The complete code for DemoController is given below.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class DemoController extends Controller
{
/**
* To display the show page
*
* @return \Illuminate\Http\Response
*/
public function viewOnlineVisitors()
{
return view('demos.onlineVisitors');
}
}
The viewOnlineVisitors method is used to just show our view page.
Note: Both controller and routes parts will exclusively depend on the projects you are working on. They are unnecessary in this tutorial as only this they do is give some response data. I just added them only for reference purpose.
Now let's create a demo/onlineVisitors.blade.php file to include our where we inject our main React Component. The complete code of this file is given below.
@extends('main')
@section('title','Online Visitors Count using Socket.io and React Demo -')
@section('content')
<div class="uk-container uk-margin">
<div class="uk-grid uk-margin-top uk-flex-center" data-ukgrid>
<div class="uk-width-1-1">
<h3 class="uk-heading-line uk-text-center uk-text-bold uk-margin m-b-50">
<span>Online Visitors Count using Socket.io and React Demo</span>
</h3>
</div>
<!-- Inject React Online Visitors Table Component -->
<div class="uk-width-1-1" id="online-visitors-table"
data-url="{{ env('SOCKET','http://localhost:3000/') }}"></div>
</div>
</div>
@stop
Don't forget to include your bundled js file at bottom of your view file. Remember you will see some fancy class names but don't worry about it as CSS and HTML parts changes according to your requirements. I used UIKIt 3 to create ShareurCodes.
First, don't forget to include our React Component for the Online Reader Table in your app.js file as follows.
require('./components/OnlineVisitors/OnlineVisitorsTable');
The complete code of OnlineVisitorsTable component is given below.
import React from 'react';
import ReactDOM from 'react-dom';
import io from 'socket.io-client';
export default class OnlineVisitorsTable extends React.Component {
constructor(props) {
super(props);
this.state = {
postUsers: []
};
this.updatePostUsers = this.updatePostUsers.bind(this);
}
componentDidMount() {
const socket = io(this.props.url, { transports: ['websocket'] });
socket.emit('visit');
socket.on('onlineUsers', data => this.updatePostUsers(data));
}
updatePostUsers(data) {
const posts = data.map(post => post.post);
const uniquePosts = [...new Set(posts)].filter(x => x !== null);
const postUsers = uniquePosts.map(post => {
const users = data.filter(user => {
if (user.post === post) return true;
});
return {
post,
userCount: users.length
};
});
this.setState(() => ({ postUsers }));
}
render() {
const postUsers = this.state.postUsers;
return (
<table className="uk-table uk-table-divider uk-table-responsive">
<thead>
<tr>
<th>Blog Post</th>
<th>Online Readers</th>
</tr>
</thead>
<tbody>
{postUsers.length > 0 &&
postUsers.map(postUser => (
<tr key={postUser.post}>
<td>
<a href={postUser.post} target="_blank">
{postUser.post}
</a>
</td>
<td>
<span className="uk-badge violet-bg">
{postUser.userCount > 1
? `${postUser.userCount} readers`
: `${postUser.userCount} reader`}
</span>
</td>
</tr>
))}
</tbody>
</table>
);
}
}
if (document.getElementById('online-visitors-table')) {
const el = document.getElementById('online-visitors-table');
const props = Object.assign({}, el.dataset);
ReactDOM.render(<OnlineVisitorsTable {...props} />, el);
}
First, we initialize the Socket.IO client with WebSocket as transport policy in componentDidMount life cycle hook. Now immediately we emit a user Visit Event with URL of the blog post. Then we always listen to onlineUsers Broadcast Event from our socket server to update the user count state.
Now may wonder why I used following syntax,
[...new Set(posts)].filter(x => x !== null);
The posts are an array will duplicate posts and null values. To remove all duplicate value easily in an array we can use a new ES6 javascript collection called Set which is an object lets you store unique values of any type, whether primitive values or object references.I spread the Set inside an array literal to make a new array which consists of unique posts. I used the array filter to remove null values from the array.
Now since we created the entire module, let's deploy it in a LEMP Stack. First, we need to install Node.js and NPM in your server. I am using Ubuntu 18.04 to host shareurcodes in a Digital Ocean Droplet. The Ubuntu 18.04 contains a version of Node.js in its default repositories that can be used to provide a consistent experience across multiple systems. The node version it has now is v8.10.0. It is not the latest version. But it is more than enough for our needs.
To get this version, you can use the apt package manager. Refresh your local package index by typing:
sudo apt update
To install Node.js from the repositories:
sudo apt install nodejs
To install npm
sudo apt install npm
Now check the version of node.js to verify that it is installed correctly.
nodejs -v
Now we need to run our Socket.IO server forever in the background in our system. For that, we need to install pm2 globally - the production process manager for Node.js
npm install pm2 -g
Add pm2 start script in your package.json file with the production environment configuration.
"scripts": {
"start": "NODE_ENV=production pm2 start index.js --name nameofyourapplication --max-memory-restart 250M",
},
Now we can start our Socket.IO server in the background by using pm2 as follows.
npm start
Now your Socket.IO server started at localhost:3000 in your server.
To stop pm2
pm2 stop nameofapplication
Note: If you update your index.js file and redeploy it then remember to stop and start your application in pm2 again. Otherwise, changes will not take effect.
Now update your .env file with SOCKET env variable and add your domain URL in it.
SOCKET=https://example.com/
Now the last part is to reverse proxy our port 443 (or port 80 if you are not using https connections) to localhost:3000 using Nginx.
In your /etc/nginx/sites-available/default page add the following location block in server block for port 443.
# Requests for socket.io are passed on to Node on port 3000
location /socket.io {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_pass http://localhost:3000;
}
The above code block will proxy all request to /socket.io to localhost:3000 of our server.
Now test your nginx.conf file syntax
nginx -t
If the syntax is ok then restart the nginx for changes to apply.
sudo systemctl restart nginx
That's it is. Now the Socket.IO server is up in your LEMP stack.
You can demo the above application by visiting the following link.
https://shareurcodes.com/demos/online-visitors
If anybody has any suggestions or doubts or need any help comment below and I try will respond to every one of you as early as possible.