Connecting to MongoDB with Mongoose · learncode.live

What is Mongoose?

Mongoose is an ODM (Object Document Mapping) library for MongoDB and Node.js. It provides a schema-based solution to model your data with built-in validation, query building, and middleware (hooks).

Express App              Mongoose                MongoDB
    │                       │                       │
    │  User.find({})        │                       │
    │──────────────────────→│                       │
    │                       │  find()               │
    │                       │──────────────────────→│
    │                       │                       │
    │                       │←────── documents ─────│
    │←─── Model instances ──│                       │
    │                       │                       │

Installation

npm install mongoose

Connecting to MongoDB

const mongoose = require('mongoose');

const MONGO_URI = process.env.MONGO_URI || 'mongodb://localhost:27017/myapp';

mongoose.connect(MONGO_URI)
  .then(() => console.log('Connected to MongoDB'))
  .catch(err => console.error('MongoDB connection error:', err));

Connection Options

mongoose.connect(MONGO_URI, {
  serverSelectionTimeoutMS: 5000,  // Timeout after 5s
  socketTimeoutMS: 45000,          // Close sockets after 45s of inactivity
  family: 4                        // Use IPv4, skip IPv6
});

Best Practice: Reusable Connection Module

config/database.js:

const mongoose = require('mongoose');

async function connectDB() {
  const uri = process.env.MONGO_URI || 'mongodb://localhost:27017/myapp';

  try {
    await mongoose.connect(uri);
    console.log(`MongoDB connected: ${mongoose.connection.host}`);
  } catch (err) {
    console.error('MongoDB connection failed:', err.message);
    process.exit(1);
  }
}

module.exports = connectDB;

index.js:

const express = require('express');
const connectDB = require('./config/database');

const app = express();

// Connect to database before starting server
connectDB();

app.listen(3000);

Connection Events

mongoose.connection.on('connected', () => {
  console.log('Mongoose connected to DB');
});

mongoose.connection.on('error', (err) => {
  console.error('Mongoose connection error:', err);
});

mongoose.connection.on('disconnected', () => {
  console.log('Mongoose disconnected');
});

// Graceful shutdown
process.on('SIGINT', async () => {
  await mongoose.connection.close();
  process.exit(0);
});

Defining a Schema

A schema defines the structure of documents in a collection.

const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
  name: {
    type: String,
    required: [true, 'Name is required'],
    trim: true,
    minlength: [2, 'Name must be at least 2 characters'],
    maxlength: [50, 'Name cannot exceed 50 characters']
  },
  email: {
    type: String,
    required: [true, 'Email is required'],
    unique: true,
    lowercase: true,
    match: [/^\S+@\S+\.\S+$/, 'Please provide a valid email']
  },
  age: {
    type: Number,
    min: [13, 'Must be at least 13 years old'],
    max: [120, 'Age cannot exceed 120']
  },
  role: {
    type: String,
    enum: ['user', 'admin', 'moderator'],
    default: 'user'
  },
  isActive: {
    type: Boolean,
    default: true
  },
  createdAt: {
    type: Date,
    default: Date.now
  }
});

module.exports = mongoose.model('User', userSchema);

Schema Types

TypeDescription
StringUTF-8 string
Number64-bit float
Booleantrue/false
DateDate object
BufferBinary data
ObjectIdUnique MongoDB identifier
ArrayNested array
MixedAny type (no validation)
MapKey-value pairs
Decimal128High-precision decimal
Schema.Types.ObjectIdReference to another document

Creating a Model

A model is a compiled version of the schema that provides an interface to the database.

const User = mongoose.model('User', userSchema);
// Now you can do: User.find(), User.create(), etc.

CRUD Operations with Mongoose

Create

// Method 1: Create and save
const user = new User({
  name: 'Alice',
  email: 'alice@example.com',
  age: 30
});
await user.save();

// Method 2: Create directly
const user = await User.create({
  name: 'Bob',
  email: 'bob@example.com',
  age: 25
});

Read

// Find all
const users = await User.find();

// Find with filters
const adults = await User.find({ age: { $gte: 18 } });

// Find one
const user = await User.findOne({ email: 'alice@example.com' });

// Find by ID
const user = await User.findById('507f1f77bcf86cd799439011');

// Select specific fields
const users = await User.find().select('name email');

