Building a CRUD Application with Node.js, Express, and MongoDB

Manthan Ankolekar - Jun 4 - - Dev Community

In this blog, we will build a simple CRUD (Create, Read, Update, Delete) application using Node.js, Express, and MongoDB. CRUD applications are fundamental in web development and provide a solid foundation for understanding how to interact with databases.

Project Setup

Step 1: Setting Up the Project

First, create a new directory for your project and initialize it with npm:

mkdir crud-nodejs
cd crud-nodejs
npm init -y
Enter fullscreen mode Exit fullscreen mode

Next, install the necessary dependencies:

npm install express mongoose cors dotenv body-parser
npm install --save-dev nodemon
Enter fullscreen mode Exit fullscreen mode

Create the following project structure:

crud-nodejs
├── config
│   └── database.js
├── controllers
│   └── todoController.js
├── middleware
│   └── errorMiddleware.js
├── models
│   └── todo.js
├── routes
│   └── todoRoutes.js
├── .env.example
├── index.js
└── package.json
Enter fullscreen mode Exit fullscreen mode

Step 2: Configuring Environment Variables

Create a .env file (copy from .env.example):

cp .env.example .env
Enter fullscreen mode Exit fullscreen mode

Fill in your MongoDB credentials in the .env file:

PORT=3000
CLIENT_URL=http://localhost:3000
MONGODB_USER=your_mongodb_user
MONGODB_PASSWORD=your_mongodb_password
MONGODB_DBNAME=crud
Enter fullscreen mode Exit fullscreen mode

Step 3: Connecting to MongoDB

In config/database.js, we set up the MongoDB connection using Mongoose:

const mongoose = require('mongoose');
require('dotenv').config();

const dbUser = process.env.MONGODB_USER;
const dbPassword = process.env.MONGODB_PASSWORD;
const dbName = process.env.MONGODB_DBNAME || 'crud';

const mongoURI = `mongodb+srv://${dbUser}:${dbPassword}@cluster0.re3ha3x.mongodb.net/${dbName}?retryWrites=true&w=majority`;

module.exports = async function connectDB() {
    try {
        await mongoose.connect(mongoURI, {
            useNewUrlParser: true, useUnifiedTopology: true
        });
        console.log('MongoDB connected');
    } catch (error) {
        console.error('MongoDB connection failed');
        console.error(error);
    }
};
Enter fullscreen mode Exit fullscreen mode

Step 4: Creating the Express Server

In index.js, we configure the Express server and connect to MongoDB:

const express = require('express');
const cors = require("cors");
const bodyParser = require('body-parser');
const connectDB = require('./config/database');
const todoRoutes = require('./routes/todoRoutes');
const errorMiddleware = require('./middleware/errorMiddleware');

require('dotenv').config();

const app = express();
const PORT = process.env.PORT || 3000;

app.use(
    cors({
        origin: "*",
    })
);

// Middleware
app.use(bodyParser.json());

// Connect to MongoDB
connectDB();

// Routes
app.use('/todos', todoRoutes);

// Error middleware
app.use(errorMiddleware);

// Start the server
app.listen(PORT, () => {
    console.log(`Server started on port ${PORT}`);
});
Enter fullscreen mode Exit fullscreen mode

Step 5: Defining the Todo Model

In models/todo.js, we define the Todo schema using Mongoose:

const mongoose = require('mongoose');

const todoSchema = new mongoose.Schema({
    title: {
        type: String,
        required: true
    },
    completed: {
        type: Boolean,
        default: false
    }
});

module.exports = mongoose.model('Todo', todoSchema);
Enter fullscreen mode Exit fullscreen mode

Step 6: Creating the Controller

In controllers/todoController.js, we define the logic for handling CRUD operations:

const Todo = require('../models/todo');
const { validateTodo } = require('../utils/validationUtils');

exports.createTodo = async (req, res) => {
    const todo = req.body;

    if (!validateTodo(todo)) {
        return res.status(400).json({ message: 'Invalid todo object' });
    }

    try {
        const newTodo = new Todo(todo);
        await newTodo.save();
        res.status(201).json({ message: 'Todo created successfully' });
    } catch (error) {
        res.status(500).json({ message: error.message });
    }
};

exports.getAllTodos = async (req, res) => {
    try {
        const todos = await Todo.find();
        res.send(todos);
    } catch (err) {
        res.status(500).send(err);
    }
};

exports.getTodoById = async (req, res) => {
    try {
        const todo = await Todo.findById(req.params.id);
        if (!todo) return res.status(404).send('Todo not found');
        res.send(todo);
    } catch (err) {
        res.status(500).send(err);
    }
};

exports.updateTodo = async (req, res) => {
    try {
        const todo = await Todo.findByIdAndUpdate(req.params.id, req.body, { new: true });
        if (!todo) return res.status(404).send('Todo not found');
        res.status(200).send({ message: 'Todo updated successfully'});
    } catch (err) {
        res.status(400).send(err);
    }
};

exports.deleteTodo = async (req, res) => {
    try {
        const todo = await Todo.findByIdAndDelete(req.params.id);
        if (!todo) return res.status(404).send('Todo not found');
        res.status(200).json({ message: 'Todo deleted successfully' });
    } catch (err) {
        res.status(500).send(err);
    }
};
Enter fullscreen mode Exit fullscreen mode

Step 7: Defining Routes

In routes/todoRoutes.js, we set up the routes for the Todo API:

const express = require('express');
const router = express.Router();
const todoController = require('../controllers/todoController');

// Routes
router.post('/', todoController.createTodo);
router.get('/', todoController.getAllTodos);
router.get('/:id', todoController.getTodoById);
router.patch('/:id', todoController.updateTodo);
router.delete('/:id', todoController.deleteTodo);

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

Step 8: Error Handling Middleware

In middleware/errorMiddleware.js, we define a simple error handling middleware:

module.exports = function errorHandler(err, req, res, next) {
    console.error(err.stack);
    res.status(500).send('Something broke!');
};
Enter fullscreen mode Exit fullscreen mode

Step 9: Running the Application

Add the following scripts to package.json:

"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1",
  "dev": "nodemon index.js",
  "start": "node index.js"
}
Enter fullscreen mode Exit fullscreen mode

Start the application in development mode:

npm run dev
Enter fullscreen mode Exit fullscreen mode

Conclusion

You now have a fully functional CRUD application built with Node.js, Express, and MongoDB. This application allows you to create, read, update, and delete Todo items. This basic structure can be expanded and customized to fit more complex requirements. Happy coding!

Exploring the Code

Visit the GitHub repository to explore the code in detail.


. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .