Building a REST API with Node.js and Express
Step-by-step tutorial on creating a robust REST API using Node.js, Express, and MongoDB. Architecture best practices, rate limiting, and security.
Step-by-step tutorial on creating a robust REST API using Node.js, Express, and MongoDB. Architecture best practices, rate limiting, and security.
Creating Your First REST API
REST APIs are the backbone of modern web applications. Let’s build one from scratch using Node.js and Express.
Setting Up the Project
First, initialize a new Node.js project and install dependencies:
mkdir my-api
cd my-api
npm init -y
npm install express mongoose dotenv cors
npm install --save-dev nodemon
Creating the Server
Create server.js and set up a basic Express server:
const express = require("express");
const mongoose = require("mongoose");
const cors = require("cors");
require("dotenv").config();
const app = express();
// Middleware
app.use(express.json());
app.use(cors());
// Basic route
app.get("/", (req, res) => {
res.json({ message: "Welcome to my API" });
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Connecting to MongoDB
Add database connection in server.js:
mongoose
.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() => console.log("MongoDB connected"))
.catch((err) => console.error("MongoDB connection error:", err));
Creating a Model
Create models/Item.js:
const mongoose = require("mongoose");
const itemSchema = new mongoose.Schema(
{
name: {
type: String,
required: true,
trim: true,
},
description: {
type: String,
required: true,
},
price: {
type: Number,
required: true,
min: 0,
},
category: {
type: String,
required: true,
},
},
{
timestamps: true,
},
);
module.exports = mongoose.model("Item", itemSchema);
Defining RESTful Routes
Create routes/items.js:
const express = require("express");
const router = express.Router();
const Item = require("../models/Item");
// GET all items
router.get("/", async (req, res) => {
try {
const items = await Item.find();
res.json(items);
} catch (error) {
res.status(500).json({ message: error.message });
}
});
// GET single item
router.get("/:id", async (req, res) => {
try {
const item = await Item.findById(req.params.id);
if (!item) return res.status(404).json({ message: "Item not found" });
res.json(item);
} catch (error) {
res.status(500).json({ message: error.message });
}
});
// POST new item
router.post("/", async (req, res) => {
const item = new Item(req.body);
try {
const newItem = await item.save();
res.status(201).json(newItem);
} catch (error) {
res.status(400).json({ message: error.message });
}
});
// PUT update item
router.put("/:id", async (req, res) => {
try {
const item = await Item.findByIdAndUpdate(req.params.id, req.body, {
new: true,
runValidators: true,
});
if (!item) return res.status(404).json({ message: "Item not found" });
res.json(item);
} catch (error) {
res.status(400).json({ message: error.message });
}
});
// DELETE item
router.delete("/:id", async (req, res) => {
try {
const item = await Item.findByIdAndDelete(req.params.id);
if (!item) return res.status(404).json({ message: "Item not found" });
res.json({ message: "Item deleted" });
} catch (error) {
res.status(500).json({ message: error.message });
}
});
module.exports = router;
Error Handling Middleware
Add to server.js:
// Error handling middleware
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ message: "Something went wrong!" });
});
Testing Your API
Use Postman or curl to test endpoints:
# Get all items
curl http://localhost:3000/api/items
# Create new item
curl -X POST http://localhost:3000/api/items \
-H "Content-Type: application/json" \
-d '{"name":"Item 1","description":"Description","price":29.99,"category":"Electronics"}'
# Get single item
curl http://localhost:3000/api/items/:id
# Update item
curl -X PUT http://localhost:3000/api/items/:id \
-H "Content-Type: application/json" \
-d '{"price":24.99}'
# Delete item
curl -X DELETE http://localhost:3000/api/items/:id
Adding Authentication
Install JWT packages:
npm install jsonwebtoken bcryptjs
Create authentication middleware and protect routes as needed.
Conclusion
You now have a fully functional REST API! Next steps could include adding authentication, pagination, filtering, and deploying to production.
Key Technical Questions Answered
What are the essential security headers for a Node.js Express API? ↓
Every production Express API must use the Helmet middleware to set HTTP security headers (like Content-Security-Policy, HSTS, and X-Content-Type-Options), rate-limiting middleware to prevent brute-force attacks, and strict CORS policies to restrict unauthorized origins.
When should I choose Fastify or NestJS over Express? ↓
Express is excellent for straightforward, lightweight microservices. However, for high-throughput applications requiring automated schema validation, Fastify offers significantly higher requests-per-second performance. For complex enterprise architectures requiring strict dependency injection and modularity, NestJS is often preferred.
How should database connection pooling be handled in serverless Node.js environments? ↓
In serverless environments (like AWS Lambda or Cloudflare Workers), creating new database connections per invocation exhausts connection pools. Use connection pooling proxies (such as Prisma Accelerate, AWS RDS Proxy, or serverless HTTP database drivers) to maintain persistent pools across cold starts.
Published by Ahmed Raza
Technical publication curated by the FAAZ TECH engineering team. Focused on high-performance web systems, autonomous agents, and enterprise software scalability.
Related Engineering Insights
Build Future-Proof Web Architecture with FAAZ TECH
Whether migrating to edge serverless, deploying autonomous AI workflows, or optimizing for GEO search engines, our engineers deliver sub-second performance.
Schedule a Technical Consultation →