Skip to content

Instantly share code, notes, and snippets.

@buraxta
Created July 17, 2024 12:02
Show Gist options
  • Save buraxta/dbf354def32000678bacaa00ce9fd210 to your computer and use it in GitHub Desktop.
Save buraxta/dbf354def32000678bacaa00ce9fd210 to your computer and use it in GitHub Desktop.
Long and detailed cheatsheet covering various aspects of Mongoos

Mongoose Cheatsheet

Table of Contents

  1. Installation
  2. Connecting to MongoDB
  3. Defining Schemas
  4. Creating Models
  5. CRUD Operations
  6. Querying
  7. Population
  8. Middleware
  9. Validation
  10. Indexing
  11. Virtuals
  12. Plugins
  13. Transactions
  14. Error Handling
  15. Best Practices

Installation

Install Mongoose in your project:

npm install mongoose

Connecting to MongoDB

Install Mongoose in your project:

const mongoose = require('mongoose');

// Connect to local MongoDB
mongoose.connect('mongodb://localhost/mydatabase', {
  useNewUrlParser: true,
  useUnifiedTopology: true
});

// Connect to MongoDB Atlas
mongoose.connect('mongodb+srv://<username>:<password>@cluster0.mongodb.net/mydatabase', {
  useNewUrlParser: true,
  useUnifiedTopology: true
});

// Handle connection events
const db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function() {
  console.log('Connected to MongoDB');
});

Defining Schemas

Schemas define the structure of documents in a collection.

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

// Basic schema
const userSchema = new Schema({
  name: String,
  email: String,
  age: Number
});

// Schema with more options
const productSchema = new Schema({
  name: {
    type: String,
    required: true,
    trim: true
  },
  price: {
    type: Number,
    min: 0
  },
  category: {
    type: String,
    enum: ['Electronics', 'Books', 'Clothing']
  },
  inStock: {
    type: Boolean,
    default: true
  },
  createdAt: {
    type: Date,
    default: Date.now
  }
});

Creating Models

Models are fancy constructors compiled from Schema definitions.

const User = mongoose.model('User', userSchema);
const Product = mongoose.model('Product', productSchema);

CRUD Operations

Create

// Create a single document
const newUser = new User({
  name: 'John Doe',
  email: '[email protected]',
  age: 30
});

newUser.save((err, user) => {
  if (err) return console.error(err);
  console.log('User saved:', user);
});

// Create multiple documents
User.create([
  { name: 'Jane Doe', email: '[email protected]', age: 25 },
  { name: 'Bob Smith', email: '[email protected]', age: 35 }
], (err, users) => {
  if (err) return console.error(err);
  console.log('Users created:', users);
});

Read

// Find all documents
User.find({}, (err, users) => {
  if (err) return console.error(err);
  console.log('All users:', users);
});

// Find documents with specific criteria
User.find({ age: { $gte: 18 } }, (err, users) => {
  if (err) return console.error(err);
  console.log('Adult users:', users);
});

// Find one document
User.findOne({ email: '[email protected]' }, (err, user) => {
  if (err) return console.error(err);
  console.log('Found user:', user);
});

// Find by ID
User.findById('5f7c3b3f9d3e2a1234567890', (err, user) => {
  if (err) return console.error(err);
  console.log('User by ID:', user);
});

Update

// Update one document
User.updateOne({ name: 'John Doe' }, { age: 31 }, (err, result) => {
  if (err) return console.error(err);
  console.log('Update result:', result);
});

// Update multiple documents
User.updateMany({ age: { $lt: 18 } }, { isMinor: true }, (err, result) => {
  if (err) return console.error(err);
  console.log('Update result:', result);
});

// Find and update
User.findOneAndUpdate(
  { email: '[email protected]' },
  { $inc: { age: 1 } },
  { new: true },
  (err, updatedUser) => {
    if (err) return console.error(err);
    console.log('Updated user:', updatedUser);
  }
);

