This comprehensive guide walks you through building a robust backend REST API using Node.js, Express, and MongoDB, centered around a Goals project. In this project, you’ll build an API to manage “goals” simple text-based records representing tasks or objectives. The guide includes step-by-step instructions on setting up the project, configuring environment variables, connecting to MongoDB, designing the Goal model with Mongoose, and creating REST API endpoints (CRUD) to create, read, update, and delete goals. Along the way, you’ll also learn best practices in structuring your application, handling errors gracefully, and preparing your backend for integration with a frontend app.
🔧 1. Setting Up the Project
✅ Initialize Node Project
npm init -y npm install express mongoose dotenv express-async-handler npm install nodemon --save-dev
Add to package.json.
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
}
✅ Create .env
PORT = 5000
MONGO_URI = your_mongodb_connection_string
🚀 2. server.js — App Entry Point
const express = require('express')
const dotenv = require('dotenv').config()
const connectDB = require('./config/db')
const goalRoutes = require('./routes/goalRoutes')
const { errorHandler } = require('./middleware/errorMiddleware')
connectDB()
const app = express()
app.use(express.json())
app.use(express.urlencoded({ extended: false }))
app.use('/api/goals', goalRoutes)
app.use(errorHandler)
const PORT = process.env.PORT || 5000
app.listen(PORT, () => console.log(`Server started on ${PORT}`))
🔗 3. Database Connection — /config/db.js
const mongoose = require('mongoose')
const connectDB = async () => {
try {
const conn = await mongoose.connect(process.env.MONGO_URI) console.log(`MongoDB Connected: ${conn.connection.host}`)
} catch (error) {
console.error(error) process.exit(1)
}
}
module.exports = connectDB
📦 4. Goal Model — /models/goalModel.js
const mongoose = require('mongoose') const goalSchema = mongoose.Schema({
text: {
type: String,
required: [true, 'Please add a text value'],
},
}, {
timestamps: true
}) module.exports = mongoose.model('Goal', goalSchema)
🧠 5. Goal Controller — /controllers/goalController.js
const asyncHandler = require('express-async-handler') const Goal = require('../models/goalModel')
// @desc Get goals
exports.getGoals = asyncHandler(async (req, res) => {
const goals = await Goal.find() res.status(200).json(goals)
})
// @desc Create goal
exports.setGoal = asyncHandler(async (req, res) => {
if (!req.body.text) {
res.status(400) throw new Error('Please add a text field')
}
const goal = await Goal.create({
text: req.body.text
}) res.status(201).json(goal)
})
// @desc Update goal
exports.updateGoal = asyncHandler(async (req, res) => {
const goal = await Goal.findById(req.params.id) if (!goal) {
res.status(404) throw new Error('Goal not found')
}
const updatedGoal = await Goal.findByIdAndUpdate(req.params.id, req.body, {
new: true
}) res.status(200).json(updatedGoal)
})
// @desc Delete goal
exports.deleteGoal = asyncHandler(async (req, res) => {
const goal = await Goal.findById(req.params.id) if (!goal) {
res.status(404) throw new Error('Goal not found')
} await goal.deleteOne()
res.status(200).json({
id: req.params.id
})
})
🔀 6. Goal Routes — /routes/goalRoutes.js
const express = require('express')
const router = express.Router()
const {
getGoals,
setGoal,
updateGoal,
deleteGoal,
} = require('../controllers/goalController')
router.route('/')
.get(getGoals)
.post(setGoal)
router.route('/:id')
.put(updateGoal)
.delete(deleteGoal)
module.exports = router
⚠️ 7. Error Middleware — /middleware/errorMiddleware.js
const errorHandler = (err, req, res, next) => {
const statusCode = res.statusCode ? res.statusCode : 500 res.status(statusCode) res.json({
message: err.message,
stack: process.env.NODE_ENV === 'production' ? null : err.stack,
})
}
module.exports = {
errorHandler
}
🔥8. Run the Application
//Start the Backend Server
nodemon server.js
🧪 9. Testing Your API
Use Postman to test.
Method |
Endpoint |
Description |
GET |
/api/goals |
Get all goals |
POST |
/api/goals |
Create a new goal |
PUT |
/api/goals/:id |
Update goal |
DELETE |
/api/goals/:id |
Delete goal |
10. FAQs
1️⃣ What is the MERN stack?
The MERN stack refers to MongoDB, Express.js, React, and Node.js — a popular combination for building full-stack JavaScript applications.
2️⃣ Why use Express with Node.js?
Express simplifies routing, middleware, and server setup in Node.js applications, providing a clean structure for building APIs.
3️⃣ What is Mongoose, and why do we use it?
Mongoose is an ODM (Object Data Modeling) library for MongoDB and Node.js. It simplifies interacting with MongoDB, providing schema validation and helpful methods for queries and updates.
4️⃣ What is CRUD in REST APIs?
CRUD stands for Create, Read, Update, Delete — the fundamental operations typically exposed via REST API endpoints.
5️⃣ How do I test my REST API?
You can test endpoints using tools like Postman, Insomnia, or by writing automated tests with frameworks like Jest or Mocha.
6️⃣ Why use async/await with controllers?
Because database operations are asynchronous, using async/await (often combined with express-async-handler) makes your code cleaner and avoids callback hell.
7️⃣ What is the purpose of the .env file?
It stores environment variables like your MongoDB URI or JWT secret, keeping sensitive data out of source code and making your app easier to configure in different environments.
8️⃣ How can I protect certain routes?
By using authentication middleware that checks for a valid token (JWT) in request headers before allowing access to protected routes.
9️⃣ Can I deploy this backend separately from the frontend?
Yes — typically the backend runs on its own server (Heroku, AWS, DigitalOcean, etc.) and the frontend (React) calls it via HTTP requests.
🔟 How do I handle errors gracefully?
Use middleware (like a centralized errorHandler) to catch and return errors in a consistent JSON format, and set appropriate HTTP status codes.