In this article, we’ll walk through the process of creating a robust Todo API using Node.js, Express, and MongoDB. This API will allow users to create, read, update, and delete todo items. We’ll cover the setup, file structure, and implementation of each component.
Part Two Flutter Integration: https://raheemdev.com/building-the-flutter-frontend-for-a-todo-app-with-node-js-rest-api/
Project Structure
Before we dive into the code, let’s look at our project structure:
/todo_api
│
├── /config
│ └── db.js # MongoDB connection
├── /controllers
│ └── todoController.js # Controller for handling CRUD logic
├── /models
│ └── todoModel.js # Todo Mongoose model
├── /routes
│ └── todoRoutes.js # Route definitions
├── .env # Environment variables
├── app.js # Express setup
└── package.json # Dependencies
Install the necessary dependencies by running:
npm install express mongoose dotenv
Step 1: Setting Up MongoDB Connection
First, let’s set up our MongoDB connection. Create a file named db.js
in the config
folder:
const mongoose = require('mongoose');
const connectDB = async () => {
try {
const conn = await mongoose.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
console.log(`MongoDB connected: ${conn.connection.host}`);
} catch (error) {
console.error(`Error: ${error.message}`);
process.exit(1); // Exit process with failure
}
};
module.exports = connectDB;
This function will handle the connection to our MongoDB database using the provided URI.
Step 2: Creating the Todo Model
Next, let’s define our Todo model. Create a file named todoModel.js
in the models
folder:
const mongoose = require('mongoose');
const todoSchema = new mongoose.Schema({
title: {
type: String,
required: true,
},
body: {
type: String,
required: true
},
completed: {
type: Boolean,
default: false,
},
}, { timestamps: true });
const Todo = mongoose.model('Todo', todoSchema); // todos -> collection
module.exports = Todo;
This model defines the structure of our todo items, with a title, body, and a completed status. The body
field is now required.
Step 3: Implementing CRUD Operations
Now, let’s create our controller to handle CRUD operations. Create a file named todoController.js
in the controllers
folder:
const Todo = require('../models/todoModel');
// Get all todos
const getTodos = async (req, res) => {
try {
const todos = await Todo.find();
res.status(200).json(todos);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Create new todo
const createTodo = async (req, res) => {
try {
const todo = new Todo({
title: req.body.title,
body: req.body.body,
completed: req.body.completed || false,
});
const savedTodo = await todo.save();
res.status(201).json(savedTodo);
} catch (error) {
res.status(400).json({ message: error.message });
}
};
// Update todo
const updateTodo = async (req, res) => {
try {
const updatedTodo = await Todo.findByIdAndUpdate(req.params.id, req.body, {
new: true,
runValidators: true,
});
if (!updatedTodo) return res.status(404).json({ message: "Todo not found" });
res.status(200).json(updatedTodo);
} catch (error) {
res.status(400).json({ message: error.message });
}
};
// Delete todo
const deleteTodo = async (req, res) => {
try {
const deletedTodo = await Todo.findByIdAndDelete(req.params.id);
if (!deletedTodo) return res.status(404).json({ message: "Todo not found" });
res.status(200).json({ message: "Todo deleted" });
} catch (error) {
res.status(500).json({ message: error.message });
}
};
module.exports = {
getTodos,
createTodo,
updateTodo,
deleteTodo,
};
These functions handle getting all todos, creating a new todo, updating an existing todo, and deleting a todo. Note that the createTodo
function now includes the body
field when creating a new todo.
Step 4: Setting Up Routes
Let’s set up our routes to connect our API endpoints to the controller functions. Create a file named todoRoutes.js
in the routes
folder:
const express = require('express');
const { getTodos, createTodo, updateTodo, deleteTodo } = require('../controllers/todoController');
const router = express.Router();
router.get('/', getTodos);
router.post('/', createTodo);
router.put('/:id', updateTodo);
router.delete('/:id', deleteTodo);
module.exports = router;
This sets up our routes for each CRUD operation.
Step 5: Main Application Setup
Finally, let’s set up our main application file. Create a file named app.js
in the root directory:
const express = require('express');
const dotenv = require('dotenv');
const connectDB = require('./config/db');
const todoRoutes = require('./routes/todoRoutes');
// Load environment variables
dotenv.config();
// Connect to MongoDB
connectDB();
const app = express();
app.use(express.json()); // Middleware to parse JSON
// Todo Routes
app.use('/api/todos', todoRoutes);
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
This file brings everything together, setting up our Express application, connecting to the database, and implementing our routes.
Environment Variables
Don’t forget to set up your .env
file in the root directory:
MONGO_URI=mongodb+srv://<username>:<password>@cluster.mongodb.net/todoDB
PORT=5000
Replace <username>
and <password>
with your actual MongoDB credentials.
Conclusion
With these components in place, you now have a fully functional Todo API built with Node.js, Express, and MongoDB. This API allows for creating, reading, updating, and deleting todo items, including a title and body for each todo. You can further expand on this foundation by adding user authentication, more complex querying, or additional features as needed for your specific use case.