Delete

// Delete one document
User.deleteOne({ name: 'John Doe' }, (err) => {
  if (err) return console.error(err);
  console.log('User deleted');
});

// Delete multiple documents
User.deleteMany({ age: { $lt: 18 } }, (err) => {
  if (err) return console.error(err);
  console.log('Minor users deleted');
});

// Find and delete
User.findOneAndDelete({ email: '[email protected]' }, (err, deletedUser) => {
  if (err) return console.error(err);
  console.log('Deleted user:', deletedUser);
});

Querying

Mongoose provides a rich query API.

// Basic querying
User.find({ age: { $gte: 18 } })
   .sort({ name: 1 })
   .limit(10)
   .select('name email')
   .exec((err, users) => {
     if (err) return console.error(err);
     console.log('Adult users:', users);
   });

// Chaining queries
User.find({ isActive: true })
   .where('age').gte(18).lte(65)
   .where('email').ne(null)
   .limit(50)
   .sort('-lastLogin')
   .select('name email')
   .exec((err, users) => {
     if (err) return console.error(err);
     console.log('Active adult users:', users);
   });

// Using query builders
const query = User.find({ isActive: true });
query.where('age').gte(18).lte(65);
query.where('email').ne(null);
query.limit(50).sort('-lastLogin').select('name email');
query.exec((err, users) => {
  if (err) return console.error(err);
  console.log('Active adult users:', users);
});

// Using $or operator
User.find({
  $or: [
    { age: { $lt: 18 } },
    { age: { $gt: 65 } }
  ]
}, (err, users) => {
  if (err) return console.error(err);
  console.log('Users not of working age:', users);
});

// Using regex
User.find({ name: /^John/ }, (err, users) => {
  if (err) return console.error(err);
  console.log('Users with names starting with John:', users);
});

Population

Population is the process of automatically replacing specified paths in the document with document(s) from other collection(s).

// Define schemas with references
const authorSchema = new Schema({
  name: String,
  bio: String
});

const bookSchema = new Schema({
  title: String,
  author: { type: Schema.Types.ObjectId, ref: 'Author' }
});

const Author = mongoose.model('Author', authorSchema);
const Book = mongoose.model('Book', bookSchema);

// Create an author and a book
const author = new Author({ name: 'John Doe', bio: 'A prolific writer' });
author.save((err, savedAuthor) => {
  if (err) return console.error(err);
  
  const book = new Book({ title: 'My Great Novel', author: savedAuthor._id });
  book.save((err, savedBook) => {
    if (err) return console.error(err);
    console.log('Book saved:', savedBook);
  });
});

// Populate the author when querying for books
Book.findOne({ title: 'My Great Novel' })
    .populate('author')
    .exec((err, book) => {
      if (err) return console.error(err);
      console.log('Book with author details:', book);
    });

// Populate specific fields
Book.findOne({ title: 'My Great Novel' })
    .populate('author', 'name')
    .exec((err, book) => {
      if (err) return console.error(err);
      console.log('Book with author name:', book);
    });

// Nested population
const publisherSchema = new Schema({
  name: String,
  location: String
});

const bookSchema = new Schema({
  title: String,
  author: { type: Schema.Types.ObjectId, ref: 'Author' },
  publisher: { type: Schema.Types.ObjectId, ref: 'Publisher' }
});

const Publisher = mongoose.model('Publisher', publisherSchema);
const Book = mongoose.model('Book', bookSchema);

Book.findOne({ title: 'My Great Novel' })
    .populate({
      path: 'author',
      populate: { path: 'publisher' }
    })
    .exec((err, book) => {
      if (err) return console.error(err);
      console.log('Book with author and publisher details:', book);
    });

Middleware

Middleware (pre and post hooks) are functions which are passed control during execution of asynchronous functions.

// Pre-save middleware
userSchema.pre('save', function(next) {
  // 'this' refers to the document being saved
  if (this.isModified('password')) {
    this.password = hashPassword(this.password);
  }
  next();
});

