Categories
Node.js Basics

Node.js Basics — Socket.io Namespace and Rooms

Node.js is a popular runtime platform to create programs that run on it.

It lets us run JavaScript outside the browser.

In this article, we’ll look at how to start using Node.js to create programs.

Namespace and Express Middleware

We can use Socket.io namespaces with Express middleware.

For example, we can write:

index.js

const express = require('express');
const app = express();
const server = require('http').createServer(app);
const options = { /* ... */ };
const io = require('socket.io')(server, options);
const session = require('express-session');
const wrap = middleware => (socket, next) => middleware(socket.request, {}, next);

io.use(wrap(session({ secret: 'cats' })));
io.on('connection', socket => {
  console.log('connect')
});

const adminNamespace = io.of('/admin');

adminNamespace.use((socket, next) => {
  console.log(socket);
  next();
});

adminNamespace.on('connection', socket => {
  socket.on('delete user', (data) => {
    console.log(data);
  });
});

app.get('/', (req, res) => {
  res.sendFile(`${__dirname}/index.html`);
});

server.listen(3000);

index.html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Minimal working example</title>
</head>

<body>
  <ul id="events"></ul>

  <script src="/socket.io/socket.io.js"></script>
  <script>
    const socket = io('/admin');
    socket.emit('delete user', 'foo')
  </script>
</body>

</html>

In index.js , we added the express-session middleware.

We combine the middleware with Socket.io with the wrap function.

The function returns a function that returns the middleware called by the middleware function with the socket.request and next function together as one.

Then we can call that function with io.use with the express-session options.

Rooms in Namespaces

We can add rooms to namespaces by calling various methods.

To join a room, we can call the socket.join method:

index.js

const express = require('express');
const app = express();
const server = require('http').createServer(app);
const options = { /* ... */ };
const io = require('socket.io')(server, options);
io.on('connection', function (socket) {
  socket.join("room");
  io.sockets.in("room").emit('connectToRoom', "You are in room");
})

app.get('/', (req, res) => {
  res.sendFile(`${__dirname}/index.html`);
});

server.listen(3000);

index.html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Minimal working example</title>
</head>

<body>
  <ul id="events"></ul>

  <script src="/socket.io/socket.io.js"></script>
  <script>
    const socket = io();
    socket.on('connectToRoom', function (data) {
      document.write(data);
    });
  </script>
</body>

</html>

In index.js , we listen to the connection event.

In the callback, we call socket.join with the room name.

Then we call io.sockets.in method to emit an event in the room.

The emit method has event name the first argument and the 2nd argument is the data we want to send.

In index.html , we listen to the connectToRoom event that we emitted and get the data from the callback parameter.

We can listen to the disconnect event to watch for room disconnections.

For example, we can write:

index.js

const express = require('express');
const app = express();
const server = require('http').createServer(app);
const options = { /* ... */ };
const io = require('socket.io')(server, options);
io.on('connection', function (socket) {
  socket.join("room");
  io.sockets.in("room").emit('connectToRoom', "You are in room");
  socket.on('disconnecting', () => {
    const rooms = Object.keys(socket.rooms);
    console.log(rooms)
  });

  socket.on('disconnect', () => {
    console.log('disconnect')
  });
})

app.get('/', (req, res) => {
  res.sendFile(`${__dirname}/index.html`);
});

server.listen(3000);

We listen to the disconnecting and disconnect events to listen for room disconnections.

socket.rooms has all the room data.

The keys of it have the names of the rooms.

Conclusion

We can listen for room connections and disconnections and emit events to rooms from the server to the client with Socket.io.

Categories
Node.js Basics

Node.js Basics — Socket.io

Node.js is a popular runtime platform to create programs that run on it.

It lets us run JavaScript outside the browser.

In this article, we’ll look at how to start using Node.js to create programs.

Real-Time Communication with Socket.io

Socket.io is a real-time communication library that combines HTTP requests with WebSockets to enable real-time communication in our app.

For example, we can create a simple app with Socket.io by writing:

index.js

const content = require('fs').readFileSync(__dirname + '/index.html', 'utf8');

const httpServer = require('http').createServer((req, res) => {
  res.setHeader('Content-Type', 'text/html');
  res.setHeader('Content-Length', Buffer.byteLength(content));
  res.end(content);
});

const io = require('socket.io')(httpServer);

io.on('connect', socket => {
  console.log('connect');
});

httpServer.listen(3000, () => {
  console.log('go to http://localhost:3000');
});

index.html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Minimal working example</title>
</head>

<body>
  <ul id="events"></ul>

