HVRDHVRD
Databases

Mongoose

Comprehensive guide to using Mongoose for MongoDB in Node.js, covering schema design, model creation, connection handling, and usage in a simple backend.

Mongoose is a powerful Object Data Modeling (ODM) library for MongoDB and Node.js. It provides a straight-forward, schema-based solution to model your application data, manage data validation, build relationships between data, and simplify querying.

Why Use Mongoose?

  • Enforces a schema structure on MongoDB documents
  • Provides built-in data validation
  • Simplifies interaction with MongoDB via convenient model methods
  • Handles connection management
  • Supports middleware (pre/post hooks) for models

Installation

Install Mongoose using npm:

npm install mongoose

Connecting to MongoDB

To connect to a MongoDB database using Mongoose, follow these steps:

  1. Import mongoose
  2. Use mongoose.connect(uri, options) to establish the connection
  3. Handle success and error events

Example: Connecting to MongoDB

const mongoose = require('mongoose');

const mongoURI = 'mongodb://localhost:27017/my_database';

mongoose.connect(mongoURI, {
  useNewUrlParser: true,
  useUnifiedTopology: true,
})
.then(() => {
  console.log('Successfully connected to MongoDB');
})
.catch((err) => {
  console.error('Error connecting to MongoDB:', err);
});

Easy Schema vs Hard Schema

Easy Schema

An Easy Schema is a flexible schema definition where you don’t explicitly define all fields up front. It allows storing arbitrary data, letting documents have different fields. This offers more flexibility at the expense of structure and validation.

Example of Easy Schema:

const flexibleSchema = new mongoose.Schema({}, { strict: false });
const FlexibleModel = mongoose.model('Flexible', flexibleSchema);

const doc = new FlexibleModel({
  anyField: 'value',
  anotherField: 42,
});

doc.save()
  .then(() => console.log('Flexible document saved'))
  .catch((err) => console.error(err));

Advantages:

  • Quick to prototype
  • Accepts any data structure

Disadvantages:

  • No enforced structure
  • Higher risk of inconsistent data

Hard Schema

A Hard Schema explicitly defines all fields, their types, validation rules, and constraints. This ensures strict data integrity and structure.

Example of Hard Schema:

const strictSchema = new mongoose.Schema({
  name: { type: String, required: true },
  email: { type: String, required: true, unique: true },
  age: { type: Number, min: 0 },
}, { timestamps: true });

const StrictModel = mongoose.model('StrictUser', strictSchema);

const user = new StrictModel({
  name: 'Harsha',
  email: 'harsha@example.com',
  age: 25,
});

user.save()
  .then(() => console.log('Strict document saved'))
  .catch((err) => console.error(err));

Advantages:

  • Enforced structure and validation
  • Data consistency guaranteed
  • Easier maintenance and debugging

Disadvantages:

  • Requires upfront schema design
  • Less flexibility for unknown data shapes

Defining a Schema and Model

A Schema defines the structure of documents in a MongoDB collection, while a Model provides an interface for interacting with the database.

Example: User Schema and Model

const { Schema, model } = require('mongoose');

// Define the User schema
const userSchema = new Schema({
  name: {
    type: String,
    required: true,
    trim: true,
  },
  email: {
    type: String,
    required: true,
    unique: true,
    lowercase: true,
  },
  age: {
    type: Number,
    min: 0,
  },
}, {
  timestamps: true, // Automatically add createdAt and updatedAt
});

// Create the User model
const User = model('User', userSchema);

Basic CRUD Operations

Create a New User

const newUser = new User({
  name: 'Harsha',
  email: 'harsha@example.com',
  age: 25,
});

newUser.save()
  .then((user) => console.log('User saved:', user))
  .catch((err) => console.error('Error saving user:', err));

Find Users

User.find()
  .then((users) => console.log('All users:', users))
  .catch((err) => console.error('Error fetching users:', err));

Complete Example: Simple Backend Connecting to MongoDB

const express = require('express');
const mongoose = require('mongoose');

const app = express();
app.use(express.json());

const mongoURI = 'mongodb://localhost:27017/my_database';

mongoose.connect(mongoURI, {
  useNewUrlParser: true,
  useUnifiedTopology: true,
})
.then(() => console.log('MongoDB connected'))
.catch((err) => console.error('MongoDB connection error:', err));

// User schema and model
const userSchema = new mongoose.Schema({
  name: String,
  email: String,
  age: Number,
}, {
  timestamps: true,
});

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

// API Endpoint to create a user
app.post('/users', async (req, res) => {
  try {
    const user = new User(req.body);
    await user.save();
    res.status(201).json(user);
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});

// API Endpoint to get all users
app.get('/users', async (req, res) => {
  try {
    const users = await User.find();
    res.json(users);
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

const PORT = 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

Best Practices

  • Always use environment variables to store sensitive information like the MongoDB URI.
  • Handle connection errors gracefully and implement retries if necessary.
  • Validate input data using Mongoose schema validation or an additional library like Joi.
  • Use timestamps in schemas to track creation and update times.
  • Take advantage of Mongoose middleware for things like hashing passwords before saving.