// Post-save middleware
userSchema.post('save', function(doc, next) {
  console.log('%s has been saved', doc._id);
  next();
});

// Pre-find middleware
userSchema.pre('find', function() {
  // 'this' refers to the query
  this.start = Date.now();
});

userSchema.post('find', function(result) {
  console.log('Query took %d milliseconds', Date.now() - this.start);
});

// Error handling middleware
userSchema.post('save', function(error, doc, next) {
  if (error.name === 'MongoError' && error.code === 11000) {
    next(new Error('There was a duplicate key error'));
  } else {
    next(error);
  }
});

Validation

Mongoose provides built-in and custom validators.

const userSchema = new Schema({
  name: {
    type: String,
    required: true,
    minlength: 2,
    maxlength: 50
  },
  email: {
    type: String,
    required: true,
    unique: true,
    lowercase: true,
    validate: {
      validator: function(v) {
        return /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(v);
      },
      message: props => `${props.value} is not a valid email address!`
    }
  },
  age: {
    type: Number,
    min: [18, 'Must be at least 18 years old'],
    max: [120, 'Age seems unrealistic']
  },
  interests: {
    type: [String],
    validate: {
      validator: function(v) {
        return v && v.length > 0;
      },
      message: 'A user must have at least one interest'
    }
  }
});

// Custom async validator
userSchema.path('email').validate(async function(value) {
  const emailCount = await mongoose.models.User.countDocuments({ email: value });
  return !emailCount;
}, 'Email already exists');

// Using validate()
const user = new User({
  name: 'J',
  email: 'invalid-email',
  age: 15,
  interests: []
});

user.validate((err) => {
  if (err) console.log(err.message);
});

Indexing

Indexes support the efficient execution of queries in MongoDB.

const userSchema = new Schema({
  name: String,
  email: { type: String, unique: true },
  createdAt: Date
});

// Single field index
userSchema.index({ name: 1 });

// Compound index
userSchema.index({ name: 1, createdAt: -1 });

// Text index
userSchema.index({ name: 'text', email: 'text' });

// Geospatial index
const locationSchema = new Schema({
  name: String,
  location: {
    type: { type: String, default: 'Point' },
    coordinates: [Number]
  }
});

locationSchema.index({ location: '2dsphere' });

// Creating indexes
mongoose.connect('mongodb://localhost/test', function(error) {
  if (error) console.error(error);
  else console.log('connected');

  User.createIndexes(function(error) {
    if (error) console.error(error);
    else console.log('indexes created');
  });
});

Virtuals

Virtuals are document properties that you can get and set but that do not get persisted to MongoDB.

const personSchema = new Schema({
  firstName: String,
  lastName: String
});

// Virtual for full name
personSchema.virtual('fullName')
  .get(function() {
    return this.firstName + ' ' + this.lastName;
  })
  .set(function(v) {
    this.firstName = v.substr(0, v.indexOf(' '));
    this.lastName = v.substr(v.indexOf(' ') + 1);
  });

const Person = mongoose.model('Person', personSchema);

const person = new Person({
  firstName: 'John',
  lastName: 'Doe'
});

console.log(person.fullName); // 'John Doe'

person.fullName = 'Jane Smith';
console.log(person.firstName); // 'Jane'
console.log(person.lastName);  // 'Smith'

Plugins

Plugins are a tool for reusing logic in multiple schemas.

// Define a plugin
const lastModifiedPlugin = function(schema, options) {
  schema.add({ lastMod: Date });
  
  schema.pre('save', function(next) {
    this.lastMod = new Date();
    next();
  });

  if (options && options.index) {
    schema.path('lastMod').index(options.index);
  }
}

// Use the plugin
const userSchema = new Schema({ name: String, email: String });
userSchema.plugin(lastModifiedPlugin, { index: true });

// Alternatively, apply the plugin to all schemas
mongoose.plugin(lastModifiedPlugin);

