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
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
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
Install required dependencies:
npm install express mongoose dotenv jasonwebtoken bcrypt
-
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
If using nodemon, add this to package.json:
"scripts": {
"dev": "nodemon server.js"
}
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}`)
})
Environment Variables
Create a .env file.
JWT_SECRET = my_@secretkey123
MONGO_URI=your_mongodb_connection_string
PORT=5000
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
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);
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 };
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
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;
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
Body:
{
"name": "John",
"email": "john@test.com",
"password": "123456"
}
Login
POST /api/auth/login
Response:
{
"token": "eyJhbGciOiJIUzI1NiIs..."
}
Access Protected Route
GET /api/users
Header:
Enter 'Authorization' as key and 'Bearer ' in the headers tab in Postman
Authorization: Bearer TOKEN
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)
This is the kind of content that keeps me coming back to Dev.to.