DEV Community

BuildWithGagan
BuildWithGagan

Posted on

1

Build a Login and Registration System Using Node.js: A Step-By-Step Guide

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

Add your MongoDB connection string in .env:

MONGO_URI=your_mongo_connection_string
JWT_SECRET=your_jwt_secret
PORT=5000
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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}`));
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

Testing the Application

Using Postman or Thunder Client

  1. Register a user:

    • Endpoint: POST /api/auth/register
    • Body:
     {
       "username": "john_doe",
       "email": "john@example.com",
       "password": "123456"
     }
    
  2. Login a user:

    • Endpoint: POST /api/auth/login
    • Body:
     {
       "email": "john@example.com",
       "password": "123456"
     }
    

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.

Top comments (0)

Playwright CLI Flags Tutorial

5 Playwright CLI Flags That Will Transform Your Testing Workflow

  • 0:56 --last-failed: Zero in on just the tests that failed in your previous run
  • 2:34 --only-changed: Test only the spec files you've modified in git
  • 4:27 --repeat-each: Run tests multiple times to catch flaky behavior before it reaches production
  • 5:15 --forbid-only: Prevent accidental test.only commits from breaking your CI pipeline
  • 5:51 --ui --headed --workers 1: Debug visually with browser windows and sequential test execution

Learn how these powerful command-line options can save you time, strengthen your test suite, and streamline your Playwright testing experience. Click on any timestamp above to jump directly to that section in the tutorial!

Watch Full Video 📹️

👋 Kindness is contagious

Engage with a wealth of insights in this thoughtful article, valued within the supportive DEV Community. Coders of every background are welcome to join in and add to our collective wisdom.

A sincere "thank you" often brightens someone’s day. Share your gratitude in the comments below!

On DEV, the act of sharing knowledge eases our journey and fortifies our community ties. Found value in this? A quick thank you to the author can make a significant impact.

Okay