Building REST APIs with Node.js and Express
Learn how to build production-ready RESTful APIs using Node.js, Express, and best practices.
Prerequisites
- ✓JavaScript fundamentals
- ✓Node.js installed
- ✓Understanding of HTTP methods
Building REST APIs with Node.js and Express
Learn how to build a complete RESTful API from scratch using Node.js and Express with proper error handling, validation, and best practices.
What We're Building
A Task Manager API with full CRUD operations:
- Create tasks
- Read tasks (all and by ID)
- Update tasks
- Delete tasks
Prerequisites Check
Ensure you have:
✅ Node.js 18+ installed (node --version)
✅ A code editor (VS Code recommended)
✅ Postman or curl for testing
Step 1: Project Setup
Initialize your project:
mkdir task-api
cd task-api
npm init -y
Install dependencies:
npm install express
npm install --save-dev nodemon
Update package.json scripts:
{
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
}
}
Step 2: Create Basic Server
Create server.js:
const express = require("express");
const app = express();
// Middleware to parse JSON
app.middleware(express.json());
// Test route
app.get("/", (req, res) => {
res.json({ message: "Task API Server" });
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Test it:
npm run dev
Visit http://localhost:3000 - you should see the welcome message.
Step 3: Design API Endpoints
Plan your API structure:
| Method | Endpoint | Description |
| ------ | ---------------- | --------------- |
| GET | /api/tasks | Get all tasks |
| GET | /api/tasks/:id | Get task by ID |
| POST | /api/tasks | Create new task |
| PUT | /api/tasks/:id | Update task |
| DELETE | /api/tasks/:id | Delete task |
Step 4: Create Data Model
For this tutorial, we'll use in-memory storage (later you'd use a database).
Create models/task.js:
// In-memory storage
let tasks = [];
let nextId = 1;
class Task {
constructor(title, description, completed = false) {
this.id = nextId++;
this.title = title;
this.description = description;
this.completed = completed;
this.createdAt = new Date().toISOString();
}
}
module.exports = { tasks, Task };
Step 5: Implement GET Routes
Create routes/tasks.js:
const express = require("express");
const router = express.Router();
const { tasks, Task } = require("../models/task");
// GET all tasks
router.get("/", (req, res) => {
res.json({
success: true,
count: tasks.length,
data: tasks,
});
});
// GET task by ID
router.get("/:id", (req, res) => {
const task = tasks.find((t) => t.id === parseInt(req.params.id));
if (!task) {
return res.status(404).json({
success: false,
error: "Task not found",
});
}
res.json({
success: true,
data: task,
});
});
module.exports = router;
Update server.js:
const express = require("express");
const app = express();
app.use(express.json());
// Routes
app.use("/api/tasks", require("./routes/tasks"));
app.get("/", (req, res) => {
res.json({ message: "Task API Server" });
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Step 6: Implement POST Route
Add to routes/tasks.js:
// POST create new task
router.post("/", (req, res) => {
const { title, description } = req.body;
// Validation
if (!title) {
return res.status(400).json({
success: false,
error: "Title is required",
});
}
const task = new Task(title, description);
tasks.push(task);
res.status(201).json({
success: true,
data: task,
});
});
Test with curl:
curl -X POST http://localhost:3000/api/tasks \
-H "Content-Type: application/json" \
-d '{"title":"Learn Node.js","description":"Build REST API"}'
Step 7: Implement PUT Route
Add to routes/tasks.js:
// PUT update task
router.put("/:id", (req, res) => {
const task = tasks.find((t) => t.id === parseInt(req.params.id));
if (!task) {
return res.status(404).json({
success: false,
error: "Task not found",
});
}
const { title, description, completed } = req.body;
if (title !== undefined) task.title = title;
if (description !== undefined) task.description = description;
if (completed !== undefined) task.completed = completed;
res.json({
success: true,
data: task,
});
});
Step 8: Implement DELETE Route
Add to routes/tasks.js:
// DELETE task
router.delete("/:id", (req, res) => {
const index = tasks.findIndex((t) => t.id === parseInt(req.params.id));
if (index === -1) {
return res.status(404).json({
success: false,
error: "Task not found",
});
}
tasks.splice(index, 1);
res.json({
success: true,
data: {},
});
});
Step 9: Add Error Handling
Create middleware/errorHandler.js:
const errorHandler = (err, req, res, next) => {
console.error(err.stack);
res.status(err.status || 500).json({
success: false,
error: err.message || "Server Error",
});
};
module.exports = errorHandler;
Add to server.js:
const errorHandler = require("./middleware/errorHandler");
// ... routes ...
// Error handler (must be last)
app.use(errorHandler);
Step 10: Testing Your API
Test all endpoints:
# Get all tasks
curl http://localhost:3000/api/tasks
# Create task
curl -X POST http://localhost:3000/api/tasks \
-H "Content-Type: application/json" \
-d '{"title":"Test","description":"Testing"}'
# Get single task
curl http://localhost:3000/api/tasks/1
# Update task
curl -X PUT http://localhost:3000/api/tasks/1 \
-H "Content-Type: application/json" \
-d '{"completed":true}'
# Delete task
curl -X DELETE http://localhost:3000/api/tasks/1
Best Practices Implemented
✅ RESTful conventions - Proper HTTP methods and status codes
✅ Consistent response format - All responses have success and data/error
✅ Input validation - Check required fields
✅ Error handling - Centralized error handler
✅ Modular structure - Separated routes and models
Next Steps
Enhance your API with:
- Database integration - Use MongoDB or PostgreSQL
- Authentication - JWT tokens
- Validation library - Joi or Zod
- Rate limiting - Prevent abuse
- API documentation - Swagger/OpenAPI
- Testing - Jest or Mocha
Summary
You've built a complete REST API with:
- ✅ CRUD operations
- ✅ Proper error handling
- ✅ Clean code structure
- ✅ RESTful conventions
Your API is ready for production after adding a database and authentication!