Categories
MongoDB

Using MongoDB with Mongoose

To make MongoDB database manipulation easy, we can use the Mongoose NPM package to make working with MongoDB databases easier.

In this article, we’ll look at how to use Mongoose to manipulate our MongoDB database.

Getting Started with Mongoose

We can install the package by running:

npm install mongoose --save

Then we can use it by writing:

const mongoose = require('mongoose');
const connection = "mongodb://localhost:27017";
mongoose.connect(connection, { useNewUrlParser: true });
const db = mongoose.connection;
db.on('error', () => console.error('connection error:'));
db.once('open', () => {
  console.log('connected')
});

We connect th our MongoDB database with Mongoose by using the mongoose.connect method.

Then to see if we connected successfully, we can listen to the open event.

Defining Schemas

Now we can define a schema to restrict what we can put on our documents.

This is something that’s not available with the native MongoDB client.

For example, we can write:

const mongoose = require('mongoose');
const connection = "mongodb://localhost:27017";
mongoose.connect(connection, { useNewUrlParser: true });
const db = mongoose.connection;
db.on('error', () => console.error('connection error:'));
db.once('open', () => {
  console.log('connected')
});

const kittySchema = new mongoose.Schema({
  name: String
});

const Kitten = mongoose.model('Kitten', kittySchema);
const baby = new Kitten({ name: 'james' });
baby.save((err, baby) => {
  if (err) {
    return console.error(err);
  }
  console.log(baby.name);
});

We create the schema with the mongoose.Schema constructor.

The object has the fields as the keys and the data type as the values.

Then we define the model with the mongoose.model method.

Then we use the Kitten constructor, and then we call save to save the Kitten object to the database.

The _id will automatically be generated for each inserted entry.

If we want to get the entries, then we can call the find method:

const mongoose = require('mongoose');
const connection = "mongodb://localhost:27017/test";
mongoose.connect(connection, { useNewUrlParser: true });
const db = mongoose.connection;
db.on('error', () => console.error('connection error:'));
db.once('open', () => {
  console.log('connected')
});

const kittySchema = new mongoose.Schema({
  name: String
});

const Kitten = mongoose.model('Kitten', kittySchema);
const baby = new Kitten({ name: 'james' });
baby.save((err, baby) => {
  if (err) {
    return console.error(err);
  }
  console.log(baby.name);
});

Kitten.find((err, kittens) => {
  if (err) return console.error(err);
  console.log(kittens);
})

If we want to find a specific entry we can pass in an object to the first argument of find :

const mongoose = require('mongoose');
const connection = "mongodb://localhost:27017/test";
mongoose.connect(connection, { useNewUrlParser: true });
const db = mongoose.connection;
db.on('error', () => console.error('connection error:'));
db.once('open', () => {
  console.log('connected')
});

const kittySchema = new mongoose.Schema({
  name: String
});

kittySchema.methods.speak = function () {
  console.log(`hello ${this.name}`);
}

const Kitten = mongoose.model('Kitten', kittySchema);
const baby = new Kitten({ name: 'james' });
baby.speak();
baby.save((err, baby) => {
  if (err) {
    return console.error(err);
  }
  console.log(baby.name);
});

Kitten.find({ name: /^james/ },(err, kittens) => {
  if (err) return console.error(err);
  console.log(kittens);
})

If we want to add methods, we can write:

const mongoose = require('mongoose');
const connection = "mongodb://localhost:27017/test";
mongoose.connect(connection, { useNewUrlParser: true });
const db = mongoose.connection;
db.on('error', () => console.error('connection error:'));
db.once('open', () => {
  console.log('connected')
});

const kittySchema = new mongoose.Schema({
  name: String
});

kittySchema.methods.speak = function () {
  console.log(`hello ${this.name}`);
}

const Kitten = mongoose.model('Kitten', kittySchema);
const baby = new Kitten({ name: 'baby' });
baby.speak();
baby.save((err, baby) => {
  if (err) {
    return console.error(err);
  }
  console.log(baby.name);
});

Kitten.find((err, kittens) => {
  if (err) return console.error(err);
  console.log(kittens);
})

We added a method to the Kitten model by adding a method to the methods object property.

Conclusion

We can create schemas, add items, and find items with Mongoose.

Categories
MongoDB

Node.js Basics — Creating and Deploying Packages

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.

Creating NPM Packages

We can create our own NPM packages to do that, we run npm add user so we can log into NPM to create our package.

Once we typed in the command, we’ll have to enter a username, password, and email address for our account to create an account on NPM.

The email wil be public.

Then we run npm init to create our package’s package.json file.

Once we answered all the questions, we should get something like:

{
  "name": "npm-example",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo "Error: no test specified" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

Once we’re done, we can run npm publish to publish our package.

Then once we published our package, we can run:

npm install npm-example

to install our package.

To update the version of our package, we run npm version patch .

Then we can run npm publish to publish the package with the later version.

To keep some files from being published in a package, we can create a .npmignore file which follows the same rules as the .gitignore file.

Conclusion

We can create NPM packages easily with npm .

All we have to do is enter some package data and publish the package.

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.