DEV Community

Cover image for Building a Fast and Flexible CRUD API with Node.js and MongoDB Native Drivers
jafeer shaik
jafeer shaik

Posted on

Building a Fast and Flexible CRUD API with Node.js and MongoDB Native Drivers

Using Node.js and Express with MongoDB Native Drivers: Why and How

If you’re working with Node.js and Express, you might have encountered Mongoose, a popular ODM (Object Data Modeling) library for MongoDB. While Mongoose provides a lot of helpful features, there are reasons you might choose to work directly with MongoDB's native drivers. In this post, I'll walk you through the benefits of using MongoDB native drivers and share how you can implement a simple CRUD API with them.

Why Use MongoDB Native Drivers?

  1. Performance: MongoDB native drivers offer better performance by directly interacting with MongoDB without the additional abstraction layer that Mongoose introduces. This can be particularly beneficial for high-performance applications.

  2. Flexibility: Native drivers provide greater control over your queries and data interactions. Mongoose, with its schemas and models, imposes some structure, which might not be ideal for every use case.

  3. Reduced Overhead: By using native drivers, you avoid the additional overhead of maintaining Mongoose schemas and models, which can simplify your codebase.

  4. Learning Opportunity: Working directly with native drivers helps you understand MongoDB’s operations better and can be a great learning experience.

Implementing a CRUD API with MongoDB Native Drivers

Here’s a step-by-step guide on how to set up a simple CRUD API using Node.js, Express, and MongoDB native drivers.

1. Set Up Database Connection

Create a utils/db.js file to manage the MongoDB connection:

require('dotenv').config()
const dbConfig = require('../config/db.config');
const { MongoClient } = require('mongodb');

const client = new MongoClient(dbConfig.url);

let _db;
let connectPromise;

async function connectToDb() {
    if (!connectPromise) {
        connectPromise = new Promise(async (resolve, reject) => {
            try {
                await client.connect();
                console.log('Connected to the database 🌎', client.s.options.dbName);
                _db = client.db();
                resolve(_db);
            } catch (error) {
                console.error('Error connecting to the database:', error);
                reject(error);
            }
        });
    }
    return connectPromise;
}

function getDb() {
    if (!_db) {
        throw new Error('Database not connected');
    }
    return _db;
}

function isDbConnected() {
    return Boolean(_db);
}

module.exports = { connectToDb, getDb, isDbConnected };
Enter fullscreen mode Exit fullscreen mode

2. Define Your Model

Create a models/model.js file to interact with the MongoDB collection:

const { ObjectId } = require('mongodb');
const db = require('../utils/db');

class AppModel {
    constructor($collection) {
        this.collection = null;

        (async () => {
            if (!db.isDbConnected()) {
                console.log('Waiting for the database to connect...');
                await db.connectToDb();
            }
            this.collection = db.getDb().collection($collection);
            console.log('Collection name:', $collection);
        })();
    }

    async find() {
        return await this.collection.find().toArray();
    }

    async findOne(condition = {}) {
        const result = await this.collection.findOne(condition);
        return result || 'No document Found!';
    }

    async create(data) {
        data.createdAt = new Date();
        data.updatedAt = new Date();
        let result = await this.collection.insertOne(data);
        return `${result.insertedId} Inserted successfully`;
    }

    async update(id, data) {
        let condition = await this.collection.findOne({ _id: new ObjectId(id) });
        if (condition) {
            const result = await this.collection.updateOne({ _id: new ObjectId(id) }, { $set: { ...data, updatedAt: new Date() } });
            return `${result.modifiedCount > 0} Updated successfully!`;
        } else {
            return `No document found with ${id}`;
        }
    }

    async deleteOne(id) {
        const condition = await this.collection.findOne({ _id: new ObjectId(id) });
        if (condition) {
            const result = await this.collection.deleteOne({ _id: new ObjectId(id) });
            return `${result.deletedCount > 0} Deleted successfully!`;
        } else {
            return `No document found with ${id}`;
        }
    }
}

module.exports = AppModel;
Enter fullscreen mode Exit fullscreen mode

3. Set Up Routes

Create an index.js file to define your API routes:

const express = require('express');
const router = express.Router();

const users = require('../controllers/userController');

router.get("/users", users.findAll);
router.post("/users", users.create);
router.put("/users", users.update);
router.get("/users/:id", users.findOne);
router.delete("/users/:id", users.deleteOne);

module.exports = router;
Enter fullscreen mode Exit fullscreen mode

4. Implement Controllers

Create a userController.js file to handle your CRUD operations:

const { ObjectId } = require('mongodb');
const Model = require('../models/model');

const model = new Model('users');

let userController = {
    async findAll(req, res) {
        model.find()
            .then(data => res.send(data))
            .catch(err => res.status(500).send({ message: err.message }));
    },
    async findOne(req, res) {
        let condition = { _id: new ObjectId(req.params.id) };
        model.findOne(condition)
            .then(data => res.send(data))
            .catch(err => res.status(500).send({ message: err.message }));
    },
    create(req, res) {
        let data = req.body;
        model.create(data)
            .then(data => res.send(data))
            .catch(error => res.status(500).send({ message: error.message }));
    },
    update(req, res) {
        let id = req.body._id;
        let data = req.body;
        model.update(id, data)
            .then(data => res.send(data))
            .catch(error => res.status(500).send({ message: error.message }));
    },
    deleteOne(req, res) {
        let id = new ObjectId(req.params.id);
        model.deleteOne(id)
            .then(data => res.send(data))
            .catch(error => res.status(500).send({ message: error.message }));
    }
}

module.exports = userController;
Enter fullscreen mode Exit fullscreen mode

5. Configuration

Store your MongoDB connection string in a .env file and create a db.config.js file to load it:

module.exports = {
    url: process.env.DB_CONFIG,
};
Enter fullscreen mode Exit fullscreen mode

Conclusion

Switching from Mongoose to MongoDB native drivers can be a strategic choice for certain projects, offering performance benefits and greater flexibility. The implementation provided here should give you a solid foundation to start building applications with Node.js and MongoDB native drivers.

Feel free to check out the full code on GitHub and explore more features or enhancements for your own projects!

Any further questions feel free to comment.

Happy coding! 🚀

Top comments (0)