Categories
MongoDB Node.js Basics

Node.js Basics — Deleting and Updating MongoDB Documents

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.

Delete a Document

We can delete a document within our MongoDB collection.

To do that, we can write:

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

const pizzaDocuments = [
  { name: "Sicilian pizza", shape: "square" },
  { name: "New York pizza", shape: "round" },
  { name: "Grandma pizza", shape: "square" },
];

async function run() {
  try {
    await client.connect();
    const testCollection = await client.db("test").collection('test');
    const result = await testCollection.insertMany(pizzaDocuments);
    console.log(result)
    const deleteResult = await testCollection.deleteOne({ name: { $type: "string" } });
    console.log(deleteResult)
  } finally {
    await client.close();
  }
}
run().catch(console.dir);

We pass in a query into the deleteOne method to delete all the entries with the name that has type string.

Also, we can delete all the entries with the name set to type string with the deleteMany method:

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

const pizzaDocuments = [
  { name: "Sicilian pizza", shape: "square" },
  { name: "New York pizza", shape: "round" },
  { name: "Grandma pizza", shape: "square" },
];

async function run() {
  try {
    await client.connect();
    const testCollection = await client.db("test").collection('test');
    const result = await testCollection.insertMany(pizzaDocuments);
    console.log(result)
    const deleteResult = await testCollection.deleteMany({ name: { $type: "string" } });
    console.log(deleteResult)
  } finally {
    await client.close();
  }
}
run().catch(console.dir);

Change a Document

We can update a document with thw updateOne method.

For example, we can write:

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

const filter = { foo: 'bar' };
const updateDocument = {
  $set: {
    foo: 'baz',
  },
};

async function run() {
  try {
    await client.connect();
    const testCollection = await client.db("test").collection('test');
    const result = await testCollection.updateOne(filter, updateDocument);
    console.log(result)
  } finally {
    await client.close();
  }
}
run().catch(console.dir);

We call the updateOne method to find the item with the query object in the first argument.

The 2nd argument is the document to update the item with.

The $set property indicates that we want to add or update the key-value pair in the object we set as its value.

Update Arrays in a Document

We can update arrays in a document by accessing its property.

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 testCollection = await client.db("test").collection('test');
    const result = await testCollection.insertOne({
      name: "Popeye",
      address: "1 Sweethaven",
      items: [
        {
          type: "pizza",
          size: "large",
          toppings: ["garlic, spinach"],
        },
        {
          type: "calzone",
          toppings: ["ham"],
        },
      ],
    });
    console.log(result)
    const query = { name: "Popeye" };
    const updateDocument = {
      $set: { "items.0.toppings": "veggie" }
    };
    const updateResult = await testCollection.updateOne(query, updateDocument);
    console.log(updateResult)
  } finally {
    await client.close();
  }
}
run().catch(console.dir);

We call insertOne to add the entry we want into our collection.

Then we define the updateDocument object with the $set property to set the item we want.

items.0.toppings will get the toppings property of the first entry ofitems array and update it.

To match all array elements, we can use the $[] positional operator:

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

async function run() {
  try {
    await client.connect();
    const testCollection = await client.db("test").collection('test');
    const result = await testCollection.insertOne({
      name: "Popeye",
      address: "1 Sweethaven",
      items: [
        {
          type: "pizza",
          size: "large",
          toppings: ["garlic, spinach"],
        },
        {
          type: "calzone",
          toppings: ["ham"],
        },
      ],
    });
    console.log(result)
    const query = { name: "Popeye" };
    const updateDocument = {
      $set: { "items.$[].toppings": "veggie" }
    };
    const updateResult = await testCollection.updateOne(query, updateDocument);
    console.log(updateResult)
  } finally {
    await client.close();
  }
}
run().catch(console.dir);

Now both items in the items array has 'veggie' appended into it.

Conclusion

We can delete a document and update an array in an existing document with the MongoDB driver.

Categories
Node.js Basics

Node.js Basics — Level DB

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.

Level DB

Most apps need to store data somewhere.

They most likely use a database to store their data.

One way to store data within a Node app is to use the Level DB database.

It’s a key-value pair database, which makes it a NoSQL database.

It supports JSON right off the bat so we won’t have to do much to get and set the data.

The database is stored in a file.

To use Level DB, we run:

npm install level

to install the package.

Then we can use it by writing:

const level = require('level')
const db = level('my-db')
db.put('name', 'level',  (err) => {
  if (err) {
    return console.log(err)
  }
  db.get('name',  (err, value) => {
    if (err) {
      return console.log(err)
    }
    console.log(`name=${value}`)
  })
})

We get the database file by calling the level function with the database file’s path.

Then we call db.put with the key and value as the first 2 arguments to save the key-value pair.

The 3rd argument is a function to get the error from the err parameter in case there is any.

The db.get method lets us get a piece of data by the key.

The first argument is the key .

The 2nd argument is the callback in the same format.

Then we should see the output:

name=level

displayed.

We can also store a bunch of key-value pairs with one operation.

For instance, we can write:

