This README is a backend-only roadmap for becoming strong in Node.js, Express.js, REST APIs, databases, authentication, deployment, and real-world backend project structure.
Goal:
Learn backend fundamentals deeply, build APIs, connect databases, add authentication, deploy projects, and become capable of building production-style backend systems.
- Backend Learning Path
- Phase 1: Node.js Basics
- Phase 2: npm and package.json
- Phase 3: Express.js Basics
- Phase 4: REST API Fundamentals
- Phase 5: Request Data
- Phase 6: Request Validation
- Phase 7: Middleware
- Phase 8: Routes and Controllers
- Phase 9: Error Handling
- Phase 10: Database
- Phase 11: MongoDB and Mongoose
- Phase 12: PostgreSQL and Prisma
- Phase 13: Authentication and Authorization
- Phase 14: Cookies, CORS, and Security
- Phase 15: File Upload
- Phase 16: Real-Time Backend
- Phase 17: Deployment
- Phase 18: Advanced Backend Concepts
- Backend Project Order
- 10-Day Backend Starter Plan
- Backend Checklist
Recommended order:
Node.js basics
-> npm/package.json
-> Express.js
-> REST APIs
-> request/response
-> validation
-> middleware
-> routes/controllers
-> error handling
-> database
-> authentication
-> cookies/CORS/security
-> file upload
-> deployment
-> advanced backend conceptsNode.js lets you run JavaScript outside the browser.
- What is Node.js?
- Running JavaScript using Node
nodecommand- CommonJS vs ES Modules
- Built-in Node modules
- Environment variables
Create index.js:
console.log("Hello from Node.js");Run:
node index.jsYou should know these at a basic level:
fs -> file system
path -> file/folder paths
http -> create basic server
crypto -> hashing/random valuesimport fs from "fs";
fs.writeFileSync("notes.txt", "Learning backend development");
const data = fs.readFileSync("notes.txt", "utf-8");
console.log(data);Install dotenv later:
npm install dotenvExample:
import dotenv from "dotenv";
dotenv.config();
console.log(process.env.PORT);npm is used to install packages.
npm init -y
npm install express
npm install -D nodemon
npm uninstall package-nameExample:
{
"name": "backend-project",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "nodemon src/index.js",
"start": "node src/index.js"
},
"dependencies": {
"express": "^4.18.0"
},
"devDependencies": {
"nodemon": "^3.0.0"
}
}scripts -> commands you can run
dependencies -> packages needed in production
devDependencies -> packages needed only during development
type: module -> allows import/export syntaxExpress is a framework for building backend APIs.
npm install expressimport express from "express";
const app = express();
app.use(express.json());
app.get("/", (req, res) => {
res.json({ message: "Backend is running" });
});
app.listen(3000, () => {
console.log("Server running on port 3000");
});app.get() -> handle GET request
app.post() -> handle POST request
app.patch() -> handle PATCH request
app.delete() -> handle DELETE request
req -> request object
res -> response object
app.use() -> use middlewareREST APIs use HTTP methods to perform CRUD operations.
GET -> read data
POST -> create data
PUT -> replace full data
PATCH -> update partial data
DELETE -> delete dataGET /api/users -> get all users
GET /api/users/:id -> get one user
POST /api/users -> create user
PATCH /api/users/:id -> update user
DELETE /api/users/:id -> delete user200 OK
201 Created
400 Bad Request
401 Unauthorized
403 Forbidden
404 Not Found
500 Internal Server ErrorBackend receives data in different ways.
Used for dynamic route values.
app.get("/api/users/:id", (req, res) => {
const { id } = req.params;
res.json({ id });
});Example URL:
/api/users/123Used for filtering, searching, sorting, pagination.
app.get("/api/products", (req, res) => {
const { category, sort } = req.query;
res.json({ category, sort });
});Example URL:
/api/products?category=shoes&sort=priceUsed when client sends data.
app.use(express.json());
app.post("/api/users", (req, res) => {
const { name, email } = req.body;
res.json({ name, email });
});Always validate incoming data.
- required fields
- empty strings
- email format
- password length
- invalid IDs
- wrong data types
- duplicate values
app.post("/api/users", (req, res) => {
const { name, email } = req.body;
if (!name || name.trim() === "") {
return res.status(400).json({ message: "Name is required" });
}
if (!email || !email.includes("@")) {
return res.status(400).json({ message: "Valid email is required" });
}
res.status(201).json({ message: "User created" });
});zod
joi
express-validatorMiddleware is a function that runs between request and response.
function middleware(req, res, next) {
// do something
next();
}function logger(req, res, next) {
console.log(`${req.method} ${req.url}`);
next();
}
app.use(logger);function protectRoute(req, res, next) {
const token = req.headers.authorization;
if (!token) {
return res.status(401).json({ message: "Unauthorized" });
}
next();
}
app.get("/api/profile", protectRoute, (req, res) => {
res.json({ message: "Profile data" });
});built-in middleware
custom middleware
third-party middleware
auth middleware
error middleware
logging middlewareDo not keep everything in one file.
src/
index.js
routes/
user.routes.js
todo.routes.js
controllers/
user.controller.js
todo.controller.js
middleware/
auth.middleware.js
error.middleware.js
models/
user.model.js
todo.model.js
lib/
db.js
utils/
generateToken.jsroutes/user.routes.js
import express from "express";
import { getUsers, createUser } from "../controllers/user.controller.js";
const router = express.Router();
router.get("/", getUsers);
router.post("/", createUser);
export default router;controllers/user.controller.js
export const getUsers = (req, res) => {
res.json({ message: "Get users" });
};
export const createUser = (req, res) => {
res.status(201).json({ message: "Create user" });
};src/index.js
import express from "express";
import userRoutes from "./routes/user.routes.js";
const app = express();
app.use(express.json());
app.use("/api/users", userRoutes);
app.listen(3000, () => {
console.log("Server running on port 3000");
});Backend code can fail because of bad input, database errors, invalid IDs, network issues, etc.
app.get("/api/users/:id", async (req, res) => {
try {
const user = await findUser(req.params.id);
if (!user) {
return res.status(404).json({ message: "User not found" });
}
res.status(200).json(user);
} catch (error) {
res.status(500).json({ message: "Internal server error" });
}
});function errorHandler(error, req, res, next) {
console.log(error);
res.status(500).json({
message: "Internal server error",
});
}
app.use(errorHandler);return res.status(400).json({
success: false,
message: "Invalid input",
});res.status(200).json({
success: true,
data: users,
});You should learn both:
MongoDB + Mongoose
PostgreSQL + PrismaSuggested order:
MongoDB + Mongoose first
PostgreSQL + Prisma after thatWhy?
MongoDB is easier to start with for CRUD APIs.
PostgreSQL + Prisma is very useful for serious full-stack apps.- MongoDB Atlas or local MongoDB
- Mongoose connection
- Schema
- Model
- CRUD operations
- ObjectId
- Relationships
- Timestamps
npm install mongooselib/db.js
import mongoose from "mongoose";
export const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGO_URI);
console.log("MongoDB connected");
} catch (error) {
console.log("MongoDB connection error:", error.message);
process.exit(1);
}
};models/user.model.js
import mongoose from "mongoose";
const userSchema = new mongoose.Schema(
{
name: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
},
{ timestamps: true }
);
const User = mongoose.model("User", userSchema);
export default User;const user = await User.create({
name,
email,
password,
});const users = await User.find();const user = await User.findById(id);const updatedUser = await User.findByIdAndUpdate(
id,
{ name },
{ new: true }
);await User.findByIdAndDelete(id);- SQL basics
- tables
- rows
- columns
- primary key
- foreign key
- relations
- Prisma schema
- migrations
- Prisma Client
npm install prisma @prisma/client
npx prisma initmodel User {
id String @id @default(uuid())
name String
email String @unique
password String
posts Post[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Post {
id String @id @default(uuid())
title String
content String
authorId String
author User @relation(fields: [authorId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}npx prisma migrate dev
npx prisma generate
npx prisma studioimport { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
const users = await prisma.user.findMany();await prisma.user.create({
data: {
name,
email,
password,
},
});
await prisma.user.findMany();
await prisma.user.findUnique({
where: { id },
});
await prisma.user.update({
where: { id },
data: { name },
});
await prisma.user.delete({
where: { id },
});Authentication means:
Who are you?
Authorization means:
What are you allowed to access?
- signup
- login
- password hashing
- JWT
- cookies
- protected routes
- logout
- auth middleware
- role-based access
npm install bcryptjs jsonwebtoken cookie-parser corsReceive name/email/password
-> validate input
-> check if user already exists
-> hash password
-> save user
-> generate token
-> send responseReceive email/password
-> validate input
-> find user by email
-> compare password
-> generate token
-> send token in cookie/headerimport bcrypt from "bcryptjs";
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(password, salt);const isPasswordCorrect = await bcrypt.compare(password, user.password);
if (!isPasswordCorrect) {
return res.status(400).json({ message: "Invalid credentials" });
}import jwt from "jsonwebtoken";
const token = jwt.sign(
{ userId: user._id },
process.env.JWT_SECRET,
{ expiresIn: "7d" }
);res.cookie("jwt", token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "strict",
maxAge: 7 * 24 * 60 * 60 * 1000,
});import jwt from "jsonwebtoken";
import User from "../models/user.model.js";
export const protectRoute = async (req, res, next) => {
try {
const token = req.cookies.jwt;
if (!token) {
return res.status(401).json({ message: "Unauthorized" });
}
const decoded = jwt.verify(token, process.env.JWT_SECRET);
const user = await User.findById(decoded.userId).select("-password");
if (!user) {
return res.status(401).json({ message: "Unauthorized" });
}
req.user = user;
next();
} catch (error) {
res.status(401).json({ message: "Unauthorized" });
}
};CORS controls which frontend can call your backend.
npm install corsimport cors from "cors";
app.use(
cors({
origin: "http://localhost:5173",
credentials: true,
})
);npm install cookie-parserimport cookieParser from "cookie-parser";
app.use(cookieParser());Learn:
- httpOnly cookies
- secure cookies
- sameSite
- CORS origin
- rate limiting
- helmet
- input validation
- password hashing
- environment variables
helmet
express-rate-limit
cors
cookie-parser
bcryptjs
jsonwebtoken
zodFile upload is used for profile pictures, product images, chat images, and blog covers.
- frontend file input
multipart/form-data- multer
- Cloudinary
- storing image URL in database
npm install multer cloudinaryUser selects image
-> frontend sends file/base64
-> backend receives image
-> upload to Cloudinary
-> get secure_url
-> save secure_url in databaseimport { v2 as cloudinary } from "cloudinary";
cloudinary.config({
cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
api_key: process.env.CLOUDINARY_API_KEY,
api_secret: process.env.CLOUDINARY_API_SECRET,
});
export default cloudinary;Real-time backend is used for chat, notifications, live comments, online status, etc.
- WebSockets
- Socket.IO
- connection
- disconnect
- emit
- listen
- rooms
- online users
- typing indicators
npm install socket.ioimport { Server } from "socket.io";
import http from "http";
import express from "express";
const app = express();
const server = http.createServer(app);
const io = new Server(server, {
cors: {
origin: "http://localhost:5173",
credentials: true,
},
});
io.on("connection", (socket) => {
console.log("User connected:", socket.id);
socket.on("sendMessage", (message) => {
io.emit("newMessage", message);
});
socket.on("disconnect", () => {
console.log("User disconnected:", socket.id);
});
});
server.listen(3000, () => {
console.log("Server running on port 3000");
});- chat app
- notification system
- live comments
- collaborative todo app
- online status system
- environment variables
- production scripts
- database hosting
- backend hosting
- frontend-backend connection
- CORS in production
- logs
- debugging deployed backend
Render
Railway
Fly.io
Vercel
Neon
Supabase
MongoDB Atlas
Docker-
.envvariables added on hosting platform - database URL is correct
- CORS origin is frontend deployed URL
- cookies configured correctly
- backend logs checked
- frontend API URL updated
- no secrets pushed to GitHub
Learn these after building 2-3 backend projects.
- pagination
- filtering
- sorting
- search
- indexing
- caching
- queues
- cron jobs
- rate limiting
- refresh tokens
- RBAC
- webhooks
- background jobs
- transactions
- database relationships
- logging
- testing APIs
- Docker
- CI/CD basics
GET /api/products?page=1&limit=10GET /api/products?category=electronicsGET /api/products?sort=priceGET /api/products?search=laptopBuild projects in this order.
Features:
- health route
- JSON response
- environment variable for port
Features:
- get all users
- get user by id
- create user
- update user
- delete user
Features:
- CRUD todos
- validation
- status codes
- filter completed todos
Features:
- create note
- get all notes
- get one note
- update note
- delete note
- MongoDB/Mongoose
Features:
- signup
- login
- logout
- JWT
- cookies
- protected route
- get current user
Features:
- auth required for create/update/delete
- public posts
- comments
- categories/tags
- search
- pagination
Features:
- products
- categories
- cart
- orders
- auth
- admin role
- image upload
Features:
- auth
- users
- messages
- Socket.IO
- online users
- image messages
Features:
- companies
- jobs
- applications
- auth
- role-based access
- search/filter/sort
Features:
- teams
- roles
- workspaces
- invites
- billing mock
- activity logs
Learn:
- Node.js
- npm
- package.json
- Express server
- GET/POST routes
Build:
- basic server
- users API with array
Learn:
- route params
- query params
- request body
- PUT/PATCH/DELETE
- status codes
- validation
Build:
- Todo API with array
Learn:
- routes/controllers folder structure
- middleware
- error handling
Build:
- refactor Todo API properly
Learn:
- MongoDB
- Mongoose
- schema/model
- CRUD with database
Build:
- Notes API with MongoDB
Learn:
- signup/login
- bcrypt
- JWT
- cookies
- protected routes
Build:
- Auth API
- Understand Node.js
- Run JS using Node
- Use npm
- Understand package.json
- Use ES Modules
- Use environment variables
- Create Express server
- Use
express.json() - Create GET route
- Create POST route
- Create PATCH route
- Create DELETE route
- Understand
req - Understand
res
- Understand HTTP methods
- Use route params
- Use query params
- Use request body
- Use status codes
- Validate input
- Use
app.use() - Create logger middleware
- Create auth middleware
- Use error middleware
- Create routes folder
- Create controllers folder
- Create models folder
- Create middleware folder
- Create lib folder
- Connect MongoDB
- Create Mongoose schema
- Create Mongoose model
- Perform CRUD operations
- Understand relationships
- Learn PostgreSQL basics
- Learn Prisma basics
- Signup
- Login
- Hash password
- Compare password
- Generate JWT
- Send cookie
- Protect route
- Logout
- Get current user
- CORS
- Cookies
- httpOnly
- secure
- sameSite
- rate limiting
- helmet
- env variables
- Use production env variables
- Deploy backend
- Connect database
- Fix CORS in production
- Check logs
- Test deployed API
Node.js
-> npm
-> Express
-> REST APIs
-> request data
-> validation
-> middleware
-> routes/controllers
-> error handling
-> MongoDB/Mongoose
-> auth
-> cookies/CORS/security
-> file upload
-> real-time backend
-> deployment
-> advanced backend conceptsDo not just watch tutorials.
Use this cycle:
learn one backend topic
-> build one small API
-> test with Postman/Thunder Client
-> debug errors
-> push to GitHub
-> write README notes
-> move to next topic