Transactions

Transactions let you execute multiple operations in isolation and potentially undo all the operations if one of them fails.

const session = await mongoose.startSession();
session.startTransaction();

try {
  const opts = { session };
  const A = await Account.findOne({ name: 'A' }, null, opts);
  const B = await Account.findOne({ name: 'B' }, null, opts);

  A.balance -= 100;
  B.balance += 100;

  await A.save(opts);
  await B.save(opts);

  await session.commitTransaction();
  session.endSession();
} catch (error) {
  await session.abortTransaction();
  session.endSession();
  throw error;
}

Error Handling

Proper error handling is crucial for robust applications. Here are some ways to handle errors in Mongoose:

// Using callbacks
User.findById(id, (err, user) => {
  if (err) {
    if (err.name === 'CastError') {
      return console.error('Invalid ID');
    }
    return console.error(err);
  }
  console.log(user);
});

// Using promises
User.findById(id)
  .then(user => {
    console.log(user);
  })
  .catch(err => {
    if (err.name === 'CastError') {
      console.error('Invalid ID');
    } else {
      console.error(err);
    }
  });

// Using async/await
async function findUser(id) {
  try {
    const user = await User.findById(id);
    console.log(user);
  } catch (err) {
    if (err.name === 'CastError') {
      console.error('Invalid ID');
    } else {
      console.error(err);
    }
  }
}

// Global error handler for Mongoose
mongoose.connection.on('error', err => {
  console.error('MongoDB connection error:', err);
});

// Custom error handling middleware (for Express.js)
app.use((err, req, res, next) => {
  if (err instanceof mongoose.Error.ValidationError) {
    return res.status(400).json({ error: err.message });
  }
  if (err.name === 'MongoError' && err.code === 11000) {
    return res.status(409).json({ error: 'Duplicate key error' });
  }
  next(err);
});

Best Practices

Here are some best practices to follow when using Mongoose:

  1. Use Schemas: Always define schemas for your models. This ensures data consistency and allows for validation.
const userSchema = new Schema({
  name: { type: String, required: true },
  email: { type: String, required: true, unique: true },
  age: { type: Number, min: 18 }
});
  1. Validation: Use built-in and custom validators to ensure data integrity.
userSchema.path('email').validate(function(email) {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}, 'Invalid email format');
  1. Middleware: Use middleware for repetitive tasks like data transformation or logging.
userSchema.pre('save', function(next) {
  this.updatedAt = Date.now();
  next();
});
  1. Indexing: Create indexes for frequently queried fields to improve performance.
userSchema.index({ email: 1 }, { unique: true });
  1. Lean Queries: Use .lean() for read-only operations to get plain JavaScript objects instead of Mongoose documents.
User.find().lean().exec((err, users) => {
  console.log(users);  // Plain JavaScript objects
});
  1. Projections: Use projections to select only the fields you need.
User.find({}, 'name email', (err, users) => {
  console.log(users);  // Only name and email fields
});
  1. Population: Use population to work with references efficiently.
Post.find().populate('author').exec((err, posts) => {
  console.log(posts);  // Posts with author details
});
  1. Error Handling: Always handle potential errors, especially in production environments.
User.findById(id)
  .then(user => {
    // Handle success
  })
  .catch(err => {
    // Handle error
  });
  1. Connections: Manage database connections properly.
mongoose.connect(uri, { useNewUrlParser: true, useUnifiedTopology: true })
  .then(() => console.log('Connected to MongoDB'))
  .catch(err => console.error('Could not connect to MongoDB', err));
  1. Transactions: Use transactions for operations that require atomicity.
const session = await mongoose.startSession();
session.startTransaction();
try {
  // Perform operations
  await session.commitTransaction();
} catch (error) {
  await session.abortTransaction();
} finally {
  session.endSession();
}

Remember, these are general guidelines. The best practices for your specific application may vary depending on your requirements and use case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment