Mongoose ODM Best Practices Part One.
Tags: mongodb, database, javascript, tutorial
Published: 2023-01-10 03:44:35

**1. A better way to connect to the database**

To connect the latest version of Mongoose with an Express app and use the separation of concern design principle, you can do the following:

Create a separate module for your database connection and Mongoose models:

```javascript
// models/index.js

const mongoose = require('mongoose');

async function connectToDatabase() {
  try {
    await mongoose.connect('mongodb://localhost/my-app', {
      useCreateIndex: true,
      useFindAndModify: false,
    });
    console.log('Successfully connected to database');
  } catch (error) {
    console.error(error);
  }
}

module.exports = {
  connectToDatabase,
};

```
Then in your Express app, require the models module and call the connectToDatabase function when the app starts:

```javascript
const express = require('express');
const models = require('./models');

const app = express();

models.connectToDatabase();

// ... your app routes and middleware go here

app.listen(3000, () => {
  console.log('Express app listening on port 3000');
});
```
This code separates the database connection and Mongoose models into a separate models module, which can be imported and used in the main Express app. This follows the separation of concern design principle, as it allows you to keep your database logic and app logic separate and modular.

**2. Use the power of Index**

In a database, an index is a data structure that improves the speed of data access operations on a table. Indexes allow the database to quickly find and retrieve specific rows of data based on the values in one or more columns.

Here is an example of how you might use an index in Mongoose, a popular ODM (Object Document Mapper) for MongoDB:

```javascript
const UserSchema = new mongoose.Schema({
  name: String,
  email: { type: String, index: true },
});

```

In this example, we are creating a new Mongoose schema for a User model with a name field and an email field. We are also adding an index to the email field by setting the index property to true. This will create an index on the email field in the underlying MongoDB collection, allowing the database to quickly find and retrieve documents based on the value of the email field.

There are several benefits to using indexes in Mongoose:

- 
**Improved performance:** Indexes can significantly improve the performance of read operations, such as queries and sorts, by allowing the database to quickly locate the desired documents.

- 
**Unique constraints:** Indexes can be used to enforce unique constraints on a field, ensuring that no two documents in a collection have the same value for that field.

 ```javascript
 const UserSchema = new mongoose.Schema({
   name: String,
   email: { type: String, unique: true },
 });
 ```
This will create a unique index on the email field in the underlying MongoDB collection, ensuring that no two documents in the users collection have the same value for the email field.

If you try to insert a document with the same value for the email field as an existing document, Mongoose will return a validation error indicating that the value is not unique.

```javascript
const user1 = await User.create({ name: 'John', email: 'john@example.com' });
// success

const user2 = await User.create({ name: 'Jane', email: 'john@example.com' });
// throws an error: "E11000 duplicate key

```

- 
**Text search:** Indexes can be used to support text search operations, allowing you to search for documents that contain specific words or phrases. You can create a text index on the fields you want to search and use the **$text operator** in your queries.

```javascript
const BlogPostSchema = new mongoose.Schema({
  title: String,
  content: String,
});

BlogPostSchema.index({ title: 'text', content: 'text' });

const BlogPost = mongoose.model('BlogPost', BlogPostSchema);

async function searchBlogPosts(searchTerm) {
  const results = await BlogPost.find({
    $text: { $search: searchTerm },
  });
  console.log(results);
}

searchBlogPosts('MongoDB');

```
This code defines a **BlogPostSchema** with a **title field** and a **content field**, and creates a text index on both fields. It then defines a BlogPost model using this schema.

The **searchBlogPosts** function demonstrates how to use the **$text operator** in a query to search for documents that contain the specified search term in either the title or content field. When the **searchBlogPosts** function is called with the search term "MongoDB", it will find all blog posts that contain the word "MongoDB" in either the title or content field.

- 
**Geospatial queries:** Indexes can be used to support geospatial queries, allowing you to find documents based on their location relative to a given point or shape.

**3. Create Relationship**

To create a **one-to-many relationship** in Mongoose, you will need to use a **ref property** to reference the related documents.

Here is an example of how you might set up a **one-to-many relationship** between a **User model** and a **Task model** in Mongoose:

```javascript
const TaskSchema = new mongoose.Schema({
  description: String,
  user: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
});

const UserSchema = new mongoose.Schema({
  name: String,
  email: String,
  tasks: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Task' }],
});

const User = mongoose.model('User', UserSchema);
const Task = mongoose.model('Task', TaskSchema);

```
In this example, we are defining a **TaskSchema** with a **user field** that _references_ a **User document**, and a **UserSchema** with a **tasks field** that is an array of references to Task documents. This sets up a **one-to-many relationship** between users and tasks, where a user can have many tasks, but each task is associated with a single user.

To create a new task for a user, you can use the populate method to retrieve the related user documents:

```javascript
const task = await Task.create({ description: 'Finish homework', user: userId });
const populatedTask = await Task.findById(task.id).populate('user');

console.log(populatedTask.user.name); // prints the name of the user associated with the task

```


Here is a more complete example of how you might set up a one-to-many relationship between a User model and a Task model in Mongoose:

```javascript
const mongoose = require('mongoose');

mongoose.connect('mongodb://localhost/my-app', {
  useNewUrlParser: true,
  useUnifiedTopology: true,
});

const TaskSchema = new mongoose.Schema({
  description: String,
  user: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
});

const UserSchema = new mongoose.Schema({
  name: String,
  email: String,
  tasks: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Task' }],
});

const User = mongoose.model('User', UserSchema);
const Task = mongoose.model('Task', TaskSchema);

async function createUserAndTask() {
  const user = await User.create({ name: 'John', email: 'john@example.com' });
  const task = await Task.create({ description: 'Finish homework', user: user._id });
  console.log(task);
}

createUserAndTask();

async function getUserAndTasks() {
  const user = await User.findOne({ name: 'John' }).populate('tasks');
  console.log(user);
}

getUserAndTasks();

```

This code defines a TaskSchema with a user field that references a User document, and a UserSchema with a tasks field that is an array of references to Task documents. It also defines User and Task models using these schemas, and creates a new user and task in the createUserAndTask function.

The getUserAndTasks function demonstrates how to use the populate method to retrieve the related task documents for a user. When the getUserAndTasks function is called, it will find the user with the name "John" and populate the tasks field with the associated task documents, resulting in a single query to the database.

To create multiple tasks for a single user in Mongoose, you can simply push the new task documents to the tasks array of the user document. Here is an example of how you might do this:

```javascript
async function createTasksForUser(userId) {
  const user = await User.findById(userId);
  const task1 = await Task.create({ description: 'Finish homework', user: user._id });
  const task2 = await Task.create({ description: 'Buy groceries', user: user._id });
  user.tasks.push(task1, task2);
  await user.save();
}

createTasksForUser(userId);

```

This code finds the user with the specified userId, creates two new tasks, and then pushes the tasks to the tasks array of the user document. It then saves the updated user document back to the database.

To retrieve all the tasks associated with a user, you can use the populate method as shown in the previous example:

```javascript
async function getTasksForUser(userId) {
  const user = await User.findById(userId).populate('tasks');
  console.log(user.tasks);
}

getTasksForUser(userId);

```

More to come in Part 2 :)