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
Next, install the necessary dependencies:
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
):
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:
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:
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:
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:
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:
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:
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
:
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "nodemon index.js",
"start": "node index.js"
}
Start the application in development mode:
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 to explore the code in detail.