In our previous articles, we've laid the foundation of NodeJS, exploring its core concepts, including how NodeJS works, building servers, managing URLs, HTTP methods, utilizing the power of the Express framework, REST APIs, middleware, and HTTP headers. Now, we’ll take a step further by diving into databases particularly MongoDB, Models, Views, Controllers, and other essential components for building efficient and scalable backend applications.
Link to the previous articles:
- Getting Started with NodeJS
- Deepening NodeJS Knowledge: URLs, HTTP Methods, Express Framework, and Versioning
- Mastering NodeJS: REST APIs, Middleware, and HTTP Headers
Continuing our journey into NodeJS, this article explores how to integrate MongoDB with NodeJS, leverage Mongoose for seamless database interactions, and implement the MVC architecture in your projects.
Getting Started with MongoDB and NodeJS
What is MongoDB?
MongoDB is a NoSQL database that uses a flexible, document-oriented data model, allowing for easy storage of structured or semi-structured data.
-
Key Features:
- Scalability: MongoDB is designed to scale out across distributed systems.
- Flexibility: The document model allows for changes to data structure over time without needing to redefine schemas.
- Performance: Optimized for high-volume data storage and retrieval.
Why Use MongoDB?
- JSON-like Documents: MongoDB stores data in flexible, JSON-like documents, making it easy to work with and integrate with modern applications.
- Schema-less: MongoDB doesn’t require a predefined schema, allowing for dynamic changes in data structures.
- Scalability: Built to scale horizontally by distributing data across multiple servers.
- Community Support: MongoDB has a large and active community, with comprehensive documentation and a wealth of resources.
Basic MongoDB Terminologies
- Document: A record in MongoDB, similar to a row in relational databases, stored in BSON (Binary JSON) format.
- Collection: A group of documents, similar to a table in relational databases.
- Database: A container for collections.
- Index: A data structure that improves the speed of data retrieval operations.
Example: MongoDB Document
Here’s an example of a MongoDB document representing a user:
{
"_id": "507f1f77bcf86cd799439011",
"name": "John Doe",
"email": "johndoe@example.com",
"age": 29,
"created_at": "2023-08-21T15:00:00Z"
}
Connecting NodeJS with MongoDB
How to Connect NodeJS to MongoDB?
To connect NodeJS with MongoDB, we’ll use the MongoDB Node.js
driver.
Installation:
npm install mongodb
Code:
const { MongoClient } = require('mongodb');
const url = 'mongodb://localhost:27017';
const client = new MongoClient(url);
async function connectDB() {
try {
await client.connect();
console.log('Connected to MongoDB');
const db = client.db('testdb');
const collection = db.collection('users');
// Perform database operations
} catch (error) {
console.error('Error connecting to MongoDB', error);
}
}
connectDB();
What is Mongoose?
Mongoose is an Object Data Modeling (ODM) library for MongoDB and NodeJS. It provides a straightforward, schema-based solution to model application data.
Mongoose simplifies interactions with MongoDB by providing schema validation, middleware, and a convenient API for database operations.
Installation:
npm install mongoose
Code:
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/testdb', {
useNewUrlParser: true,
useUnifiedTopology: true
}).then(() => {
console.log('Connected to MongoDB using Mongoose');
}).catch(err => {
console.error('Error connecting to MongoDB', err);
});
Basic CRUD Operations with MongoDB and Mongoose
Creating a Schema and Model
Code:
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
name: String,
email: String,
age: Number,
created_at: {
type: Date,
default: Date.now
}
});
const User = mongoose.model('User', userSchema);
Create Operation (Insert Data)
Code:
async function createUser() {
const newUser = new User({
name: 'John Doe',
email: 'johndoe@example.com',
age: 29
});
const result = await newUser.save();
console.log('User Created:', result);
}
createUser();
Output:
User Created: { _id: 507f1f77bcf86cd799439011, name: 'John Doe', email: 'johndoe@example.com', age: 29, created_at: 2023-08-21T15:00:00Z }
Read Operation (Retrieve Data)
Code:
async function getUsers() {
const users = await User.find();
console.log('Users:', users);
}
getUsers();
Output:
Users: [ { _id: 507f1f77bcf86cd799439011, name: 'John Doe', email: 'johndoe@example.com', age: 29, created_at: 2023-08-21T15:00:00Z } ]
Update Operation
Code:
async function updateUser(id) {
const user = await User.findByIdAndUpdate(id, { name: 'Jane Doe' }, { new: true });
console.log('User Updated:', user);
}
updateUser('507f1f77bcf86cd799439011');
Output:
User Updated: { _id: 507f1f77bcf86cd799439011, name: 'Jane Doe', email: 'johndoe@example.com', age: 29, created_at: 2023-08-21T15:00:00Z }
Delete Operation
Code:
async function deleteUser(id) {
const result = await User.findByIdAndDelete(id);
console.log('User Deleted:', result);
}
deleteUser('507f1f77bcf86cd799439011');
Output:
User Deleted: { _id: 507f1f77bcf86cd799439011, name: 'Jane Doe', email: 'johndoe@example.com', age: 29, created_at: 2023-08-21T15:00:00Z }
Timestamps in Mongoose
Timestamps are fields that store the creation and update times of a document.
Timestamps help track when a document was created and last updated, which is essential for auditing and maintaining data integrity.
Code:
const userSchema = new mongoose.Schema({
name: String,
email: String,
age: Number
}, { timestamps: true });
Result: Automatically adds createdAt
and updatedAt
fields to the schema.
Understanding MVC Architecture
What is MVC?
MVC (Model-View-Controller) is a design pattern that separates an application into three interconnected components.
- Model: Represents the data and business logic.
- View: The user interface that displays data.
-
Controller: Handles user input and interacts with the model to update the view.
- Purpose: MVC promotes separation of concerns, making the application easier to manage, test, and scale.
Why Use MVC?
- Organized Codebase: MVC organizes your code into separate components, making it easier to maintain and scale.
- Reusability: Components in MVC can be reused across different parts of the application.
- Testability: Each component can be tested independently, improving the reliability of your application.
When to Use MVC?
- Large Applications: MVC is ideal for large-scale applications with complex business logic and a need for organized code.
- Scalability: When planning for future growth, MVC allows for easy expansion of the application.
Use Case: Project with and without MVC
Without using MVC
Let us take an example of a simple NodeJS application where all logic (database interaction, business logic, and routing) is handled in a single file.
- Code Example:
const express = require('express');
const mongoose = require('mongoose');
const app = express();
mongoose.connect('mongodb://localhost:27017/simpledb');
app.use(express.json());
app.post('/users', (req, res) => {
// Directly handle database interaction and response
const newUser = new User(req.body);
newUser.save().then(user => res.send(user));
});
app.listen(3000, () => console.log('Server running'));
-
Issues:
- Tightly Coupled Code: The application logic is tightly coupled, making it difficult to maintain or scale.
- Harder to Test: Testing individual components or logic is more challenging.
With MVC
Let us take a NodeJS application with a clear separation of concerns, following the MVC pattern.
-
Code Example:
- Model:
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
name: String,
email: String,
age: Number
});
const User = mongoose.model('User', userSchema);
- Controller:
const User = require('../models/user');
exports.createUser = async (req, res) => {
const newUser = new User(req.body);
const user = await newUser.save();
res.send(user);
};
- Route:
const express = require('express');
const userController = require('../controllers/userController');
const router = express.Router();
router.post('/users', userController.createUser);
module.exports = router;
- Main App:
const express = require('express');
const mongoose = require('mongoose');
const userRoutes = require('./routes/userRoutes');
const app = express();
mongoose.connect('mongodb://localhost:27017/mvcdb');
app.use(express.json());
app.use('/api', userRoutes);
app.listen(3000, () => console.log('Server running'));
-
Benefits:
- Separation of Concerns: Each part of the application has a clear responsibility, making the code easier to manage and scale.
- Testability: Individual components (like models and controllers) can be tested independently.