const level = require('level')
const db = level('my-db')

const ops = [
  { type: 'del', key: 'father' },
  { type: 'put', key: 'name', value: 'james smith' },
  { type: 'put', key: 'dob', value: '1990-01-01' },
  { type: 'put', key: 'spouse', value: 'jane smith' },
  { type: 'put', key: 'occupation', value: 'waiter' }
]

db.batch(ops, function (err) {
  if (err){
     return console.log(err)
  }
  console.log('Great success')
})

We have an ops array with a bunch of objects to define the operations.

The type is the type of the operation we want to do. They are the same as the method names.

The key is needed for getting the items to delete.

And the value is what we insert as the value of the key.

We can also chain the del and put methods to manipulate the key-value pairs:

const level = require('level')
const db = level('my-db')

db.batch()
  .del('father')
  .put('name', 'james smith')
  .put('dob', '1990-01-01')
  .put('spouse', 'jane smith')
  .put('occupation', 'waiter')
  .write(function () { console.log('Done!') })

We can read our data with a read stream.

For example, we can write:

const level = require('level')
const db = level('my-db')

db.put('foo', 'bar', function(error) {
  if (error){
    return console.log(error)
  }
  const stream = db.createReadStream();
  stream
    .on('data', function({ key, value }) {
      console.log(`${key}=${value}`);
    })
    .on('error', function(error) {
      console.log(error);
    })
    .on('end', function() {
      console.log('end');
    });
});

We call db.createReadStream to create the read stream.

Then we listen to the data to get the key-value pairs from the key and value properties respectively.

Conclusion

We can use the Level DB database to store key-value pairs in our app.

Categories
Node.js Basics

Node.js Basics — App Configuration

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.

Configuration

We can configure our Node app in various ways. We can use it to make our app more flexible.

JSON Files

One way to configure our Node.js app is to read the configuration from one or more JSON files.

For example, we can configure our HTTP server from a JSON file by writing:

config.json

{
  "host": "0.0.0.0",
  "port": 8000
}

index.js

const { port, host } = require('./config.json'),
  http = require('http');

http
  .createServer(function(request, response) {
  })
  .listen(port, host, function() {
    console.log('Listening on port', port, 'and host', host);
  });

We read the configuration from the config.json file.

It’ll automatically be parsed into an object when we call require with the path.

Then we can call createServer and listen methods with it.

We can store multiple configurations with it.

For example, we can write:

config.json

{
  "server": {
    "host": "0.0.0.0",
    "port": 8000
  },
  "database": {
    "host": "db1.example.com",
    "port": 27017
  }
}

index.js

const { server: { port, host } } = require('./config.json'),
  http = require('http');

http
  .createServer(function(request, response) {
  })
  .listen(port, host, function() {
    console.log('Listening on port', port, 'and host', host);
  });

We have a server and database property so that we can store the config for the HTTP server and the config.

Then we destructured the property we need for the HTTP server.

Environmental Variables

We can store our config as environment variables. This way, we can store the settings in our server’s OS or in a file and read them from there.

To do that, we can use the dotenv library.

We can install it by running:

npm i dotenv

Then we can use it by creating and .env file:

HOST=0.0.0.0
PORT=8000

index.js

const http = require('http');
require('dotenv').config()
const { PORT, HOST } = process.env;

http
  .createServer(function(request, response) {
  })
  .listen(PORT, HOST, function() {
    console.log('Listening on port', PORT, 'and host', HOST);
  });

We read the environment variables from the process.env property after we call:

require('dotenv').config()

We store the key-value pairs in the .env file and then read them with the code above into the process.env property.

Arguments

A Node app can also take in arguments.

The command-line arguments will be put into an array and assigned to the process.argv property.

It’ll contain the parts of the command split by spaces.

For example, if we run:

node server.js --port=8001

then process.argv is:

['node', 'server.js', '--port=8001']

We should set some default values if there’s no value set for the command-line argument.

For example, we can write:

const http = require('http');
const [,, PORT = 8080, HOST = '0.0.0.0'] = process.argv;
http
  .createServer(function(request, response) {
  })
  .listen(PORT, HOST, function() {
    console.log('Listening on port', PORT, 'and host', HOST);
  });

We assign the default values as we’re destructuring the command line arguments.

Conclusion

We can configure our Node app in various ways.

Methods include using JSON files, environment variables, and command-line arguments.

Categories
Node.js Basics

Node.js Basics — Error Handling

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.

Error Handling

Most nontrivial programs will run into errors at some point.

Therefore, we got to handle them so that we can keep our programming running properly.

There’re a few ways to handle errors. One way is to use try-catch .

The others include catching them at the process or domain level.

To catch errors with try-catch , we just write:

function parseJSON(input) {  
  let json;  
  try {  
    json = JSON.parse(input);  
  } catch (error) {  
    return Promise.reject("Couldn't parse JSON");  
  }  
  return Promise.resolve(json);  
}

We tried to parse JSON in our function with the JSON.parse method.

