DEV Community

Cover image for Day 40 of #100DayOfCode — Building a Mini Auth System
M Saad Ahmad
M Saad Ahmad

Posted on

Day 40 of #100DayOfCode — Building a Mini Auth System

Today, for Day 40 of #100DaysOfCode, my goal was to apply all the backend topics and concepts I have learned by building something practical. So, I developed a mini authentication system that stores user data in a MongoDB database using Mongoose schemas. This system was implemented using Node.js and Express.js for the routing, and I utilized a JWT token for user authentication during the login process.


TL;DR

The Auth System was built using:

  • Node.js
  • Express
  • MongoDB
  • Mongoose
  • bcrypt
  • JWT

This project implements the following essential backend operations:

Authenticating users
Hashing Passwords
Saving user information to the Database
Generate and then verify the JWT token
Enter fullscreen mode Exit fullscreen mode

Project Folder Structure

To keep the code organized, I used a separation-of-concerns structure.

auth_system/
│
├── models/
│   └── user.js
│
├── routes/
│   ├── auth.route.js
│   └── user.route.js
│
├── controllers/
│   ├── auth.controller.js
│   └── user.controller.js
│
├── middleware/
│   └── auth.middleware.js
│
├── config/
│   └── db.js
│
├── .env
├── server.js
Enter fullscreen mode Exit fullscreen mode

This structure separates responsibilities:

Folder Responsibility
Models Database schema
Routes API endpoints
Middleware Verify JWT token
Controllers Business logic
Config Database connection
Server Application entry point

This pattern scales well for larger APIs and production apps.


Step 1 — Initialize the Project

Create a new Node project:

npm init -y
Enter fullscreen mode Exit fullscreen mode

Install required dependencies:

npm install express mongoose dotenv jasonwebtoken bcrypt
Enter fullscreen mode Exit fullscreen mode
  • express — To handle routes
  • mongoose — For creating a schema for MongoDB
  • dotenv — For creating environment variables
  • jsonwebtoken — For creating a secure JSON Web Token
  • bcrypt — For password hashing

Install nodemon for auto server restarting (Optional):

npm install nodemon --save-dev
Enter fullscreen mode Exit fullscreen mode

If using nodemon, add this to package.json:

"scripts": {
  "dev": "nodemon server.js"
}
Enter fullscreen mode Exit fullscreen mode

Step 2 — Create the Express Server

File: server.js

const express = require("express")
const authRoutes = require("./routes/auth.route");
const userRoutes = require("./routes/user.routes");
const connectDB = require("./config/db")
const dotenv = require("dotenv")

dotenv.config()
connectDB()

const app = express()

app.use(express.json())

app.use("/api/auth", authRoutes);
app.use("/api/users", userRoutes);

const PORT = process.env.PORT || 3000
app.listen(PORT, () => {
    console.log(`Server is running on http://localhost:${PORT}`)
})
Enter fullscreen mode Exit fullscreen mode

Environment Variables

Create a .env file.

JWT_SECRET = my_@secretkey123
MONGO_URI=your_mongodb_connection_string
PORT=5000
Enter fullscreen mode Exit fullscreen mode

Step 3 — Connect MongoDB

File: config/db.js

const mongoose = require("mongoose")

const connectDB = async () => {
  try {
    await mongoose.connect(process.env.MONGO_URI)

    console.log("MongoDB Connected")
  } catch (error) {
    console.error(error)
    process.exit(1)
  }
}

module.exports = connectDB
Enter fullscreen mode Exit fullscreen mode

Step 4 — Create the Mongoose Model

File: models/user.js

const mongoose = require("mongoose");

const userSchema = new mongoose.Schema({
  name: {
    type: String,
    required: true
  },

  email: {
    type: String,
    required: true,
    unique: true
  },

  password: {
    type: String,
    required: true
  }
});

module.exports = mongoose.model("user", userSchema);
Enter fullscreen mode Exit fullscreen mode

This schema defines the structure of the User in MongoDB.


Step 5 — Create Controllers

// File: controllers/auth.controller.js
const user = require("../models/user");
const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");

// register the user
const register = async (req, res) => {
  try {
    const { name, email, password } = req.body;
    const existingUser = await user.findOne({ name, email });
    if (existingUser) {
      return res.status(400).json({ message: "User already exists" });
    }

    const hashedPassword = await bcrypt.hash(password, 10);

    const userObj = {
      name,
      email,
      password: hashedPassword,
    };
    const newUser = await user.create(userObj);

    res.status(201).json(newUser);
  } catch (error) {
    console.log(error);
    res.status(500).json({ message: "Internal Server Error" });
  }
};

