MongoDB Essentials: Fundamentals and Node.js Integration with Mongoose
Relational vs. Non-Relational Databases
Relational databases (RDBMS) are defined by structured tables with predefined relationships. Operations are conductde using SQL, and the table structure (schema) must be defined before data can be stored. These systems enforce strict constraints on data integrity.
In contrast, non-relational databases (NoSQL) offer high flexibility. They often utilize a key-value or document-oriented storage model, allowing for unstructured or semi-structured data without the need for a rigid schema upfront.
MongoDB Architecture
MongoDB is a document-oriented NoSQL database that structurally resembles relational databases but uses dynamic schemas. The core mapping is as follows:
- Database: Equivalent to a relational database.
- Collection: Equivalent to a table, but stores JSON-like documents.
- Document: Equivalent to a row or record, composed of key-value pairs.
Conceptually, the data hierarchy is structured like this:
{
"DatabaseA": {
"CollectionOne": [
{ "id": 1, "role": "admin" },
{ "id": 2, "role": "user" }
]
},
"DatabaseB": {
"CollectionTwo": [
{ "sku": "A100", "price": 50 }
]
}
}
Basic MongoDB Shell Commands
To verify the installation, check the version. You can start the database daemon specifying a custom data path; otherwise, it defaults to /data/db on the root drive.
- Check Version:
mongod --version - Start Service:
mongod --dbpath /custom/path - Stop Service:
Ctrl + C - List Databases:
show dbs - Show Current Database:
db - Switch/Create Database:
use databaseName(creates the database upon inserting data) - Insert Document:
db.users.insertOne({ "name": "Alice" }) - List Collections:
show collections - Query Collection:
db.users.find()
Node.js Integration with Mongoose
Interacting with MongoDB in a Node.js environment is asynchronous. Mongoose is a popular ODM (Object Data Modeling) library that provides schema validation and a straightforward API.
Connecting to MongoDB
const mongoose = require('mongoose');
// Connect to the local MongoDB instance
mongoose.connect('mongodb://localhost:27017/myapp')
.then(() => console.log('Connection successful'))
.catch(err => console.error('Connection failed:', err));
Defining a Schema
Schemas define the structure of the documents within a collection. Mongoose will automatically create a pluralized collection name based on the model name (e.g., Article becomes articles).
const Schema = mongoose.Schema;
// Define the schema
const articleSchema = new Schema({
title: { type: String, required: true },
author: String,
content: String,
tags: [String],
publishedAt: { type: Date, default: Date.now },
isDraft: { type: Boolean, default: true },
metadata: {
views: { type: Number, default: 0 },
likes: Number
}
});
// Create the model
const Article = mongoose.model('Article', articleSchema);
CRUD Operations using Async/Await
Inserting Data
async function createEntry() {
try {
const newPost = new Article({
title: 'Understanding Mongoose',
author: 'DevOps',
content: 'Mongoose simplifies interactions with MongoDB.',
tags: ['database', 'nodejs']
});
const savedPost = await newPost.save();
console.log('Document saved:', savedPost);
} catch (error) {
console.error('Error saving document:', error);
}
}
Querying Data
async function queryData() {
try {
// Find all documents
const allArticles = await Article.find();
// Find with specific criteria
const specificArticle = await Article.findOne({ title: 'Understanding Mongoose' });
console.log('Query results:', specificArticle);
} catch (error) {
console.error('Query error:', error);
}
}
Deleting Data
async function removeData() {
try {
// Delete one document matching the condition
const deletionResult = await Article.deleteOne({ title: 'Understanding Mongoose' });
console.log('Deletion count:', deletionResult.deletedCount);
} catch (error) {
console.error('Deletion error:', error);
}
}
Updating Data
async function updateData(id) {
try {
// Update a document by ID
const updatedArticle = await Article.findByIdAndUpdate(
id,
{ $set: { isDraft: false } },
{ new: true } // Return the updated document
);
console.log('Updated article:', updatedArticle);
} catch (error) {
console.error('Update error:', error);
}
}
Handling Asynchronous Logic
Using modern async/await syntax is generally preferred over callback chains for better readability. Here is an example of checking for user existence and registering a new one if they do not exist.
async function registerUser(userData) {
try {
// Check if user exists
const existingUser = await User.findOne({ username: userData.username });
if (existingUser) {
console.log('Username is already taken.');
return;
}
// Create and save new user
const newUser = new User(userData);
await newUser.save();
console.log('User registered successfully');
} catch (error) {
console.error('Registration failed:', error);
}
}
// Usage
registerUser({
username: 'jdoe',
password: 'securePassword123',
email: 'jdoe@example.com'
});