To build a more advanced CRUD API in Node.js, we can add several features such as:
- Database Integration: Instead of using an in-memory database, we’ll integrate a real database (e.g., MongoDB, PostgreSQL).
-
Input Validation and Error Handling: We’ll use libraries like
Joi
orexpress-validator
for input validation, and improve error handling. - Authentication and Authorization: We’ll add JWT authentication to secure the API.
- API Documentation: We’ll integrate Swagger for API documentation.
-
Environment Variables: We'll use
dotenv
for handling sensitive information like database credentials and API keys.
Here’s how you can implement this:
1. Setup Project and Install Dependencies
Create a new project and install the necessary dependencies:
mkdir advanced-crud-api
cd advanced-crud-api
npm init -y
Install the following dependencies:
npm install express mongoose dotenv joi jsonwebtoken bcryptjs body-parser
npm install --save-dev nodemon
-
express
: The web framework. -
mongoose
: MongoDB ORM. -
dotenv
: For environment variable management. -
joi
: For input validation. -
jsonwebtoken
: For creating and verifying JWT tokens. -
bcryptjs
: For hashing passwords. -
body-parser
: Middleware for parsing request bodies.
2. Create Environment Configuration
Create a .env
file to store sensitive data:
touch .env
Add the following configuration to the .env
file:
PORT=5000
DB_URI=mongodb://localhost:27017/advanced-crud
JWT_SECRET=your_jwt_secret_key
3. Create Models and Database Configuration
Create a models
folder and define a User
model in models/User.js
:
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
name: {
type: String,
required: [true, 'Name is required'],
},
email: {
type: String,
required: [true, 'Email is required'],
unique: true,
match: [/^[\w-]+(\.[\w-]+)*@([\w-]+\.)+[a-zA-Z]{2,7}$/, 'Please provide a valid email'],
},
password: {
type: String,
required: [true, 'Password is required'],
minlength: [6, 'Password should be at least 6 characters long'],
},
});
module.exports = mongoose.model('User', userSchema);
In this model, we include validation rules for name
, email
, and password
.
4. Database Connection Setup
Create a config/db.js
file to set up the database connection:
const mongoose = require('mongoose');
const dotenv = require('dotenv');
dotenv.config();
const connectDB = async () => {
try {
await mongoose.connect(process.env.DB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
console.log('MongoDB connected');
} catch (error) {
console.error('Error connecting to MongoDB:', error.message);
process.exit(1); // Exit process with failure
}
};
module.exports = connectDB;
5. JWT Authentication Helper Functions
Create a file utils/auth.js
for handling JWT generation and password hashing:
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const generateToken = (id) => {
return jwt.sign({ id }, process.env.JWT_SECRET, { expiresIn: '30d' });
};
const hashPassword = async (password) => {
const salt = await bcrypt.genSalt(10);
return await bcrypt.hash(password, salt);
};
const comparePasswords = async (password, hashedPassword) => {
return await bcrypt.compare(password, hashedPassword);
};
module.exports = { generateToken, hashPassword, comparePasswords };
6. Create Controllers for CRUD Operations
In the controllers
folder, create userController.js
to handle CRUD operations.
const User = require('../models/User');
const Joi = require('joi');
const { generateToken, hashPassword, comparePasswords } = require('../utils/auth');
// Validation schema
const userValidationSchema = Joi.object({
name: Joi.string().min(3).required(),
email: Joi.string().email().required(),
password: Joi.string().min(6).required(),
});
// Register user
exports.registerUser = async (req, res) => {
try {
const { name, email, password } = req.body;
// Validate input data
const { error } = userValidationSchema.validate(req.body);
if (error) return res.status(400).json({ error: error.details[0].message });
// Check if the user already exists
const userExists = await User.findOne({ email });
if (userExists) {
return res.status(400).json({ message: 'User already exists' });
}
const hashedPassword = await hashPassword(password);
const newUser = new User({ name, email, password: hashedPassword });
await newUser.save();
const token = generateToken(newUser._id);
res.status(201).json({ user: newUser, token });
} catch (err) {
res.status(500).json({ message: 'Server error' });
}
};
// Login user
exports.loginUser = async (req, res) => {
try {
const { email, password } = req.body;
// Validate input data
const { error } = userValidationSchema.validate(req.body);
if (error) return res.status(400).json({ error: error.details[0].message });
const user = await User.findOne({ email });
if (!user) return res.status(400).json({ message: 'Invalid email or password' });
const isMatch = await comparePasswords(password, user.password);
if (!isMatch) return res.status(400).json({ message: 'Invalid email or password' });
const token = generateToken(user._id);
res.json({ user, token });
} catch (err) {
res.status(500).json({ message: 'Server error' });
}
};
// Get user details
exports.getUserDetails = async (req, res) => {
try {
const user = await User.findById(req.user.id).select('-password');
if (!user) return res.status(404).json({ message: 'User not found' });
res.json(user);
} catch (err) {
res.status(500).json({ message: 'Server error' });
}
};
// Update user details
exports.updateUser = async (req, res) => {
try {
const { name, email } = req.body;
const user = await User.findByIdAndUpdate(req.user.id, { name, email }, { new: true });
if (!user) return res.status(404).json({ message: 'User not found' });
res.json(user);
} catch (err) {
res.status(500).json({ message: 'Server error' });
}
};
// Delete user
exports.deleteUser = async (req, res) => {
try {
const user = await User.findByIdAndDelete(req.user.id);
if (!user) return res.status(404).json({ message: 'User not found' });
res.status(204).send();
} catch (err) {
res.status(500).json({ message: 'Server error' });
}
};
7. Protect Routes with Middleware
Create a middleware middleware/auth.js
to protect routes that require authentication:
const jwt = require('jsonwebtoken');
const protect = (req, res, next) => {
const token = req.header('Authorization')?.replace('Bearer ', '');
if (!token) return res.status(401).json({ message: 'No token, authorization denied' });
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded; // Attach user info to request
next();
} catch (err) {
res.status(401).json({ message: 'Token is not valid' });
}
};
module.exports = protect;
8. Setup Express Routes
Create the routes for user operations in routes/userRoutes.js
:
const express = require('express');
const { registerUser, loginUser, getUserDetails, updateUser, deleteUser } = require('../controllers/userController');
const protect = require('../middleware/auth');
const router = express.Router();
router.post('/register', registerUser);
router.post('/login', loginUser);
router.get('/me', protect, getUserDetails);
router.put('/me', protect, updateUser);
router.delete('/me', protect, deleteUser);
module.exports = router;
9. Set Up the Main App
Now, set up the main server file in index.js
:
const express = require('express');
const dotenv = require('dotenv');
const connectDB = require('./config/db');
const userRoutes = require('./routes/userRoutes');
dotenv.config();
const app = express();
// Connect to database
connectDB();
app.use(express.json()); // Parse incoming JSON requests
app.use('/api/users', userRoutes); // Use the user routes
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
10. Run the Server
Finally, start the
server:
npm run dev
You now have a fully functional advanced CRUD API with JWT authentication, user registration, and error handling.
Conclusion
This advanced setup covers:
- MongoDB integration.
- Input validation with Joi.
- JWT-based authentication and authorization.
- User registration, login, update, and deletion operations.
- Secure password handling with bcrypt.
Top comments (0)