<script src="/socket.io/socket.io.js"></script>
  <script>
    const $events = document.getElementById('events');

    const newItem = (content) => {
      const item = document.createElement('li');
      item.innerText = content;
      return item;
    };

    const socket = io();

    socket.on('connect', () => {
      $events.appendChild(newItem('connect'));
    });
  </script>
</body>

</html>

In index.js , we server the index.html file.

Also, we listen to the connect event to listen to any connections that are made from the client side.

In index.html , we make the connection.

We call socket.on to listen to connect event to add the item when we connect to the Socket.io server.

Installation with Express

We can use Socket.io with an Express app.

To do that, we can write:

index.js

const express = require('express');
const app = express();
const server = require('http').createServer(app);
const options = { /* ... */ };
const io = require('socket.io')(server, options);

io.on('connection', socket => {
  console.log('connect')
});

app.use(express.static('public'));

app.get('/', (req, res) => {
  res.sendFile(`${__dirname}/index.html`);
});

server.listen(3000);

index.html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Minimal working example</title>
</head>

<body>
  <ul id="events"></ul>

  <script src="/socket.io/socket.io.js"></script>
  <script>
    const $events = document.getElementById('events');
    const newItem = (content) => {
      const item = document.createElement('li');
      item.innerText = content;
      return item;
    };
    const socket = io();
    socket.on('connect', () => {
      $events.appendChild(newItem('connect'));
    });
  </script>
</body>

</html>

We use the same index.html file as before.

The only difference is that we serve it with express.

We call http module’s createServer method with the Express app instance instead of using a callback with it.

Then we serve the static file with the res.sendFile method.

Namespace

We can create namespaces to segregate communication traffic.

For example, we can write:

index.js

const express = require('express');
const app = express();
const server = require('http').createServer(app);
const options = { /* ... */ };
const io = require('socket.io')(server, options);

io.on('connection', socket => {
  console.log('connect')
});

const adminNamespace = io.of('/admin');

adminNamespace.use((socket, next) => {
  console.log(socket);
  next();
});

adminNamespace.on('connection', socket => {
  socket.on('delete user', (data) => {
    console.log(data);
  });
});

app.get('/', (req, res) => {
  res.sendFile(`${__dirname}/index.html`);
});

server.listen(3000);

index.html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Minimal working example</title>
</head>

<body>
  <ul id="events"></ul>

  <script src="/socket.io/socket.io.js"></script>
  <script>
    const socket = io('/admin');
    socket.emit('delete user', 'foo')
  </script>
</body>

</html>

In the index.js file, we created the /admin namespace with:

const adminNamespace = io.of('/admin');

Then we call the use method with our own middleware function before we listen for the connections to the namespace.

We can add our logic to allow or deny the connect within the use callback.

We call next to finish the connection.

The adminNamespace.on method watches the connection event to watch for connections to the namespace.

Then we call socket.on with the event name we want to listen to and then get the data from the data parameter.

In index.html , we call io('/admin') to connect to the '/admin' namespace.

Then we call socket.emit with the event name as the first argument and the event data as the 2nd argument.

Conclusion

We can do real-time communication with Socket.io easily.

Categories
Node.js Basics

Node.js Basics — Monitoring with MongoDB

Node.js is a popular runtime platform to create programs that run on it.

It lets us run JavaScript outside the browser.

In this article, we’ll look at how to start using Node.js to create programs.

Monitoring with MongoDB

We can monitor MongoDB activities by listening to the events it emits.

For example, we can write:

const { MongoClient } = require('mongodb');
const connection = "mongodb://localhost:27017";
const client = new MongoClient(connection);
const eventName = "serverDescriptionChanged";
client.on(eventName, event => {
  console.log(`received ${eventName}: ${JSON.stringify(event, null, 2)}`);
});

async function run() {
  try {
    await client.connect();
    const db = client.db("test");
    db.dropCollection('test');
    const testCollection = await db.collection('test');
    const result = await testCollection.insertMany([
      { "_id": 1, "name": "apples", "qty": 5, "rating": 3 },
      { "_id": 2, "name": "apples", "qty": 7, "rating": 1 },
      { "_id": 3, "name": "oranges", "qty": 6, "rating": 2 },
      { "_id": 4, "name": "avocados", "qty": 3, "rating": 5 },
    ]);
    console.log(result)
    const cursor = await testCollection.find();
    cursor.forEach(console.dir);
  } finally {
    await client.close();
  }
}
run().catch(console.dir);

to watch for serverDescriptionChanged events and log the event data.

Then we get something like:

received serverDescriptionChanged: {
  "address": "localhost:27017",
  "previousDescription": {
    "address": "localhost:27017",
    "arbiters": [],
    "hosts": [],
    "passives": [],
    "type": "Unknown"
  },
  "newDescription": {
    "address": "localhost:27017",
    "arbiters": [],
    "hosts": [],
    "passives": [],
    "type": "Standalone"
  }
}