// Sort, limit, skip (pagination)
const users = await User.find()
  .sort({ createdAt: -1 })
  .limit(10)
  .skip(20);

Update

// Find and update (returns old document by default)
const user = await User.findByIdAndUpdate(
  id,
  { name: 'Updated Name' },
  { new: true, runValidators: true }
);

// Update one (returns update result, not the document)
const result = await User.updateOne(
  { email: 'alice@example.com' },
  { $set: { age: 31 } }
);

// Update many
await User.updateMany(
  { role: 'user' },
  { $set: { isActive: true } }
);

Delete

// Find and delete
const user = await User.findByIdAndDelete(id);

// Delete one
await User.deleteOne({ email: 'old@example.com' });

// Delete many
await User.deleteMany({ isActive: false });

Schema with References (Relationships)

// models/post.js
const postSchema = new mongoose.Schema({
  title: {
    type: String,
    required: true
  },
  body: {
    type: String,
    required: true
  },
  author: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User',        // Reference to User model
    required: true
  },
  tags: [String],
  likes: {
    type: Number,
    default: 0
  },
  comments: [{
    user: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
    text: String,
    createdAt: { type: Date, default: Date.now }
  }]
}, {
  timestamps: true      // Adds createdAt and updatedAt automatically
});

module.exports = mongoose.model('Post', postSchema);

Populating References (Joins)

// Get posts with author data populated
const posts = await Post.find()
  .populate('author', 'name email')
  .populate('comments.user', 'name');

// Populate nested
const posts = await Post.find().populate({
  path: 'author',
  populate: { path: 'profile', select: 'avatar bio' }
});

Schema Middleware (Hooks)

const bcrypt = require('bcrypt');

userSchema.pre('save', async function(next) {
  // Only hash if password is modified
  if (!this.isModified('password')) return next();

  const salt = await bcrypt.genSalt(10);
  this.password = await bcrypt.hash(this.password, salt);
  next();
});

userSchema.post('save', function(doc) {
  console.log(`User ${doc.email} was saved`);
});

userSchema.pre('find', function() {
  // `this` is the query - filter out inactive by default
  this.where({ isActive: true });
});

Virtual Properties

userSchema.virtual('fullName').get(function() {
  return `${this.firstName} ${this.lastName}`;
});

userSchema.virtual('isAdult').get(function() {
  return this.age >= 18;
});

// Virtuals are not included in JSON by default
// To include them:
userSchema.set('toJSON', { virtuals: true });
userSchema.set('toObject', { virtuals: true });

Full Integration Example

const express = require('express');
const mongoose = require('mongoose');
require('./models/User');
require('./models/Post');

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

// Connect
mongoose.connect(process.env.MONGO_URI);

// Routes
const User = mongoose.model('User');

app.post('/api/users', async (req, res, next) => {
  try {
    const user = await User.create(req.body);
    res.status(201).json(user);
  } catch (err) {
    next(err);
  }
});

app.get('/api/users', async (req, res, next) => {
  try {
    const users = await User.find().select('-__v');
    res.json(users);
  } catch (err) {
    next(err);
  }
});

app.get('/api/users/:id', async (req, res, next) => {
  try {
    const user = await User.findById(req.params.id);
    if (!user) return res.status(404).json({ error: 'User not found' });
    res.json(user);
  } catch (err) {
    next(err);
  }
});

app.put('/api/users/:id', async (req, res, next) => {
  try {
    const user = await User.findByIdAndUpdate(
      req.params.id,
      req.body,
      { new: true, runValidators: true }
    );
    if (!user) return res.status(404).json({ error: 'User not found' });
    res.json(user);
  } catch (err) {
    next(err);
  }
});

app.delete('/api/users/:id', async (req, res, next) => {
  try {
    const user = await User.findByIdAndDelete(req.params.id);
    if (!user) return res.status(404).json({ error: 'User not found' });
    res.status(204).send();
  } catch (err) {
    next(err);
  }
});

app.listen(3000);

Key Takeaways

  • Mongoose provides schema-based modeling with built-in validation
  • Connect with mongoose.connect(URI) in a reusable module
  • Schemas define structure; Models provide the query interface
  • Use populate() for referencing documents in other collections
  • Middleware hooks (pre/post) run logic before/after operations
  • Virtuals compute derived properties without storing them
  • Always use try/catch or middleware for async error handling
  • Store connection string in an environment variable (MONGO_URI)
Courses