How to built a Blog Backend API with Express.js: A Developer’s Journey

When I first decided to build my own blog platform from scratch, I knew I wanted something lightweight, flexible, and fast. After some research, I chose Express.js, a minimalist web framework for Node.js. What followed was a journey of transforming simple ideas into a powerful backend API that could support posts, comments, authentication, and more.
Let me walk you through that journey — not just what I built, but how I built it and what I learned along the way.
1. The Vision: What Should the Blog Do?
Before writing a single line of code, I grabbed a notebook and jotted down the core features:
-
Users should be able to create accounts and log in.
-
Authenticated users should be able to create, edit, and delete blog posts.
-
Anyone should be able to read posts.
-
Users can comment on posts.
-
The API should be RESTful and scalable.
With the vision clear, it was time to bring it to life using Express.js.
2. Setting Up the Project
Like any good project, it all started with the basics:
mkdir blog-api
cd blog-api
npm init -y
npm install express mongoose dotenv jsonwebtoken bcryptjs cors
I created a simple index.js
file to spin up the server:
const express = require('express');
const app = express();
const PORT = process.env.PORT || 5000;
app.use(express.json());
app.get('/', (req, res) => {
res.send('Blog API is running...');
});
app.listen(PORT, () => {
console.log(`Server is listening on port ${PORT}`);
});
Simple, yet exciting. The server was live!
3. Database Decisions: MongoDB with Mongoose
For storing blog posts, users, and comments, I went with MongoDB — ideal for its document-based flexibility. Using Mongoose, I modeled the core entities:
Post Model:
const mongoose = require('mongoose');
const PostSchema = new mongoose.Schema({
title: String,
body: String,
author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
createdAt: { type: Date, default: Date.now }
});
module.exports = mongoose.model('Post', PostSchema);
User Model:
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const UserSchema = new mongoose.Schema({
username: { type: String, unique: true },
password: String
});
// Password hashing
UserSchema.pre('save', async function(next) {
if (!this.isModified('password')) return next();
this.password = await bcrypt.hash(this.password, 10);
next();
});
module.exports = mongoose.model('User', UserSchema);
4. Building the RESTful Routes
Express.js made route handling seamless. I split them into different files: /routes/posts.js
, /routes/users.js
, and so on.
Sample Post Routes:
const express = require('express');
const router = express.Router();
const Post = require('../models/Post');
router.post('/', async (req, res) => {
try {
const newPost = new Post(req.body);
const saved = await newPost.save();
res.status(201).json(saved);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
router.get('/', async (req, res) => {
const posts = await Post.find().populate('author', 'username');
res.json(posts);
});
module.exports = router;
5. Authentication: Keeping It Secure
I added user authentication with JWT (JSON Web Tokens).
Login Route:
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const User = require('../models/User');
router.post('/login', async (req, res) => {
const user = await User.findOne({ username: req.body.username });
if (!user || !(await bcrypt.compare(req.body.password, user.password))) {
return res.status(401).json({ message: 'Invalid credentials' });
}
const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: '1d' });
res.json({ token });
});
I also created a simple middleware to protect routes:
const jwt = require('jsonwebtoken');
function auth(req, res, next) {
const token = req.headers.authorization?.split(" ")[1];
if (!token) return res.sendStatus(401);
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (err) {
res.sendStatus(403);
}
}
6. Commenting System and Final Touches
With posts and users working smoothly, I added a basic commenting system and ensured all data was properly linked. Middleware like cors
made cross-origin requests possible for when I’d later connect it to a frontend.
7. Testing the API
I used Postman to rigorously test every endpoint:
-
Register
-
Login
-
Create Post
-
View All Posts
-
Comment on Posts
-
Edit/Delete (with authentication)
Seeing it all come together was incredibly satisfying — watching the backend respond, store, and retrieve blog data like a digital librarian.
8. Lessons Learned
This project taught me a lot:
-
Modularity matters: Separating routes and logic into different files kept the code clean.
-
Security is essential: Hashing passwords and verifying tokens is non-negotiable.
-
Testing early helps: Catching bugs before the frontend is built saves hours.
Conclusion: From Vision to Reality
In just a few days, what started as an idea became a working blog backend API. Express.js made everything approachable — from handling routes to building a secure, RESTful interface for future frontend consumption.
Next up? Connecting it to a frontend using React or Next.js and deploying it with cloud services like Heroku or Vercel. But that’s a story for another day.
If you’re thinking of building your own blog backend, trust me — Express.js is a fantastic place to start.