Categories
JavaScript Design Patterns

Commonly Used Design Patterns in JavaScript

Design patterns enable us to organize a program’s code in a standard way.

The book Design Patterns: Elements of Reusable Object-Oriented Software was published in 1994 and came up with 23 design patterns that are used by object-oriented programs.

In this piece, we’ll look at some of the more commonly used design patterns in JavaScript programs, including the singleton, iterator, and factory patterns.


Singleton

Singleton is a pattern that is common in JavaScript. It’s a class that only creates one instance of an object.

In JavaScript, we have the object literal to define an object that isn’t an instance of a class. For example, we have:

const obj = {  
  foo: 1  
}

We also have the class syntax, which does the same thing as constructor functions, where we can define a getInstance method to get an instance of a class.

To make a singleton class, we can write something like the following:

Since we assigned this to this.instance and return it in getInstance, we should always get the same reference.

We should get that foo1 === foo2 being true since they reference the same instance of the Foo class.

Singleton classes are useful for facade objects to hide the complexities of a program. State objects that are shared by different parts of a program also make singleton classes a good choice.

They also let us share data without creating global variables. The global scope isn’t polluted, so it’s a good choice for sharing data.


Iterator

The iterator pattern is a pattern where we create an iterator to sequentially access data in a container.

We need this pattern to traverse items in collections without exposing the underlying data structure. And we should also be able to traverse objects in an aggregate object without changing its interface.

In JavaScript, we can define iterable objects and generators to do this.

To define an iterable object, we can write:

As we can see, iterableObj hides the array that’s inside it from the outside. Also, we can change it to anything else we want and not have to worry about changing the code outside.

[Symbol.iterator] and generator functions have been available since ES6. Ever since then, we can define iterators easily.

We can also define generator functions that return generators. To do this, we can write the following:

It’s similar to iterableObj since they both use generators. The difference is that generatorFn is a generator function that returns generators. Generators are what we iterate through.


Factory

The factory pattern centers around the factory function. It’s a function that returns objects without using the new keyword to construct an instance of a class or a constructor function.

We want to use the factory pattern to make code more readable since it lets us create functions that return new objects from more code.

It also lets us return objects without knowing the code that creates the object. We don’t have to worry about what class is instantiated to create the object or how it’s created otherwise.

In JavaScript, when a function returns an object without the new keyword, then it’s a factory function.

For example, we can create a simple factory function as follows:

const createFoo = () => ({  
  foo: 1  
});

The createFoo function always returns the { foo: 1 } object when it’s called. It always returns an object without the new keyword, so it’s a factory function and uses the factory pattern.

Factory functions are common in JavaScript. For example, browsers have the document.querySelector() method to get a DOM object given the CSS selector.

There are similar methods that return objects everywhere in JavaScript.

The singleton pattern creates a single instance of an object. In JavaScript, we can do this with object literals or a getInstance method of a class that always returns the same instance of a class.

It’s useful for sharing data and hiding the complexity of implementation.

The iterator pattern lets us traverse through collections of objects without knowing the implementation of it. Also, it lets us change the underlying data structure and logic without changing the interface.

JavaScript has iterators to do this in addition to generator functions.

Finally, the factory pattern is common in JavaScript. Any function that returns a new object without using the new keyword to instantiate it is a factory function.

It’s useful for hiding the complexities of creating objects from other developers. For example, we don’t have to worry about how document.querySelector() gets an element from the DOM. It just returns the first DOM element that matches the selector.

Categories
JavaScript Basics

How to Combine Multiple Strings with JavaScript

Combining strings is something that we have to do often with JavaScript.

In this article, we’ll look at how to combine them with JavaScript.

Plus Operator

The + operator also lets us combine multiple strings together.

For instance, we can write:

const firstName = 'james',
  lastName = 'smith';

const greeting = 'hi' + ' ' + firstName + ' ' + lastName;

This is uglier than the other solutions since we have to use the + sign for each string expression.

Mutative Concatenation

We can concatenate to existing strings.

For instance, we can write:

const firstName = 'james',
  lastName = 'smith';

let greeting = 'hi'
greeting += ' ' + firstName + ' ' + lastName;

Then we concatenate to the greeting string.

We can’t use const to declare greeting since we’re assigning a new value to it.

