Node.js Project: Blog API (REST + Auth + MongoDB)
It's time to build something real! In this project, you'll apply your knowledge of Node.js, Express.js, and MongoDB to develop a complete RESTful API for a blog. The API will include user management with secure authentication (JWT) and comment functionality for posts. This is a fundamental project that will allow you to solidify the foundations of backend development.
Project Goal
Build a backend API that allows for:
- Managing users (registration, login).
- Creating, reading, updating, and deleting (CRUD) blog posts.
- Adding and reading comments on posts.
- Protecting certain routes and functionalities using token-based authentication (JWT).
Key Technologies
- Node.js: JavaScript runtime environment.
- Express.js: Web framework for Node.js, ideal for building REST APIs.
- MongoDB: Document-oriented NoSQL database.
- Mongoose: ODM (Object Data Modeling) for MongoDB and Node.js, simplifies database interaction.
- JSON Web Tokens (JWT): Standard for creating secure access tokens that verify user identity without needing to query the database on every request.
- Bcrypt.js: Library for secure password hashing.
- dotenv: For loading environment variables from an `.env` file.
Suggested Project Structure
A good project structure is key for maintainability and scalability.
my-blog-api/
├── node_modules/
├── src/
│ ├── models/ # Mongoose Schemas (User, Post, Comment)
│ │ ├── User.js
│ │ ├── Post.js
│ │ └── Comment.js
│ ├── routes/ # API Route Definitions (auth, posts, comments)
│ │ ├── authRoutes.js
│ │ ├── postRoutes.js
│ │ └── commentRoutes.js
│ ├── controllers/ # Business logic for each route
│ │ ├── authController.js
│ │ ├── postController.js
│ │ └── commentController.js
│ ├── middleware/ # Authentication Middleware (verifyToken.js)
│ │ └── authMiddleware.js
│ ├── config/ # DB, JWT Configuration
│ │ ├── db.js # MongoDB Connection
│ │ └── jwt.js # JWT Configuration
│ ├── utils/ # Utility functions (e.g., error handling)
│ │ └── asyncHandler.js # Wrapper for async route error handling
│ └── app.js # Main Express file
├── .env # Environment Variables (DO NOT commit to Git)
├── .gitignore
├── package.json
└── README.md
Database Design (Mongoose Schemas)
Define your data models using Mongoose.
User Schema (`src/models/User.js`):
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const UserSchema = new mongoose.Schema({
username: { type: String, required: true, unique: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
role: { type: String, enum: ['user', 'admin'], default: 'user' },
}, { timestamps: true });
// Hash password before saving
UserSchema.pre('save', async function(next) {
if (!this.isModified('password')) return next();
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
next();
});
// Method to compare password
UserSchema.methods.matchPassword = async function(enteredPassword) {
return await bcrypt.compare(enteredPassword, this.password);
};
module.exports = mongoose.model('User', UserSchema);
Post Schema (`src/models/Post.js`):
const mongoose = require('mongoose');
const PostSchema = new mongoose.Schema({
title: { type: String, required: true },
content: { type: String, required: true },
author: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
comments: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Comment' }],
}, { timestamps: true });
module.exports = mongoose.model('Post', PostSchema);
Comment Schema (`src/models/Comment.js`):
const mongoose = require('mongoose');
const CommentSchema = new mongoose.Schema({
content: { type: String, required: true },
author: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
post: { type: mongoose.Schema.Types.ObjectId, ref: 'Post', required: true },
}, { timestamps: true });
module.exports = mongoose.model('Comment', CommentSchema);
API Endpoints (RESTful)
Define your API routes in a RESTful manner.
- Authentication (`/api/auth`):
- `POST /api/auth/register`: Register new users.
- `POST /api/auth/login`: User login, returns a JWT token.
- Posts (`/api/posts`):
- `GET /api/posts`: Get all posts.
- `GET /api/posts/:id`: Get a post by ID.
- `POST /api/posts`: Create a new post (requires authentication).
- `PUT /api/posts/:id`: Update a post (requires authentication, only author or admin).
- `DELETE /api/posts/:id`: Delete a post (requires authentication, only author or admin).
- Comments (`/api/posts/:postId/comments` and `/api/comments`):
- `GET /api/posts/:postId/comments`: Get comments for a specific post.
- `POST /api/posts/:postId/comments`: Add a comment to a post (requires authentication).
- `DELETE /api/comments/:id`: Delete a comment (requires authentication, only author or admin).
Authentication Implementation (JWT)
JWT authentication is crucial for protecting your routes.
- User Registration:
When a user registers, their password must be hashed using `bcrypt.js` before saving it to the database. The `pre('save')` method in the `UserSchema` (shown above) handles this automatically.
- Login:
Upon login, verify the provided password against the hashed one in the database (`user.matchPassword`). If they match, generate a JWT token using `jsonwebtoken` that includes the `userId` and optionally the user's `role`. This token is sent to the client.
// authController.js (login excerpt) const jwt = require('jsonwebtoken'); const User = require('../models/User'); const generateToken = (id) => { return jwt.sign({ id }, process.env.JWT_SECRET, { expiresIn: '1h' }); }; exports.loginUser = async (req, res) => { const { email, password } = req.body; const user = await User.findOne({ email }); if (user && (await user.matchPassword(password))) { res.json({ _id: user._id, username: user.username, email: user.email, token: generateToken(user._id), }); } else { res.status(401).json({ message: 'Invalid credentials' }); } };
- Authentication Middleware (`src/middleware/authMiddleware.js`):
This middleware intercepts requests to protected routes. It extracts the JWT token from the `Authorization` header (format `Bearer TOKEN`), verifies it (`jwt.verify`), and if valid, adds the `userId` (and `role`) to the `req` object before passing to the next middleware/controller.
const jwt = require('jsonwebtoken'); const User = require('../models/User'); const protect = async (req, res, next) => { let token; if (req.headers.authorization && req.headers.authorization.startsWith('Bearer')) { try { token = req.headers.authorization.split(' ')[1]; const decoded = jwt.verify(token, process.env.JWT_SECRET); req.user = await User.findById(decoded.id).select('-password'); // Attach user without password next(); } catch (error) { res.status(401).json({ message: 'Not authorized, token failed' }); } } if (!token) { res.status(401).json({ message: 'Not authorized, no token' }); } }; // Middleware for roles (optional) const authorizeRoles = (...roles) => { return (req, res, next) => { if (!roles.includes(req.user.role)) { return res.status(403).json({ message: 'You do not have permission to access this route' }); } next(); }; }; module.exports = { protect, authorizeRoles };
Steps to Build the Project (Guide)
- Initialize Project: Create a folder, `npm init -y`. Install dependencies: `npm install express mongoose jsonwebtoken bcryptjs dotenv`.
- Configure `.env`: Create a `.env` file in the root with variables like `MONGO_URI`, `JWT_SECRET`, `PORT`.
- MongoDB Connection: In `src/config/db.js`, configure the connection using Mongoose. Call this function from `app.js`.
- Define Models: Create `User`, `Post`, `Comment` schemas in `src/models/`.
- Implement Authentication:
- Create `src/controllers/authController.js` with registration and login logic.
- Create `src/routes/authRoutes.js` and connect it to the controllers.
- Create Authentication Middleware: Implement `src/middleware/authMiddleware.js` to verify JWT tokens.
- Implement CRUD for Posts:
- Create `src/controllers/postController.js` with CRUD logic.
- Create `src/routes/postRoutes.js`, apply the `protect` middleware to routes that require it (`POST`, `PUT`, `DELETE`).
- Implement CRUD/Comments:
- Create `src/controllers/commentController.js`.
- Create `src/routes/commentRoutes.js`, integrating with posts and applying authentication.
- Configure `app.js`:
- Configure Express.js, middlewares (e.g., `express.json()`).
- Import and use routes (`app.use('/api/auth', authRoutes)`).
- Basic error handling (middleware at the end).
- Start the server.
- Testing: Use tools like Postman or Insomnia to test all your API endpoints. Be sure to test success cases, errors, authentication, and permissions.
This Blog API project is a solid foundation for any Node.js backend developer. By completing it, you will have mastered key concepts such as RESTful API design, interaction with NoSQL databases, and implementing secure authentication. Get to work and enjoy the process of building your own API!