Introduction:
In the dynamic landscape of web development, creating robust and scalable applications requires the harmonious integration of powerful tools and technologies. One such pairing that has become synonymous with building feature-rich web applications is Express.js and MongoDB. Express.js, a minimalist and flexible Node.js web application framework, provides a solid foundation for constructing server-side applications. MongoDB, a NoSQL database, complements Express by offering a schema-less and scalable data storage solution.
Together, they form a potent stack that empowers developers to build applications that are not only performant but also highly adaptable to evolving requirements. In this article, we will delve into the seamless integration of Express.js with MongoDB, exploring how this combination unlocks the potential to create modern, data-driven web applications. From setting up the environment to performing CRUD (Create, Read, Update, Delete) operations, join us on a journey that showcases the synergy between Express.js and MongoDB, illustrating how this combination is reshaping the way developers approach server-side development. Let’s embark on a quest to harness the power of Express.js and MongoDB for building scalable and data-centric web applications.
Module:Employee:
we’ll explore the development of an “Employee Module” — a crucial component in many enterprise applications. The Employee Module is responsible for managing employee-related data, providing functionalities such as adding new employees, updating their information, deleting records, fetching individual employee details, and listing all employees within the organization.
The key to a powerful Employee Module lies not only in its features but also in the backend infrastructure supporting it. MongoDB, with its document-oriented nature, offers a seamless solution for storing and retrieving data. We will delve into the integration of MongoDB with our Node.js application to create a robust and performant Employee Module.
Github Repository:
https://github.com/KtreeOpenSource/expressExamples/tree/main/MongoDB
Installation:
npm install mongodb
https://www.npmjs.com/package/mongodb
Connection :
MongoDB Driver Import:
const { MongoClient, ServerApiVersion } = require(‘mongodb’);
Imports the necessary modules from the mongodb package. MongoClient is the official MongoDB driver for Node.js, and ServerApiVersion is used to specify the version of the MongoDB Server API.
Environment Variables:
const pw = process.env.MONGO_PW;
const un = process.env.MONGO_UN;
const host = process.env.MONGO_HOST;
Fetch the MongoDB connection details from environment variables. This is a common practice to keep sensitive information like database credentials separate from the codebase.
Connection URI:
const uri = `mongodb+srv://${un}:${pw}@${host}/?retryWrites=true&w=majority`;
Construct the connection URI for MongoDB. It includes the username (un), password (pw), and host information fetched from environment variables. The URI specifies the protocol (mongodb+srv), the credentials, and additional connection options like retryWrites and w=majority.
MongoDB Client Initialization:
const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true, serverApi: ServerApiVersion.v1 });
Create a new MongoDB client instance. It takes the connection URI (uri) and an options object as parameters. The options include useNewUrlParser and useUnifiedTopology to ensure compatibility with the latest MongoDB driver features. The serverApi option is set to use version 1 of the MongoDB Server API.
Connect to MongoDB Server:
client.connect();
Initiate the connection to the MongoDB server using the client instance.
Database Name:
const DB_NAME = "dbname";
Define a constant variable DB_NAME representing the name of the MongoDB database you want to connect to.
Export Database Connection:
exports.db = client.db(DB_NAME);
Export the connected database instance using the db property. This exported object can be used elsewhere in your code to interact with the MongoDB database.
In summary, this code sets up a connection to a MongoDB database using the official MongoDB driver for Node.js. It securely retrieves the connection details from environment variables, constructs a connection URI, initializes a MongoDB client, connects to the server, and exports the connected database for use in other parts of your application.
Routing:
Importing Dependencies:
let express = require(‘express’);
let router = express.Router();
Here, the express module is imported to create an instance of an Express router. The router variable will be used to define and organize routes related to the “Employee” entity.
Importing Crud Operations
let { create, get, update, deleteEmployee } = require(‘./index’);
It imports functions (create, get, update, and deleteEmployee) responsible for handling specific CRUD operations. These functions are expected to be defined in a file named index.js located in the same directory.
Defining Routes
router.post(‘/’, (req,res) => create(req.body,res));
router.get(‘/’, (req,res) => get(req.query.id,res));
router.patch(‘/’, (req,res) => update(req.query.id, req.body,res));
router.delete(‘/’, (req,res) => deleteEmployee(req.query.id,res));
POST Route (/):
Path: /
Method: POST
Action: Calls the create function with the request body and response objects.
GET Route (/):
Path: /
Method: GET
Action: Calls the get function with the employee ID from the query parameters (req.query.id) and the response object.
PATCH Route (/):
Path: /
Method: PATCH
Action: Calls the update function with the employee ID from the query parameters and the request body for updates, along with the response object.
DELETE Route (/):
Path: /
Method: DELETE
Action: Calls the deleteEmployee function with the employee ID from the query parameters and the response object.
Exporting the router
module.exports = router;
The module exports the configured router so that it can be used in other parts of the application. This modular approach allows you to organize and reuse your route configurations effectively.
With this setup, when this router is mounted in your main Express application using app.use(‘/employees’, employeeRouter), it will handle routes like /employees for creating, getting, updating, and deleting employee records. The actual logic for these operations is expected to be defined in the index.js file.
Handling CRUD Operations:
Handling CRUD operations related to an “Employee” entity we used the @hapi/joi library for data validation and interacts with a repository (repo) to perform these operations.
Importing Dependencies:
const Joi = require(‘@hapi/joi’);
const repo = require(‘./repo’);
Joi: It’s a powerful validation library (@hapi/joi) used to define the schema for data validation.
repo: It’s likely a module that encapsulates the data access logic for the “Employee” entity.
Defining Schema:
const schema = Joi.object({
_id: Joi.string(),
firstName: Joi.string(),
lastName: Joi.string(),
businessId: Joi.string(),
email: Joi.string(),
phoneNumber: Joi.string(),
role: Joi.string(),
});
A Joi schema is defined to validate the structure of the data for creating and updating an employee.
Exported Functions:
Create Function:
exports.create = async (data, res) => {
schema.validate(data);
const response = await repo.create(data);
return res.json(response);
}
The create function takes employee data and a response object.
It validates the data against the defined schema using Joi.
Calls the create function from the repository (repo) to create a new employee.
Sends the JSON response back to the client.
Get Function:
exports.get = async (id, res) => {
const response = await repo.getById(id);
return res.json(response);
}
The get function takes an employee ID and a response object.
Calls the getById function from the repository (repo) to fetch employee details.
Sends the JSON response back to the client.
Update Function:
exports.update = async (id, data, res) => {
schema.validate(data);
const response = await repo.update(id, data);
return res.json(response);
}
The update function takes an employee ID, data for update, and a response object.
Validates the data against the defined schema.
Calls the update function from the repository (repo) to update an existing employee.
Sends the JSON response back to the client.
Delete Function:
exports.deleteEmployee = async (id, res) => {
const response = await repo.deleteEmployee(id);
return res.json(response);
}
The deleteEmployee function takes an employee ID and a response object.
Calls the deleteEmployee function from the repository (repo) to delete an employee.
Sends the JSON response back to the client.
This module follows a modular structure, separating concerns related to validation (Joi schema) and data access (repo). It’s designed to be used in an Express.js application, where these functions would be associated with specific routes for handling CRUD operations on employee entities.
Interaction with MongoDB
Interacting with a MongoDB collection named “employee”. It provides functions for creating, retrieving, updating, and deleting employee records. Let’s break down each part:
Importing Dependencies:
let mongo = require(‘../mongodb’);
let { ObjectId } = require(‘mongodb’);
let COLLECTION_NAME = "employee";
mongo: It’s likely a module that encapsulates MongoDB connection logic.
ObjectId: It’s used to convert string IDs to MongoDB ObjectId.
Exported Functions:
Create Function:
exports.create = async (data) => {
let response = await mongo?.db?.collection(COLLECTION_NAME)?.insertOne({
…data,
…{ createdDate: new Date().getTime() }
});
let employeeId = response.insertedId.toString();
return { _id: employeeId };
}
Async Function:
The exports.create function is an asynchronous function, indicated by the async keyword. This means it can handle asynchronous operations using the await keyword.
Optional Chaining:
The optional chaining operator (?.) is used to safely access nested properties. In this case, it’s used to access mongo, mongo.db, and mongo.db.collection(COLLECTION_NAME) without causing errors if any of these properties are undefined.
MongoDB Insert Operation:
The code is using the MongoDB insertOne method to insert a document into a collection. The document being inserted is a combination of the data parameter and a new field createdDate, which is set to the current timestamp using new Date().getTime().
Response Handling:
The result of the insertOne operation is stored in the response variable. This object contains information about the operation, and in this context, it includes the insertedId, which is the unique identifier of the inserted document.
Conversion to String:
The insertedId is then converted to a string using the toString() method. This is likely done to ensure consistency or compatibility, as MongoDB ObjectIds are typically represented as strings.
Return Statement:
The function returns an object containing the employee ID under the key _id. This object will be useful for informing the caller about the success of the operation and providing the ID of the newly created employee.
Function is used for creating a new document in a MongoDB collection. It takes a data parameter, inserts a document with additional timestamp information, retrieves the inserted ID, converts it to a string, and then returns an object with the employee ID.
GetById Function:
exports.getById = async (id) => {
return (await mongo?.db?.collection(COLLECTION_NAME)?.aggregate([
{
$match: { _id: new ObjectId(id) }
},
{
$project: {
"_id": 1,
"firstName": 1,
"lastName": 1,
"phoneNumber": 1,
"email": 1,
"role": 1,
"createdDate": 1
}
}
]).toArray())[0];
}
Async Function:
The exports.getById function is an asynchronous function, allowing for the use of await in order to handle asynchronous operations.
Optional Chaining:
Similar to the previous code snippet, optional chaining (?.) is used to safely access nested properties (mongo, mongo.db, and mongo.db.collection(COLLECTION_NAME)) without causing errors if any of these properties are undefined.
MongoDB Aggregation Pipeline:
The function uses MongoDB’s aggregation framework with the $match stage to filter documents based on the provided id. The ObjectId constructor is used to convert the id parameter into the BSON ObjectId type, which is required for matching against MongoDB _id fields.
$project Stage:
The $project stage is utilized to shape the output of the aggregation. It specifies the fields to include in the result, setting them to 1 to indicate inclusion.
toArray() Method:
The toArray() method is called to convert the result of the aggregation pipeline into an array. This is necessary for further manipulation and accessing individual documents.
Array Indexing:
The result is wrapped in an array indexing [0] to access the first (and presumably the only) document in the array.
Return Statement:
The function returns the document with the specified fields. If no matching document is found, it returns undefined.
Function is used for retrieving a document from a MongoDB collection based on its ID. It uses the aggregation pipeline to match the document, project specific fields, converts the result to an array, and returns the first document (or undefined if no match is found).
Update Function:
exports.update = async (id, data) => {
let inputId = new ObjectId(id);
data._id = inputId;
let d = await mongo?.db?.collection(COLLECTION_NAME)?.updateOne(
{ _id: inputId },
{
$set: {
firstName: data.firstName,
lastName: data.lastName,
role: data.role,
phoneNumber: data.phoneNumber,
email: data.email,
}
}, { upsert: true });
return d;
}
Async Function:
The exports.update function is an asynchronous function, allowing for the use of await in order to handle asynchronous operations.
ObjectId Conversion:
The provided id parameter is converted to a BSON ObjectId using new ObjectId(id). This is necessary for matching against MongoDB _id fields.
Updating _id Property:
The _id property of the data object is set to the converted inputId. This ensures that the provided id is consistently used as the document’s _id.
Optional Chaining:
Similar to previous examples, optional chaining (?.) is used to safely access nested properties (mongo, mongo.db, and mongo.db.collection(COLLECTION_NAME)) without causing errors if any of these properties are undefined.
MongoDB Update Operation:
The code uses the updateOne method to update a document in the MongoDB collection. The $set operator is employed to update specific fields with values from the data object.
Upsert Option:
The { upsert: true } option is set, allowing for the creation of a new document if no match is found based on the provided _id.
Return Statement:
The function returns the result of the update operation, which includes information about the number of matched documents, modified documents, etc.
Function is used for updating a document in a MongoDB collection based on its ID. It converts the ID, sets the _id property in the data, updates specific fields using the $set operator, and allows for upserting a new document if no match is found. The result of the update operation is then returned.
DeleteEmployee Function:
exports.deleteEmployee = async(id) => {
return await mongo?.db?.collection(COLLECTION_NAME).deleteOne({ _id: new ObjectId(id) });
}
Async Function:
The exports.deleteEmployee function is an asynchronous function, allowing for the use of await in order to handle asynchronous operations.
ObjectId Conversion:
The provided id parameter is converted to a BSON ObjectId using new ObjectId(id). This is necessary for matching against MongoDB _id fields.
Optional Chaining:
Similar to previous examples, optional chaining (?.) is used to safely access nested properties (mongo, mongo.db, and mongo.db.collection(COLLECTION_NAME)) without causing errors if any of these properties are undefined.
MongoDB Delete Operation:
The code uses the deleteOne method to delete a document from the MongoDB collection based on the provided ID.
Return Statement:
The function returns the result of the delete operation, which includes information about the number of deleted documents.
Function is for deleting a document from a MongoDB collection based on its ID. It converts the ID, specifies the document to delete using the converted ID, performs the delete operation, and then returns the result of the delete operation.