Template Literals

Template literals are a newer string type that lets us interpolate expressions into them,.

For instance, we can write:

const firstName = 'james',
  lastName = 'smith';

const greeting = `hi ${firstName} ${lastName}`;

We have the firstName and lastName variables.

Then we get 'hi james smith’ as the value of“greeting` .

And we interpolated them into the string expression.

The ${...} is the interpolation symbol.

We can put any expression in between them.

Now we don’t have to worry about looking at ugly concatenation strings.

Array.prototype.join()

Array instances have the join method.

It takes an optional separation to combine the strings with.

If the array entries aren’t strings, then they’ll be coerced to strings.

For instance, we can use it by writing:

const firstName = 'james',
  lastName = 'smith';

const greeting = ['hi', firstName, lastName].join(' ')

We join 'hi' , firstName and lastName with a space string.

And we get the same result as with the previous example.

The separator can be any string.

String.prototype.concat()

Strings have the concat method to let us concatenate various strings into one.

For instance, we can write:

const firstName = 'james',
  lastName = 'smith';

const greeting = ''.concat('hi', ' ', firstName, ' ', lastName);

We have the concat method which has all the strings that we want to join together.

They’re 'hi' , firstName , and lastName with empty spaces in between each of them.

So we get the same result as before.

Escaping Quotes

If we want to use single quotes in single-quoted strings, double quotes with double-quotes strings, and backticks in template literals, then we’ve to escape those characters in those strings.

We can do that with a “ character.

For instance, we can write:

const str = ``hello world``;

or

const str = ''hello world'';

or

const str = ""hello world""

The “ indicates that it should be considered a character in the string rather than a string delimiter.

Conclusion

There are several ways to combine strings together in JavaScript.

Template literals and join method is convenient for joining strings together.

Categories
MongoDB

Using MongoDB with Mongoose — String, Number, and Date Schema Types

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.

String Schema Types

We can set various properties for strings schema types.

They include:

  • lowercase: boolean, whether to always call .toLowerCase() on the value
  • uppercase: boolean, whether to always call .toUpperCase() on the value
  • trim: boolean, whether to always call .trim() on the value
  • match: RegExp, creates a validator that checks if the value matches the given regular expression
  • enum: Array, creates a validator that checks if the value is in the given array.
  • minlength: Number, creates a validator that checks if the value length is not less than the given number
  • maxlength: Number, creates a validator that checks if the value length is not greater than the given number

For example, 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 schema = new mongoose.Schema({
  test: {
    type: String,
    enum: ['apple', 'orange']
  }
});

to make the test field an enum.

Number Schema Types

For number schema types, we can set the following properties for the field:

  • min: Number, creates a validator that checks if the value is greater than or equal to the given minimum.
  • max: Number, creates a validator that checks if the value is less than or equal to the given maximum.
  • enum: Array, creates a validator that checks if the value is strictly equal to one of the values in the given array.

Date Schema Types

For date schema types, we can set:

  • min: Date
  • max: Date

We can use the schema types by writing:

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 personSchema = new mongoose.Schema({
  name: String
});

const Person = mongoose.model('Person', personSchema);
const person = new Person({ name: { toString: () => 42 } });
person.save();
console.log(person.name);

We have the toString method that converts 42 into a string.

The field can also be rewritten as:

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 personSchema = new mongoose.Schema({
  name: 'String'
});

const Person = mongoose.model('Person', personSchema);
const person = new Person({ name: { toString: () => 42 } });
person.save();
console.log(person.name);

The value is 'String' instead of the String constructor.

They do the same thing.

Dates

If we have date fields, we can call various methods to change its value:

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')
});

async function run() {
  const Assignment = mongoose.model('Assignment', { dueDate: Date });
  const assignment = new Assignment({ dueDate: new Date() });
  await assignment.save();
  Assignment.findOne((err, doc) => {
    doc.dueDate.setMonth(3);
    doc.save();
    doc.markModified('dueDate');
    doc.save();
  })
}
run();

We call the setMonth method to set the month.

Conclusion

We can set the schema types with various properties when we create the schema with Mongoose.

Categories
MongoDB

Using MongoDB with Mongoose — Connections

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.

Connections

We can connect to a MongoDB database with Mongoose.

