Loading

Quipoin Menu

Learn • Practice • Grow

express-js / Build RESTful API (CRUD + Auth)
tutorial

Build RESTful API (CRUD + Auth)

Now it's time to put everything together! Let's build a complete RESTful API with full CRUD operations and JWT authentication. We'll create a simple blog API where users can register, login, and manage their posts.

Project Setup
// package.json dependencies
// npm install express mongoose jsonwebtoken bcrypt dotenv express-validator

1. Configuration (config/config.js)
require('dotenv').config();

module.exports = {
port: process.env.PORT || 3000,
mongoUri: process.env.MONGODB_URI || 'mongodb://localhost:27017/blog-api',
jwtSecret: process.env.JWT_SECRET || 'your-secret-key-change-this',
jwtExpiresIn: '7d',
nodeEnv: process.env.NODE_ENV || 'development'
};

2. Database Connection (config/db.js)
const mongoose = require('mongoose');
const config = require('./config');

const connectDB = async () => {
try {
await mongoose.connect(config.mongoUri);
console.log('MongoDB Connected...');
} catch (err) {
console.error('Database connection error:', err.message);
process.exit(1);
}
};

module.exports = connectDB;

3. User Model (models/User.js)
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');

const userSchema = new mongoose.Schema({
name: { type: String, required: true, trim: true },
email: {
type: String,
required: true,
unique: true,
lowercase: true,
trim: true
},
password: { type: String, required: true, minlength: 6 },
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();
this.password = await bcrypt.hash(this.password, 10);
next();
});

// Method to compare passwords
userSchema.methods.comparePassword = async function(candidatePassword) {
return await bcrypt.compare(candidatePassword, this.password);
};

module.exports = mongoose.model('User', userSchema);

4. Post Model (models/Post.js)
const mongoose = require('mongoose');

const postSchema = new mongoose.Schema({
title: { type: String, required: true, trim: true },
content: { type: String, required: true },
author: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
tags: [String],
published: { type: Boolean, default: true }
}, { timestamps: true });

module.exports = mongoose.model('Post', postSchema);

5. Authentication Middleware (middleware/auth.js)
const jwt = require('jsonwebtoken');
const User = require('../models/User');
const config = require('../config/config');

const protect = async (req, res, next) => {
try {
let token;

if (req.headers.authorization && req.headers.authorization.startsWith('Bearer')) {
token = req.headers.authorization.split(' ')[1];
}

if (!token) {
return res.status(401).json({ error: 'Not authorized, no token' });
}

const decoded = jwt.verify(token, config.jwtSecret);
req.user = await User.findById(decoded.id).select('-password');

if (!req.user) {
return res.status(401).json({ error: 'User not found' });
}

next();
} catch (error) {
res.status(401).json({ error: 'Not authorized, token failed' });
}
};

const authorize = (...roles) => {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return res.status(403).json({ error: 'Not authorized for this action' });
}
next();
};
};

module.exports = { protect, authorize };

6. Auth Controller (controllers/authController.js)
const jwt = require('jsonwebtoken');
const User = require('../models/User');
const config = require('../config/config');

const generateToken = (id) => {
return jwt.sign({ id }, config.jwtSecret, { expiresIn: config.jwtExpiresIn });
};

exports.register = async (req, res) => {
try {
const { name, email, password } = req.body;

// Check if user exists
const existingUser = await User.findOne({ email });
if (existingUser) {
return res.status(400).json({ error: 'Email already registered' });
}

// Create user
const user = await User.create({ name, email, password });

// Generate token
const token = generateToken(user._id);

res.status(201).json({
success: true,
token,
user: {
id: user._id,
name: user.name,
email: user.email,
role: user.role
}
});
} catch (error) {
res.status(500).json({ error: error.message });
}
};

exports.login = async (req, res) => {
try {
const { email, password } = req.body;

// Find user
const user = await User.findOne({ email });
if (!user) {
return res.status(401).json({ error: 'Invalid credentials' });
}

// Check password
const isMatch = await user.comparePassword(password);
if (!isMatch) {
return res.status(401).json({ error: 'Invalid credentials' });
}

// Generate token
const token = generateToken(user._id);

res.json({
success: true,
token,
user: {
id: user._id,
name: user.name,
email: user.email,
role: user.role
}
});
} catch (error) {
res.status(500).json({ error: error.message });
}
};

