Building a CRUD Application with Node.js, Express, and MongoDB
Tags: webdev, javascript, beginners, programming
Published: 2024-06-04 07:16:00

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:

```bash
mkdir crud-nodejs
cd crud-nodejs
npm init -y
```

Next, install the necessary dependencies:

```bash
npm install express mongoose cors dotenv body-parser
npm install --save-dev nodemon
```

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
```

### Step 2: Configuring Environment Variables

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

```bash
cp .env.example .env
```

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
```

### Step 3: Connecting to MongoDB

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

```js
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);
    }
};
```

### Step 4: Creating the Express Server

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

```js
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}`);
});
```

### Step 5: Defining the Todo Model

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

```js
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);
```

### Step 6: Creating the Controller

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

```js
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);
    }
};
```

### Step 7: Defining Routes

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

```js
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;
```

### Step 8: Error Handling Middleware

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

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

### Step 9: Running the Application

Add the following scripts to `package.json`:

```json
"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1",
  "dev": "nodemon index.js",
  "start": "node index.js"
}
```

Start the application in development mode:

```bash
npm run dev
```

## 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](https://github.com/manthanank/crud-nodejs/tree/mongodb) to explore the code in detail.

---