Build a Login and Registration System Using Node.js: A Step-By-Step Guide
BuildWithGagan
Posted on November 22, 2024
Introduction
Building a login and registration system is a fundamental task for any web application. It ensures secure access control and user authentication, laying the foundation for a functional app. This guide is tailored for beginners, providing an easy-to-follow approach using Node.js. By the end of this tutorial, you'll have a functional authentication system in place.
What Will You Learn?
- How to set up a Node.js project
- Implement user registration
- Add secure login functionality
- Hash passwords for security using bcrypt
- Validate user input
- Use JWT for token-based authentication
- Handle common errors and edge cases
Setting Up Your Environment
1. Install Node.js
Before you start, ensure Node.js is installed on your system. Download it from Node.js Official Website and follow the installation instructions.
2. Initialize Your Project
mkdir node-auth-system
cd node-auth-system
npm init -y
This creates a package.json
file to manage your project dependencies.
3. Install Required Packages
npm install express body-parser mongoose bcrypt jsonwebtoken dotenv cors
4. Create a Basic Project Structure
Organize your files for a clean workflow:
node-auth-system/
├── .env
├── app.js
├── models/
│ └── User.js
├── routes/
│ └── auth.js
├── config/
│ └── db.js
└── package.json
Setting Up MongoDB
We'll use MongoDB to store user information. If you don’t have MongoDB installed, set it up here. Alternatively, use a cloud database like MongoDB Atlas.
Configuring Database Connection
In config/db.js
:
const mongoose = require('mongoose');
const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
console.log('MongoDB Connected...');
} catch (err) {
console.error(err.message);
process.exit(1);
}
};
module.exports = connectDB;
Add your MongoDB connection string in .env
:
MONGO_URI=your_mongo_connection_string
JWT_SECRET=your_jwt_secret
PORT=5000
Building the User Model
Create a User.js
file inside the models
directory:
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
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
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);
Creating the Authentication Routes
Initialize Express
In app.js
:
const express = require('express');
const dotenv = require('dotenv');
const connectDB = require('./config/db');
const authRoutes = require('./routes/auth');
dotenv.config();
connectDB();
const app = express();
app.use(express.json());
app.use('/api/auth', authRoutes);
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
Define Authentication Endpoints
In routes/auth.js
:
const express = require('express');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const User = require('../models/User');
const router = express.Router();
// Register User
router.post('/register', async (req, res) => {
const { username, email, password } = req.body;
try {
if (!username || !email || !password) {
return res.status(400).json({ message: 'All fields are required' });
}
const userExists = await User.findOne({ email });
if (userExists) {
return res.status(400).json({ message: 'User already exists' });
}
const newUser = await User.create({ username, email, password });
res.status(201).json({ message: 'User registered successfully', user: newUser });
} catch (err) {
res.status(500).json({ message: 'Server error' });
}
});
// Login User
router.post('/login', async (req, res) => {
const { email, password } = req.body;
try {
const user = await User.findOne({ email });
if (!user) {
return res.status(400).json({ message: 'Invalid credentials' });
}
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) {
return res.status(400).json({ message: 'Invalid credentials' });
}
const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: '1h' });
res.json({ message: 'Login successful', token });
} catch (err) {
res.status(500).json({ message: 'Server error' });
}
});
module.exports = router;
Testing the Application
Using Postman or Thunder Client
-
Register a user:
- Endpoint:
POST /api/auth/register
- Body:
{ "username": "john_doe", "email": "john@example.com", "password": "123456" }
- Endpoint:
-
Login a user:
- Endpoint:
POST /api/auth/login
- Body:
{ "email": "john@example.com", "password": "123456" }
- Endpoint:
FAQs
Why Use Bcrypt for Password Hashing?
Bcrypt securely hashes passwords, adding a salt to make brute-force attacks significantly harder.
How Does JWT Work?
JWT encodes user information into a token, ensuring secure, stateless authentication. It’s useful for scaling as the server doesn’t need to store session data.
How to Handle Expired Tokens?
Include middleware to check token validity and return a proper error message if expired.
This guide takes you from zero to hero in building a login and registration system with Node.js, covering critical features and security considerations. Experiment and expand your application by adding roles, password reset features, and frontend integration.
Posted on November 22, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.