logged in the console.

Other events include:

  • serverOpening — Emitted when a connection to an instance opens.
  • serverClosed — Emitted when a connection to an instance closes.
  • serverDescriptionChanged — Emitted when an instance state changes
  • topologyOpening — Emitted prior to attempting a connection to an instance.topologyClosed — Emitted after all instance connections in the topology close.
  • topologyDescriptionChanged — Emitted when the topology changes, such as an election of a new primary or a mongos proxy disconnecting.
  • serverHeartbeatStarted — Emitted before issuing an isMaster command to a MongoDB instance.
  • serverHeartbeatSucceeded — Emitted when the isMaster command returns successfully from a MongoDB instance.
  • serverHeartbeatFailed — Created when an isMaster command issued to a specific MongoDB instance fails to return a successful response

The type field of the ServerDescription object is the event that has one of the following values:

  • Unknown — Unknown instance
  • Standalone — Standalone instanceMongosMongos proxy instance
  • PossiblePrimary — At least one server recognizes this as the primary, but is not yet verified by all instances.
  • RSPrimary — Primary instance
  • RSSecondary — Secondary instance
  • RSArbiter — Arbiter instance
  • RSOther
  • RSGhost

The topologyDescriptionChanged event has the type field in the TopologyDescription object which can be one of the following values:

  • Single — Standalone instance
  • ReplicaSetWithPrimary — Replica set with a primary
  • ReplicaSetNoPrimary — Replica set with no primary
  • Sharded — Sharded cluster
  • Unknown — Unknown topology

Conclusion

We can monitor for events with the MongoDB Node.js client to do monitoring.

Categories
MongoDB Node.js Basics

Node.js Basics — MongoDB Collations

Node.js is a popular runtime platform to create programs that run on it.

It lets us run JavaScript outside the browser.

In this article, we’ll look at how to start using Node.js to create programs.

Collations

Collations are sets of sorting rules that are used when we do string order for specific languages and locales.

We can specify the collation property when we create the collection.

For example, we can write:

const { MongoClient } = require('mongodb');
const connection = "mongodb://localhost:27017";
const client = new MongoClient(connection);

async function run() {
  try {
    await client.connect();
    const db = client.db("test");
    await db.dropCollection('test');
    await db.createCollection("test", {
      collation: { locale: "en" },
    });
    const testCollection = await db.collection('test');
    await testCollection.dropIndexes();
    await testCollection.deleteMany({})
    const result = await testCollection.insertMany([
      { "_id": 1, "name": "apples", "qty": 5, "rating": 3 },
      { "_id": 2, "name": "bananas", "qty": 7, "rating": 1 },
      { "_id": 3, "name": "oranges", "qty": 6, "rating": 2 },
      { "_id": 4, "name": "avocados", "qty": 3, "rating": 5 },
    ]);
    console.log(result)
    const query = {};
    const projection = { name: 1 };
    const cursor = testCollection
      .find(query)
      .project(projection);
    cursor.forEach(console.dir);
  } finally {
    await client.close();
  }
}
run().catch(console.dir);

We call createCollection with the collection name as the first argument.

The 2nd argument is an object where we have the collation property to set the collation rule.

Then we call find to query the collection.

Assign a Collation to an Index

We can also assign a collection to an index.

For example, we can write:

const { MongoClient } = require('mongodb');
const connection = "mongodb://localhost:27017";
const client = new MongoClient(connection);

async function run() {
  try {
    await client.connect();
    const db = client.db("test");
    await db.dropCollection('test');
    await db.createCollection("test");
    const testCollection = await db.collection('test');
    await testCollection.createIndex(
      { 'name': 1 },
      { 'collation': { 'locale': 'en' } });
    await testCollection.dropIndexes();
    await testCollection.deleteMany({})
    const result = await testCollection.insertMany([
      { "_id": 1, "name": "apples", "qty": 5, "rating": 3 },
      { "_id": 2, "name": "bananas", "qty": 7, "rating": 1 },
      { "_id": 3, "name": "oranges", "qty": 6, "rating": 2 },
      { "_id": 4, "name": "avocados", "qty": 3, "rating": 5 },
    ]);
    console.log(result)
    const query = { name: 'apple' };
    const options = { "collation": { "locale": "en_US" } };
    const cursor = testCollection
      .find(query, options)
    cursor.forEach(console.dir);
  } finally {
    await client.close();
  }
}
run().catch(console.dir);

We have:

await testCollection.createIndex(
      { 'name': 1 },
      { 'collation': { 'locale': 'en' } });

We called createIndex with the fields to index in the first argument.

