Sure!
Here's a simple backend application that integrates all the concepts
we've discussed: Node.js, Express, MongoDB, and Socket.io for real-time
communication. The project will simulate a chat application where users
can send and receive messages in real-time.
Project Overview
1. User Registration and Login (JWT authentication).
2. Real-time chat using WebSockets (Socket.io).
3. MongoDB to store user data and messages.
4. Middleware for authentication.
5. Rate-limiting to avoid abuse.
6. Environment variables for secure configurations.
Step-by-Step Implementation
Step 1: Initialize the Project
Start by setting up your project.
mkdir chat-app
cd chat-app
npm init -y
npm install express mongoose socket.io jsonwebtoken bcryptjs express-
validator dotenv express-rate-limit helmet
express: Web framework for Node.js.
mongoose: MongoDB ODM.
socket.io: WebSocket for real-time communication.
jsonwebtoken: JWT for authentication.
bcryptjs: Password hashing.
express-validator: Input validation.
dotenv: For managing environment variables.
express-rate-limit: Rate limiting middleware.
helmet: Security middleware to protect HTTP headers.
Step 2: Set Up Environment Variables
Create a .env file to store sensitive information like database URL and JWT
secret.
MONGO_URI=mongodb://localhost:27017/chat-app
JWT_SECRET=your_jwt_secret
PORT=5000
Step 3: Set Up Basic Express Server
In server.js, create a basic Express server.
const express = require('express');
const mongoose = require('mongoose');
const dotenv = require('dotenv');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const cors = require('cors');
dotenv.config();
const app = express();
const port = process.env.PORT || 5000;
// Middleware
app.use(express.json());
app.use(helmet());
app.use(cors());
// Rate limiting (100 requests per 15 minutes)
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
message: 'Too many requests, please try again later.'
});
app.use(limiter);
// Connect to MongoDB
mongoose.connect(process.env.MONGO_URI, { useNewUrlParser: true,
useUnifiedTopology: true })
.then(() => console.log('Connected to MongoDB'))
.catch(err => console.log('MongoDB connection error:', err));
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
Step 4: Create the User Model
Create a models/User.js file to define the MongoDB schema for users.
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 }
});
// Hash password before saving the user
userSchema.pre('save', async function(next) {
if (!this.isModified('password')) return next();
this.password = await bcrypt.hash(this.password, 10);
next();
});
// Check if password is correct
userSchema.methods.isPasswordValid = async function(password) {
return bcrypt.compare(password, this.password);
};
module.exports = mongoose.model('User', userSchema);
Step 5: Set Up Authentication Routes
Create routes for registering and login users in routes/auth.js.
const express = require('express');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const User = require('../models/User');
const { check, validationResult } = require('express-validator');
const router = express.Router();
// Register User
router.post('/register', [
check('username').notEmpty().withMessage('Username is required'),
check('email').isEmail().withMessage('Valid email is required'),
check('password').isLength({ min: 6 }).withMessage('Password must be
at least 6 characters')
], async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(400).json({ errors:
errors.array() });
const { username, email, password } = req.body;
try {
// Check if user exists
const userExists = await User.findOne({ email });
if (userExists) return res.status(400).json({ message: 'User already
exists' });
const user = new User({ username, email, password });
await user.save();
// Create JWT token
const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET,
{ expiresIn: '1h' });
res.json({ token });
} catch (err) {
console.log(err);
res.status(500).json({ message: 'Server error' });
});
// Login User
router.post('/login', [
check('email').isEmail().withMessage('Valid email is required'),
check('password').notEmpty().withMessage('Password is required')
], async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(400).json({ errors:
errors.array() });
const { email, password } = req.body;
try {
const user = await User.findOne({ email });
if (!user) return res.status(400).json({ message: 'Invalid credentials' });
const isPasswordValid = await user.isPasswordValid(password);
if (!isPasswordValid) return res.status(400).json({ message: 'Invalid
credentials' });
const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET,
{ expiresIn: '1h' });
res.json({ token });
} catch (err) {
console.log(err);
res.status(500).json({ message: 'Server error' });
});
module.exports = router;
Step 6: Set Up Chat Functionality with Socket.io
In server.js, integrate Socket.io to handle real-time messaging.
const http = require('http');
const socketIo = require('socket.io');
// Create HTTP server to use with Socket.io
const server = http.createServer(app);
const io = socketIo(server);
// Handle new connection
io.on('connection', (socket) => {
console.log('A user connected');
// Listen for chat messages
socket.on('chatMessage', (msg) => {
io.emit('chatMessage', msg); // Broadcast message to all connected
clients
});
socket.on('disconnect', () => {
console.log('A user disconnected');
});
});
// Start server
server.listen(port, () => {
console.log(`Server running on port ${port}`);
});
Step 7: Frontend for Chat (HTML & JS)
You can create a simple HTML page to interact with the backend. Here’s a
minimal frontend using Socket.io-client:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-
scale=1.0">
<title>Chat App</title>
<script src="/socket.io/socket.io.js"></script>
</head>
<body>
<h1>Chat App</h1>
<input id="message" type="text" placeholder="Type a message" />
<button onclick="sendMessage()">Send</button>
<ul id="messages"></ul>
<script>
const socket = io();
// Listen for incoming chat messages
socket.on('chatMessage', (msg) => {
const li = document.createElement('li');
li.textContent = msg;
document.getElementById('messages').appendChild(li);
});
// Send a message to the server
function sendMessage() {
const message = document.getElementById('message').value;
socket.emit('chatMessage', message);
document.getElementById('message').value = ''; // Clear input
</script>
</body>
</html>
Step 8: Testing and Running the App
1. Run MongoDB (ensure it's running locally or in the cloud).
2. Start the server:
node server.js
3. Open the HTML file in the browser and use multiple tabs to simulate
different users.
---
Conclusion
In this project, we've built a simple chat application with the following:
User authentication with JWT.
Real-time messaging using Socket.io.
MongoDB for storing user data and messages.
Express.js for the backend API.
Rate limiting and security headers using express-rate-limit and helmet.
This project gives you a comprehensive view of a production-grade
backend application. You can extend it by adding features like:
Persisting chat messages in MongoDB.
Adding more advanced user features like roles or profiles.
Using Redis for more complex message handling and scaling.
Let me know if you need further clarification or more advanced features!