Today, for Day 34 of #100DaysOfCode, the goal was to build a database api that saves the data in the MongoDB database using Mongoose schema and Node.js and Express.js routes.
TL;DR
The CRUD REST API was built using:
- Node.js
- Express
- MongoDB
- Mongoose
This project implements the four essential backend operations:
Create
Read
Update
Delete
Project Folder Structure
To keep the code organized, I used a separation-of-concerns structure.
project/
│
├── models/
│ └── Todo.js
│
├── routes/
│ └── todoRoutes.js
│
├── controllers/
│ └── todoController.js
│
├── config/
│ └── db.js
│
├── server.js
├── .env
└── package.json
This structure separates responsibilities:
| Folder | Responsibility |
|---|---|
| Models | Database schema |
| Routes | API endpoints |
| Controllers | Business logic |
| Config | Database connection |
| Server | Application entry point |
This pattern scales well for larger APIs and production apps.
Step 1 — Initialize the Project
Create a new Node project:
npm init -y
Install required dependencies:
npm install express mongoose dotenv
-
express— To handle routes -
mongoose— For creating schema for MongoDB -
dotenv— For creating environment variables
Install nodemon for auto server restarting (Optional):
npm install nodemon --save-dev
If using nodemon, add this to package.json:
"scripts": {
"dev": "nodemon server.js"
}
Step 2 — Create the Express Server
File: server.js
const express = require("express")
const dotenv = require("dotenv")
const connectDB = require("./config/db")
const todoRoutes = require("./routes/todoRoutes")
dotenv.config()
connectDB()
const app = express()
app.use(express.json())
app.use("/api/todos", todoRoutes)
const PORT = process.env.PORT || 5000
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`)
})
Environment Variables
Create a .env file.
MONGO_URI=your_mongodb_connection_string
PORT=5000
Step 3 — Connect MongoDB
File: config/db.js
const mongoose = require("mongoose")
const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGO_URI)
console.log("MongoDB Connected")
} catch (error) {
console.error(error)
process.exit(1)
}
}
module.exports = connectDB
Step 4 — Create the Mongoose Model
File: models/Todo.js
const mongoose = require("mongoose")
const todoSchema = new mongoose.Schema({
title: {
type: String,
required: true
},
completed: {
type: Boolean,
default: false
},
createdAt: {
type: Date,
default: Date.now
}
})
module.exports = mongoose.model("Todo", todoSchema)
This schema defines the Todo document structure in MongoDB.
Step 5 — Create Controllers (CRUD Logic)
File: controllers/todoController.js
const Todo = require("../models/Todo")
// Create Todo
exports.createTodo = async (req, res) => {
try {
const todo = await Todo.create(req.body)
res.status(201).json(todo)
} catch (error) {
res.status(500).json({ error: error.message })
}
}
// Get All Todos
exports.getTodos = async (req, res) => {
try {
const todos = await Todo.find()
res.json(todos)
} catch (error) {
res.status(500).json({ error: error.message })
}
}
// Get Single Todo
exports.getTodo = async (req, res) => {
try {
const todo = await Todo.findById(req.params.id)
if (!todo) {
return res.status(404).json({ message: "Todo not found" })
}
res.json(todo)
} catch (error) {
res.status(500).json({ error: error.message })
}
}
// Update Todo
exports.updateTodo = async (req, res) => {
try {
const todo = await Todo.findByIdAndUpdate(
req.params.id,
req.body,
{ new: true }
)
if (!todo) {
return res.status(404).json({ message: "Todo not found" })
}
res.json(todo)
} catch (error) {
res.status(500).json({ error: error.message })
}
}
// Delete Todo
exports.deleteTodo = async (req, res) => {
try {
const todo = await Todo.findByIdAndDelete(req.params.id)
if (!todo) {
return res.status(404).json({ message: "Todo not found" })
}
res.json({ message: "Todo deleted" })
} catch (error) {
res.status(500).json({ error: error.message })
}
}
These controllers contain the core business logic of the API.
Step 6 — Create Routes
File: routes/todoRoutes.js
const express = require("express")
const router = express.Router()
const todoController = require("../controllers/todoController")
router.post("/", todoController.createTodo)
router.get("/", todoController.getTodos)
router.get("/:id", todoController.getTodo)
router.put("/:id", todoController.updateTodo)
router.delete("/:id", todoController.deleteTodo)
module.exports = router
Routes connect HTTP requests → controller logic.
REST API Endpoints
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/todos |
Create a new todo |
| GET | /api/todos |
Get all todos |
| GET | /api/todos/:id |
Get a single todo |
| PUT | /api/todos/:id |
Update a todo |
| DELETE | /api/todos/:id |
Delete a todo |
Testing the API
The API can be tested using:
- Postman
- Thunder Client (VS Code extension)
Example request:
Create Todo
POST /api/todos
Body:
{
"title": "Learn MERN stack"
}
Practice extensions to try
Once CRUD works, try implementing these real-world API features.
Filtering
Example:
GET /api/todos?completed=true
Pagination
Example:
GET /api/todos?page=1&limit=5
Search
Example:
GET /api/todos?search=mern
Validation
Example rule:
title must be at least 3 characters
You can implement validation using:
- Mongoose validation
- Joi
- Express-validator
What I Learned Today
Key backend concepts from this project:
- Building a REST API with Express
- Structuring a backend using MVC pattern
- Connecting MongoDB with Mongoose
- Implementing CRUD operations
- Organizing code for scalable APIs
Conclusion
This project is a great starting point for backend development with the MERN stack.
Next improvements could include:
- Authentication (JWT)
- Middleware
- Error handling middleware
- Rate limiting
- Logging
Thanks for reading. Feel free to share your thoughts!
Top comments (0)