To do that, we 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')
});

We connect to the server with the URL and the test collection name.

Operation Buffering

We can start using the models immediately without waiting fir Mongoose to establish a connection.

So 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')
});

async function run() {
  const Assignment = mongoose.model('Assignment', { dueDate: Date });
  const assignment = new Assignment({ dueDate: new Date() });
  await assignment.save();
  Assignment.findOne((err, doc) => {
    doc.dueDate.setMonth(3);
    doc.save();
    doc.markModified('dueDate');
    doc.save();
  })
}
run();

We create our model and use it without waiting for the connect to complete since we have the database code outside the callback.

This is because Mongoose buffers the model function calls internally.

However, Mongoose won’t throw any errors if we use models without connecting.

We can disable buffering by setting bufferCommands to false :

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

We can create collections with the createCollection 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')
});

async function run() {
  const schema = new mongoose.Schema({
    name: String
  }, {
    capped: { size: 1024 },
    bufferCommands: false,
    autoCreate: false
  });

  const Model = mongoose.model('Test', schema);
  await Model.createCollection();
}
run();

Now we’ll create the tests collection to create the collection.

Error Handling

We can catch any errors that are raised with the catch method or try-catch with async and await since mongoose.connect returns a promise.

For example, we can write:

const mongoose = require('mongoose');
async function run() {
  try {
    await mongoose.connect('mongodb://localhost:27017/test', { useNewUrlParser: true });
  } catch (error) {
    handleError(error);
  }
}
run();

or:

const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/test', { useNewUrlParser: true }).
  catch(error => handleError(error));

to connect to a database.

We can also listen to the error event as we did in the example above.

Callback

The mongoose.connect method takes a callback to so we can check for errors.

For example, we can write:

const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/test', { useNewUrlParser: true }, function (error) {
  console.log(error);
})

Connection String Options

We can add connection options to the connection string.

For example, we can write:

const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/test?connectTimeoutMS=1000&bufferCommands=false')

We set the connection timeout with the connectTimeoutMS option is set to 1000ms to set.

And we also set bufferCommands option to false .

Conclusion

There are many things we can configure with connections.

Also, Mongoose buffers any commands queued before the connection is made by default so we don’t have to wait for a connection is done before running Mongoose methods.

Categories
MongoDB

Using MongoDB with Mongoose — SchemaTypes

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.

SchemaType

A SchemaType is a configuration object for an individual property.

For example, 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
});
console.log(kittySchema.path('name') instanceof mongoose.SchemaType);

to check that the name field in the kittySchema is an instance of mongoose.SchemaType .

It should return true , so we know that name is a SchemaType .

If we have nested fields in a schema, we also have to set the types for the nested fields:

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: { type: String },
  nested: {
    firstName: { type: String },
    lastName: { type: String }
  }
});

Schema Types

With Mongoose, we can define a few schema types with our field.

They include:

  • required: boolean or function, if true adds a required validator for this property
  • default: Any or function, sets a default value for the path. If the value is a function, the return value of the function is used as the default.
  • select: boolean, specifies default projections for queries
  • validate: function, adds a validator function for this property
  • get: function, defines a custom getter for this property using Object.defineProperty().
  • set: function, defines a custom setter for this property using Object.defineProperty().
  • alias: string, available with Mongoose >= 4.10.0 only. Defines a virtual with the given name that gets/sets this path.
  • immutable: boolean, defines path as immutable. Mongoose prevents you from changing immutable paths unless the parent document has isNew: true.
  • transform: function, Mongoose calls this function when you call Document#toJSON() function, including when you JSON.stringify() a document.

For example, 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 schema = new mongoose.Schema({
  integerOnly: {
    type: Number,
    get: v => Math.round(v),
    set: v => Math.round(v),
    alias: 'i'
  }
});

to create a schema with the integerOnly field.

We control how the value is get and set with the get and set methods respectively.

And we added an alias property to define another name we can access it with.

Indexes

We can add indexes for fields. For example, 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 schema = new mongoose.Schema({
  test: {
    type: String,
    index: true,
    unique: true
  }
});

to add the test schema with the index property set to true to add an index for the test field.

The unique property set to true will add a unique index.

Conclusion

We can specify different schema types to set the fields for the schema.