The 2nd argument has the collation options.

Then we can use it by writing:

const { MongoClient } = require('mongodb');
const connection = "mongodb://localhost:27017";
const client = new MongoClient(connection);

async function run() {
  try {
    await client.connect();
    const db = client.db("test");
    await db.dropCollection('test');
    await db.createCollection("test");
    const testCollection = await db.collection('test');
    await testCollection.createIndex(
      { 'name': 1 },
      { 'collation': { 'locale': 'en' } });
    await testCollection.dropIndexes();
    await testCollection.deleteMany({})
    const result = await testCollection.insertMany([
      { "_id": 1, "name": "apples", "qty": 5, "rating": 3 },
      { "_id": 2, "name": "bananas", "qty": 7, "rating": 1 },
      { "_id": 3, "name": "oranges", "qty": 6, "rating": 2 },
      { "_id": 4, "name": "avocados", "qty": 3, "rating": 5 },
    ]);
    console.log(result)
    const cursor = testCollection
      .find()
      .sort({ "name": -1 });
    cursor.forEach(console.dir);
  } finally {
    await client.close();
  }
}
run().catch(console.dir);

We sort the name field with the sort method in descending order.

Conclusion

We can add collations applying sorting rules for a specific language with MongoDB.

Categories
MongoDB Node.js Basics

Node.js Basics — MongoDB Collation Rules for Deletion and Aggregation

Node.js is a popular runtime platform to create programs that run on it.

It lets us run JavaScript outside the browser.

In this article, we’ll look at how to start using Node.js to create programs.

Collation and findOneAndDelete

We can set collation rules with the findOneAndDelete method.

For example, we can write:

const { MongoClient } = require('mongodb');
const connection = "mongodb://localhost:27017";
const client = new MongoClient(connection);

async function run() {
  try {
    await client.connect();
    const db = client.db("test");
    await db.dropCollection('test');
    await db.createCollection("test");
    const testCollection = await db.collection('test');
    await testCollection.createIndex(
      { 'name': 1 },
      { 'collation': { 'locale': 'en' } });
    await testCollection.dropIndexes();
    await testCollection.deleteMany({})
    const result = await testCollection.insertMany([
      { "_id": 1, "name": "apples", "qty": 5, "rating": 3 },
      { "_id": 2, "name": "bananas", "qty": 7, "rating": 1 },
      { "_id": 3, "name": "oranges", "qty": 6, "rating": 2 },
      { "_id": 4, "name": "avocados", "qty": 3, "rating": 5 },
    ]);
    console.log(result)
    testCollection
      .findOneAndDelete(
        { qty: { $gt: "5" } },
        { collation: { locale: "en", numericOrdering: true } },
      );
  } finally {
    await client.close();
  }
}
run().catch(console.dir);

We call findOneAndDelete with an object with the collation property to set the locale for the collation rule.

Collation and Aggregation

For example, we can write:

const { MongoClient } = require('mongodb');
const connection = "mongodb://localhost:27017";
const client = new MongoClient(connection);

async function run() {
  try {
    await client.connect();
    const db = client.db("test");
    await db.dropCollection('test');
    await db.createCollection("test");
    const testCollection = await db.collection('test');
    await testCollection.createIndex(
      { 'name': 1 },
      { 'collation': { 'locale': 'en' } });
    await testCollection.dropIndexes();
    await testCollection.deleteMany({})
    const result = await testCollection.insertMany([
      { "_id": 1, "name": "apples", "qty": 5, "rating": 3 },
      { "_id": 2, "name": "apples", "qty": 7, "rating": 1 },
      { "_id": 3, "name": "oranges", "qty": 6, "rating": 2 },
      { "_id": 4, "name": "avocados", "qty": 3, "rating": 5 },
    ]);
    console.log(result)
    const cursor = await testCollection
      .aggregate(
        [
          { $group: { "_id": "$name", "nameCount": { "$sum": 1 } } },
          { $sort: { "_id": 1 } },
        ],
        { collation: { locale: "en", numericOrdering: true } },
      );
    cursor.forEach(console.dir);
  } finally {
    await client.close();
  }
}
run().catch(console.dir);

We call aggregate on testCollection with an array of aggregation rules that we want to apply.

We aggregate the names together and get the count of each name.

Then we sort by the _id value which is the value of the name field.

We then sort by the _id in ascending order.

In the 2nd argument, we set the collation rule.

Then the result we get from the cursor is:

{ _id: 'apples', nameCount: 2 }
{ _id: 'avocados', nameCount: 1 }
{ _id: 'oranges', nameCount: 1 }

Conclusion

We can use collation rules with the findOneAndDelete and aggregate methods of a MongoDB collection object.