exports.getMe = async (req, res) => {
res.json({
success: true,
user: req.user
});
};

7. Post Controller (controllers/postController.js)
const Post = require('../models/Post');

// Create post
exports.createPost = async (req, res) => {
try {
const post = await Post.create({
...req.body,
author: req.user._id
});
await post.populate('author', 'name email');
res.status(201).json({ success: true, data: post });
} catch (error) {
res.status(500).json({ error: error.message });
}
};

// Get all posts (with filtering and pagination)
exports.getPosts = async (req, res) => {
try {
const { page = 1, limit = 10, author, published, tag } = req.query;
const filter = {};

if (author) filter.author = author;
if (published !== undefined) filter.published = published === 'true';
if (tag) filter.tags = tag;

const posts = await Post.find(filter)
.populate('author', 'name email')
.sort('-createdAt')
.limit(limit * 1)
.skip((page - 1) * limit);

const total = await Post.countDocuments(filter);

res.json({
success: true,
count: posts.length,
total,
page: parseInt(page),
pages: Math.ceil(total / limit),
data: posts
});
} catch (error) {
res.status(500).json({ error: error.message });
}
};

// Get single post
exports.getPost = async (req, res) => {
try {
const post = await Post.findById(req.params.id).populate('author', 'name email');
if (!post) {
return res.status(404).json({ error: 'Post not found' });
}
res.json({ success: true, data: post });
} catch (error) {
res.status(500).json({ error: error.message });
}
};

// Update post
exports.updatePost = async (req, res) => {
try {
const post = await Post.findById(req.params.id);

if (!post) {
return res.status(404).json({ error: 'Post not found' });
}

// Check ownership
if (post.author.toString() !== req.user._id.toString() && req.user.role !== 'admin') {
return res.status(403).json({ error: 'Not authorized to update this post' });
}

const updatedPost = await Post.findByIdAndUpdate(
req.params.id,
req.body,
{ new: true, runValidators: true }
).populate('author', 'name email');

res.json({ success: true, data: updatedPost });
} catch (error) {
res.status(500).json({ error: error.message });
}
};

// Delete post
exports.deletePost = async (req, res) => {
try {
const post = await Post.findById(req.params.id);

if (!post) {
return res.status(404).json({ error: 'Post not found' });
}

// Check ownership
if (post.author.toString() !== req.user._id.toString() && req.user.role !== 'admin') {
return res.status(403).json({ error: 'Not authorized to delete this post' });
}

await post.deleteOne();
res.json({ success: true, message: 'Post deleted successfully' });
} catch (error) {
res.status(500).json({ error: error.message });
}
};

8. Routes (routes/index.js)
const express = require('express');
const router = express.Router();
const { protect, authorize } = require('../middleware/auth');
const authController = require('../controllers/authController');
const postController = require('../controllers/postController');

// Auth routes
router.post('/auth/register', authController.register);
router.post('/auth/login', authController.login);
router.get('/auth/me', protect, authController.getMe);

// Post routes
router.route('/posts')
.get(postController.getPosts)
.post(protect, postController.createPost);

router.route('/posts/:id')
.get(postController.getPost)
.put(protect, postController.updatePost)
.delete(protect, postController.deletePost);

// Admin only route example
router.get('/admin/users', protect, authorize('admin'), (req, res) => {
// Admin only functionality
res.json({ message: 'Admin access granted' });
});

module.exports = router;

9. Main App (app.js)
const express = require('express');
const connectDB = require('./config/db');
const config = require('./config/config');
const routes = require('./routes');

const app = express();

// Connect to database
connectDB();

// Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Routes
app.use('/api/v1', routes);

// Error handling middleware
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Something went wrong!' });
});

// Start server
app.listen(config.port, () => {
console.log(`Server running on port ${config.port}`);
});

Two Minute Drill
  • This is a complete RESTful API with authentication and CRUD operations.
  • JWT is used for stateless authentication.
  • Mongoose models define data structure with validation.
  • Middleware protects routes and checks authorization.
  • Controllers handle business logic and error handling.
  • Routes are organized by resource (auth, posts).
  • Includes pagination, filtering, and ownership checks.

Need more clarification?

Drop us an email at career@quipoinfotech.com