When there’s an error, we return a rejected promise with Promise.reject with the reason for rejection.

Otherwise, we return a resolved promise with the parsed json object.

We can catch errors when the uncaughtException event is emitted.

For example, we can write:

process.on('uncaughtException', function(error) {  
  logger.fatal(error);  
  logger.fatal('Fatal error encountered, exiting now');  
  process.exit(1);  
});

We call process.on to watch for the uncaughtException event.

Then we call logger.fatal to log a fatal error.

And then call process.exit with code 1 to exit the program with an error code.

We should always exit the process following an uncaught error.

This is because the app is in an unknown state when an uncaught exception is raised.

A better way to catch errors is to catch them at the domain level.

To do that, we can use the domain package.

We install it by running:

npm i domain

Then we can use it by writing:

const Domain = require('domain');    
const domain = Domain.create();  
domain.on('error', function(error) {  
  console.log('Domain error', error.message);  
});  
domain.run(function() {  
  console.log(process.domain === domain);  
  throw new Error('Error happened');  
});

This prevents us from stopping the program whenever an error occurs.

We just catch errors happening in some domains instead of catching all errors.

We run any code that may raise errors in the domain.run callback so that we can catch errors.

If we have any async code, then we can run the async code in the domain.run method’s callback.

For example, we can write:

const Domain = require('domain');  
const domain = Domain.create();  
domain.on('error', function(error) {  
  console.log('Domain error', error.message);  
});  
process.nextTick(function() {  
  domain.run(function() {  
    throw new Error('Error happened');  
  });  
});

to run the code for the nextTick callback in the domain.run ‘s callback instead.

Conclusion

There’re several ways to handle errors in a Node.js app.

One way is to use try-catch to catch the error.

Another way is to catch the error by listening to the uncaughtException event.

A better way to catch errors is to catch them by the domain.

Categories
Node.js Basics

Node.js Basics — Debugging Apps

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.

Logging with Morgan

To let us debug our Node.js apps easily, we can use the morgan package to add a logger into our app.

To install it, we run:

npm i morgan

Then we can use it by writing:

const Morgan = require('morgan'),
  Router = require('router'),
  http = require('http');
router = new Router();
router.use(Morgan('tiny'));

http.createServer(function(request, response) {
  router(request, response, function(error) {
    if (!error) {
      response.writeHead(404);
    } else {
      console.log(error.message, error.stack);
      response.writeHead(400);
    }
    response.end('n');
  });
}).listen(8000);

We add the Morgan middleware into our router by calling the router.use method with the Morgan('tiny') middleware.

Now we see logging in our app.

Now we can remove the console.log and write:

const Morgan = require('morgan'),
  Router = require('router'),
  http = require('http');
router = new Router();
router.use(Morgan('tiny'));

http.createServer(function(request, response) {
  router(request, response, function(error) {
    let info = process.versions;
    info = JSON.stringify(info);
    response.writeHead(200, {
      'Content-Type': 'application/json'
    });
    response.end(info);

  });
}).listen(8000);

and still get logging.

Alternatively, we can use the bunyan package to do the logging.

To install it, we run:

npm i bunyan

Then we can use it by writing:

const Bunyan = require('bunyan');
const logger = Bunyan.createLogger({
  name: 'example'
});
logger.info('Hello logging');

We require the bunyan package. Then we create our logger with the createLogger method.

The name is the name of the logger.

Then we use the logger.info method to log anything we want.

We can log items with other levels of logging by using various methods.

For example, we can write:

const Bunyan = require('bunyan');
const logger = Bunyan.createLogger({
  name: 'example'
});
logger.info('Hello logging');
logger.trace('Trace');
logger.debug('Debug');
logger.info('Info');
logger.warn('Warn');
logger.error('Error');
logger.fatal('Fatal');

Then we get object with various values of the level property logged to let us know the severity level of the log item.

We can also write:

const Bunyan = require('bunyan');
const logger = Bunyan.createLogger({
  name: 'example',
  level: Bunyan.TRACE

});
logger.info('Hello logging');
logger.trace('Trace');
logger.debug('Debug');
logger.info('Info');
logger.warn('Warn');
logger.error('Error');
logger.fatal('Fatal');

to set the level in when we call createLogger .

With Bunyan, we can also write the logged data to a file.

To do that, we write:

const Bunyan = require('bunyan');
const logger = Bunyan.createLogger({
  name: 'example',
  streams: [{
      level: Bunyan.INFO,
      path: './log.log'
    },
    {
      level: Bunyan.INFO,
      stream: process.stdout
    }
  ]
});
logger.info('Hello logging');
logger.trace('Trace');
logger.debug('Debug');
logger.info('Info');
logger.warn('Warn');
logger.error('Error');
logger.fatal('Fatal');

We add the streams property to add objects to specify where the log data goes.

The first entry of the streams array has the path property to specify the location to write to.

The 2nd object specifies that we write to stdout, which means we print the items on the screen.

Conclusion

We can add logging with various loggers.

We can use Morgan or Bunyan to add logging capabilities to our Node.js apps.