Adding Real-Time in Strapi with Socket.io

Adfab 🚀
Strapi
Published in
6 min readFeb 22, 2018

Strapi is a really promising CMF that make API managment so simple, it is based on Koa and comes backed with and easy admin panel to create and manage your API.

We don’t need alot of work to set up our API anymore we can focus on others fun stuff, let’s make thing goes real time with strapi & socket.io !

Setup strapi

Install strapi (currently strapi@3.0.0-alpha.10.2):

npm install strapi@alpha -g

Create a Strapi project:

strapi new strapi_socket
> Choose your main database: MongoDB (highly recommended)
> Database name: strapi_socket
> Host: 127.0.0.1
> Port: 27017
> Username:
> Password:

Go to your project root folder:

cd strapi_socket

Install Strapi dependencies:

npm i

Install socket.io (currently socket.io@2.0.4) as dependency:

npm install --save socket.io

Before starting Strapi you can do npm ls strapi which should print something like this:

strapi_socket@0.1.0 /path/to/project/strapi_socket
└── strapi@3.0.0-alpha.10.1

So you know where is the strapi used and we can see this is the local strapi inside our project’s node_modules and not the global version. We can now start strapi by running:

strapi start

Don’t forget to start mongoDB (mongod on your terminal before starting strapi).

Open /admin that will redirect you to /admin/plugins/users-permissions/auth/register. Register your admin user to login to the admin panel.

Add a new model

In a few steps I will create a simple model named bakery from the content type builder tab:

And give it two fields:

  • Field name is a string.
  • Field rating is an integer.

IMPORTANT: don’t forget to click save!

Strapi will create this folder structure inside your project, those folders are self explanatory config, controllers, models, services… We can use them to do more, like add some logique inside the controller, that is exactly what we will do later.

Modifying users permissions

Setup users permissions for guest role (not logged in) to read/create bakery entries, go back to admin panel on tab Users & Permissions tab. Click on Guest role, on application check create & find and don’t forget to click save !

View data at /bakery it should render an empty array []

Send socket event based from server to users

Open config/functions/bootstrap.js and create public/bakery.html. Add these two codes:

File: config/functions/bootstrap.js

module.exports = cb => {
// import socket io
var io = require('socket.io')(strapi.server);
// listen for user connection
io.on('connection', function(socket){
// send message on user connection
socket.emit('hello', JSON.stringify({message: 'Hello food lover'}));
// listen for user diconnect
socket.on('disconnect', () => console.log('a user disconnected'));
});
strapi.io = io; // register socket io inside strapi main object to use it globally anywhere cb();
};

Execute the following line in your terminal:

cp public/index.html public/bakery.html

File: public/bakery.html

<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>bakery</title>
</head>
<body>
<!-- add socket.io script -->
<script src="/socket.io/socket.io.js"></script>
<script>
// connect user throught socket
const socket = io();
// listen for event name 'hello' & log it
socket.on('hello', (res) => console.log(res));
</script>
</body>
</html>

Then reload your page, to see the log inside developper console like this:

Now let’s modify our HTML code to create new bakery entries from our bakery.html file with:

File: public/bakery.html

<section>
<h1>Add new delicious food</h1>
<label for="name">
<span>Name :</span>
<!-- name of the bakery entity -->
<input type="text" name="name" id="name" />
</label>
<label for="rating">
<span>Rating :</span>
<!-- rating of the bakery entity -->
<select name="rating" id="rating">
<option value="1">1 star</option>
<option value="2">2 stars</option>
<option value="3">3 stars</option>
<option value="4">4 stars</option>
<option value="5">5 stars</option>
</select>
</label>
<button id="add">Add</button>
</section>
<script src="/socket.io/socket.io.js"></script>
<script>
const socket = io();
// to get the value name to create an entity
const name = document.getElementById('name');
// to get the value rating to create an entity
const rating = document.getElementById('rating');
// to listen to click event and send our request
const add = document.getElementById('add');
add.addEventListener('click', () => { // listen to click event
// send a post request with our input value
fetch('/bakery', {
method: 'post',
headers: {"Content-type": "application/x-www-form-urlencoded; charset=UTF-8"},
body: `name=${name.value}&rating=${rating.value}`
})
.then((res) => {
if (res.status !== 200) return;
res.json().then((data) => console.log(data));
}
)
.catch((err) => console.log('Fetch Error :-S', err));
});
</script>

After changing our code, reload and set both name and rating value before submit with the “add” button.

You can check that the entry has been saved by checking /bakery api url with GET method, it should respond with an non empty array this time.

[{"_id":"5a86fdc6323f050a985a164d","name":"cookie","rating":4,"__v":0,"id":"5a86fdc6323f050a985a164d"}]

Yummy ! we stored our first bakery inside our mongoDB storage !

Notify users on food creation

Again we are going to make some code change, inside config/functions/bootstrap.js:

File: config/functions/bootstrap.js

module.exports = cb => {
var io = require('socket.io')(strapi.server);
var users = [];
io.on('connection', socket => {
socket.user_id = (Math.random() * 100000000000000); // not so secure
users.push(socket); // save the socket to use it later
socket.on('disconnect', () => {
users.forEach((user, i) => {
// delete saved user when they disconnect
if(user.user_id === socket.user_id) users.splice(i, 1);
});
});
});
strapi.io = io;
// send to all users connected
strapi.emitToAllUsers = food => io.emit('food_ready', food);
cb();
};

After that open our auto generated api controller for bakery entries api/bakery/controllers/Bakery.js, on ligne 47 there is the method that is called when a user create a bakery item.

We will call our emitToAllUsers() function from strapi that we defined on config/functions/bootstrap.js, inside create method add a new line:

File: api/bakery/controllers/Bakery.js

create: async (ctx) => {
const data = await strapi.services.bakery.add(ctx.request.body);
// Send 201 `created`
ctx.created(data);
// NEW LINE: call our method emitToAllUsers and pass it body request
strapi.emitToAllUsers(ctx.request.body);
},

At the end of our script tag inside public/bakery.html listen fo the food_ready event, add a div#food on your html plus some JS:

File: public/bakery.html

Add a div#food to write messages to users:

<div id="food"></div>

Getting the event:

const food = document.getElementById('food');
socket.on('food_ready', res => food.innerHTML += `<div>- ${res.name} is ${res.rating}/5 so delicious</div>`);

Open two or more tab (I’m gonna use one chrome tab and one firefox tab) to test it, and play with both to see that both users get notified when someone save some sweet on our API.

That’s all, with this quick intro on how to use strapi & socket.io we can image doing things like chat app, html5 online multiplayer games etc …

Originally published at connect.adfab.fr on February 22, 2018, by Fabrice Labbé, Frontend Developer.

--

--

Adfab 🚀
Strapi
Writer for

Studio de production & d’innovation digitales. Based in France & Canada. www.adfab.fr