// login the user
const login = async (req, res) => {
  try {
    const { name, email, password } = req.body;
    const existingUser = await user.findOne({ name, email });
    if (!existingUser) {
      return res.status(404).json({ message: "User not found" });
    }

    const isPasswordValid = await bcrypt.compare(
      password,
      existingUser.password,
    );
    if (!isPasswordValid) {
      return res.status(401).json({ message: "Invalid credentials" });
    }

    const token = jwt.sign(
      {
        id: existingUser._id,
      },
      process.env.JWT_SECRET,
      { expiresIn: "1h" },
    );

    res.status(200).json({ message: "Login successful", token });
  } catch (error) {
    console.log(error);
    res.status(500).json({ message: "Internal Server Error" });
  }
};

module.exports = { register, login };



// File: controllers/user.controller.js
const user = require("../models/user");

const getUsers = async (req, res) => {
  const users = await user.find().select("-password");

  res.json(users);
};

const getUsersById = async (req, res) => {
  const oneUser = await user.findById(req.params.id).select("-password");

  res.json(oneUser);
};

module.exports = { getUsers, getUsersById };
Enter fullscreen mode Exit fullscreen mode

These controllers contain the register/login and authorization logic of the user.


Step 6 — Create Middleware

// File: middleware/auth.middleware.js
const jwt = require("jsonwebtoken")

const authMiddleware = (req, res, next) => {
    const token = req.headers.authorization?.split(" ")[1];
    console.log(token)

  if (!token) {
    return res.status(401).json({
      message: "Unauthorized"
    });
  }

  try {
    const decoded = jwt.verify(
      token,
      process.env.JWT_SECRET
    );
    req.user = decoded;

    next();
  } catch (error) {

    res.status(401).json({
      message: "Invalid token"
    });
  }
}

module.exports = authMiddleware
Enter fullscreen mode Exit fullscreen mode

The middleware sits between the request and the controller and verifies the JWT token generated when the user registers


Step 7 — Create Routes

// File: routes/auth.route.js
const express = require("express")
const authController = require("../controller/auth.controller")
const router = express.Router()

router.post("/register", authController.register);
router.post("/login", authController.login);

module.exports = router



// File: routes/user.route.js
const express = require("express");
const router = express.Router();
const userController = require("../controller/userController");
const authMiddleware = require("../middleware/auth.middleware");

router.get("/", authMiddleware, userController.getUsers);
router.get("/:id", authMiddleware, userController.getUsersById);

module.exports = router;
Enter fullscreen mode Exit fullscreen mode

Routes connect HTTP requests → controller logic.


REST API Endpoints

Method Endpoint Description
POST /api/auth/register Register a new user
POST /api/auth/login Login the existing user
GET /api/users Protected Route. Only accessible to the registered user
GET /api/users/:id Get a single user

Testing the API

The API can be tested using:

  • Postman
  • Thunder Client (VS Code extension)

Example request:

Register

POST /api/auth/register
Enter fullscreen mode Exit fullscreen mode

Body:

{
 "name": "John",
 "email": "john@test.com",
 "password": "123456"
}
Enter fullscreen mode Exit fullscreen mode

Login

POST /api/auth/login
Enter fullscreen mode Exit fullscreen mode

Response:

{
 "token": "eyJhbGciOiJIUzI1NiIs..."
}
Enter fullscreen mode Exit fullscreen mode

Access Protected Route

GET /api/users
Enter fullscreen mode Exit fullscreen mode

Header:
Enter 'Authorization' as key and 'Bearer ' in the headers tab in Postman

Authorization: Bearer TOKEN
Enter fullscreen mode Exit fullscreen mode

What I Learned Today

Key backend concepts from this project:

  • Building a REST API with Express
  • Structuring a backend using MVC pattern
  • Connecting MongoDB with Mongoose
  • Authentication using JWT
  • Storing user credentials in the MongoDB Atlas Cluster Database
  • Organizing code for scalable APIs

Conclusion

This project is beneficial for understanding authentication in a real-world MERN app.

Next improvements could include:

  • Adding roles (admin vs regular user)
  • Adding refresh tokens
  • Connecting a frontend to consume this API

Thanks for reading. Feel free to share your thoughts!

Top comments (1)

Collapse
 
ptak_dev profile image
Patrick T

This is the kind of content that keeps me coming back to Dev.to.