2. ENVIRONMENT SETUP
2.1 Installing Node.js and npm
- To use Express, you first need to have Node.js installed on your system.
- Node.js is a JavaScript runtime that allows you to run JavaScript on the server. npm (Node Package Manager) is included with Node.js and is used to manage project dependencies.
- Steps:
- Go to the official Node.js website: https://nodejs.org
- Download and install the LTS (Long Term Support) version, which is recommended for most users.
- After installation, open your terminal or command prompt and verify the installation by typing:
node -v
npm -v
2.2 Creating a New Express App
- Once Node.js is installed, you can create a new Express application.
- Steps:
- Open your terminal.
- Create a new directory for your project:
mkdir myapp
- Navigate into the new directory:
cd myapp
- Initialize a new Node.js project:
npm init -y (the -y flag accepts all the default settings). This creates a package.json file.
- Install Express as a project dependency:
npm install express
2.3 Understanding Project Structure
- After creating the app, your folder will contain the following:
myapp/
├── node_modules/
├── package.json
├── package-lock.json
└── index.js (you will create this file)
node_modules/: This directory contains all the packages (like Express) that your project depends on.
package.json: This file contains metadata about your project and a list of its dependencies.
package-lock.json: This file records the exact version of each installed package, ensuring consistent installations across different environments.
index.js: This will be the main entry point of your application where you'll write your server code.
2.4 Using nodemon for Auto-Restart
- During development, you'll be making frequent changes to your code. Restarting the server manually each time is tedious.
- nodemon is a utility that automatically restarts your Node.js application when file changes are detected.
- Install it globally:
npm install -g nodemon
Run your app with nodemon:
- Instead of
node index.js, use:
nodemon index.js
2.5 Installing Postman or Thunder Client for Testing
- When building APIs, you need a way to send requests and inspect the responses.
- While you can use a browser for simple GET requests, dedicated API clients are much more powerful.
- Postman: A standalone application for API testing and development.
- Thunder Client: A lightweight and convenient extension for Visual Studio Code.
- Use them to:
- Send requests with different HTTP methods (GET, POST, PUT, DELETE).
- Set custom headers, request bodies, and query parameters.
- View detailed responses, including status codes, headers, and body content.
3. BASIC EXPRESS SERVER
3.1 Creating a Basic Server
- Create a file called
index.js and write this code:
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Hello World');
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
This code creates a basic web server.
3.2 Using app.listen()
app.listen() tells your app to start running on a port.
app.listen(3000, () => {
console.log('Server started');
});
This means: "Start the server and listen on port 3000."
3.3 Hello World Example
app.get('/', (req, res) => {
res.send('Hello World');
});
means:
- If someone visits the homepage (
/), send them the message "Hello World".
3.4 Port Configuration with .env
- Instead of writing the port directly, we can store it in a
.env file.
- Steps:
- Install dotenv:
npm install dotenv
- Create
.env file:
PORT=3000
- Change your code:
require('dotenv').config();
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
app.get('/', (req, res) => {
res.send('Hello World');
});
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
4. ROUTING IN EXPRESS
4.1 Basic Routes (app.get(), app.post(), etc.)
- Routes tell your app what to do when someone visits a certain address.
app.get() handles when someone asks for data (like opening a webpage).
app.post() handles when someone sends data (like filling a form).
- Example:
app.get('/', (req, res) => {
res.send('Welcome to Home Page!');
});
app.post('/submit', (req, res) => {
res.send('Form submitted!');
});
4.2 Route Parameters (/user/:id)
- Sometimes you want to get information from the URL.
- Example:
/user/5
- Here,
5 is the ID of the user.
- You write this in Express like:
app.get('/user/:id', (req, res) => {
const userId = req.params.id;
res.send(`User ID is ${userId}`);
});
4.3 Query Parameters (?name=value)
- Query parameters come after a question mark
? in the URL.
- Example:
/search?name=Rudra
- You get this in Express as:
app.get('/search', (req, res) => {
const name = req.query.name;
res.send(`Searching for ${name}`);
});
4.4 Chaining Route Handlers
- You can add multiple functions to handle a route step-by-step.
- Example:
app.get('/example', (req, res, next) => {
console.log('First handler');
next(); // Move to the next handler
}, (req, res) => {
res.send('Second handler response');
});
4.5 app.route() for Cleaner Code
- Instead of writing
app.get(), app.post() separately for the same path, you can chain them.
- Example:
app.route('/book')
.get((req, res) => {
res.send('Get a book');
})
.post((req, res) => {
res.send('Add a book');
});
5. MIDDLEWARE IN EXPRESS
5.1 What is Middleware?
- Middleware is like a helper that runs between the request and response.
- It can do things like check data, log info, or change requests.
5.2 Types of Middleware
- Built-in: Comes with Express
- Third-party: Made by others and you install them
- Custom: Made by you for your app’s needs
5.3 Built-in Middleware (express.json(), express.urlencoded())
- These help your app read data sent by the user.
app.use(express.json()); // For parsing JSON request bodies
app.use(express.urlencoded({ extended: true })); // For parsing URL-encoded request bodies
5.4 Third-party Middleware (morgan, cors, helmet)
morgan: Logs info about requests
cors: Lets your app talk to other websites
helmet: Helps keep your app safe
- You install with npm, like:
npm install morgan cors helmet
Then use in your app:
const morgan = require('morgan');
const cors = require('cors');
const helmet = require('helmet');
app.use(morgan('dev')); // 'dev' is a common format for logging
app.use(helmet());
app.use(cors());
5.5 Creating Custom Middleware
- You can make your own middleware function:
const myMiddleware = (req, res, next) => {
console.log('Custom middleware running');
next(); // Pass control to the next middleware/route handler
};
app.use(myMiddleware);
5.6 Middleware Flow with next()
next() tells Express to move to the next middleware or route handler.
- If you forget
next(), the request will stop there.
6. REQUEST AND RESPONSE OBJECTS
6.1 req.params, req.query, req.body, req.headers
req.params: Data in URL (like /user/:id)
req.query: Data after ? in URL (like ?name=Rudra)
req.body: Data sent in form or JSON (needs middleware)
req.headers: Extra info sent by browser or client
6.2 res.send(), res.json(), res.status()
res.send() sends text or HTML to the user
res.json() sends data as JSON (useful for APIs)
res.status() sets the HTTP status code (like 200 for success or 404 for not found)
- Example:
res.status(200).send('Success!');
res.status(404).send('Page not found!');
res.json({ message: 'Hello JSON!' });
6.3 Chaining Response Methods
- You can chain methods for clean code:
res.status(200).json({ message: 'Welcome back!' });
6.4 Sending Files or HTML
- To send a file or HTML page:
app.get('/home', (req, res) => {
res.sendFile(__dirname + '/index.html');
});
__dirname means current folder.
7. WORKING WITH FORMS AND JSON
7.1 Handling Form Data
- When users fill out a form on a website and click "submit", the browser sends that data to the server.
- To collect this data in Express, we need to set up a middleware to read it properly.
7.2 Parsing URL-encoded Data
- HTML forms usually send data in a format called URL-encoded.
- To read this in Express:
app.use(express.urlencoded({ extended: true }));
Now, when someone submits a form like:
<form action="/submit" method="POST">
<input type="text" name="name">
<button type="submit">Send</button>
</form>
You can access the name field like this:
app.post('/submit', (req, res) => {
const name = req.body.name;
res.send(`You submitted: ${name}`);
});
7.3 Handling JSON Requests
- When sending data using JavaScript (like from React or AJAX), it’s often sent as JSON.
- To read JSON data in Express:
app.use(express.json());
Example:
app.post('/api', (req, res) => {
const data = req.body;
res.send('JSON received: ' + JSON.stringify(data));
});
7.4 Form Submission Example
<form action="/contact" method="POST">
<input type="email" name="email">
<button type="submit">Send</button>
</form>
Express Code:
app.use(express.urlencoded({ extended: true }));
app.post('/contact', (req, res) => {
const email = req.body.email;
res.send(`Thanks! We got your email: ${email}`);
});
8. SERVING STATIC FILES
8.1 Using express.static()
- Static files are things like:
- HTML files
- CSS stylesheets
- JavaScript scripts
- Images
- To serve these, use:
app.use(express.static('public'));
Now if you have:
public/
├── index.html
├── style.css
└── image.png
You can open them like:
/index.html
/style.css
/image.png
8.2 Serving Images, CSS, JS
- Put all your static files in a folder (like
public) and Express will serve them automatically.
- Example:
app.use(express.static('public'));
Your HTML can now use:
<link rel="stylesheet" href="/style.css">
<img src="/image.png">
<script src="/script.js"></script>
8.3 Custom Static Folder Configuration
- If you want to change the folder name or use a different path:
app.use('/static', express.static('public'));
Now you access files like /static/file.png instead of /file.png.
9. EXPRESS ROUTER
9.1 What is a Router?
- A router is a mini version of your Express app.
- It helps you organize your routes better, especially when your app grows big.
9.2 Creating Route Modules
- You can create a new file for your routes.
- Example:
users.js
const express = require('express');
const router = express.Router();
// Define routes for users
router.get('/', (req, res) => {
res.send('All Users');
});
router.get('/:id', (req, res) => {
res.send(`User ID: ${req.params.id}`);
});
module.exports = router; // Export the router
9.3 Using express.Router()
- In your main file (like
index.js or app.js):
const express = require('express');
const app = express();
const usersRoute = require('./users'); // Import the users router
app.use('/users', usersRoute); // Mount the router at /users
Now:
/users → shows "All Users"
/users/5 → shows "User ID: 5"
9.4 Grouping and Mounting Routes
- You can group all your routes by topic:
/users → for users
/products → for products
/admin → for admin pages
- This makes your app cleaner and easier to manage.
10. ERROR HANDLING
10.1 Default Error Handling
- When something goes wrong in your Express app (like a mistake in code), Express will show a basic error message in the browser.
- For example: If someone visits a page that doesn't exist like
/wrongpage, Express shows:
Cannot GET /wrongpage
This is the default error handling by Express.
10.2 404 Not Found Route
- A 404 error means the page the user is looking for is not found.
- To show a custom message like "Oops! Page Not Found", we can add this at the bottom of our file:
app.use((req, res) => {
res.status(404).send("Oops! Page Not Found.");
});
This tells Express:
- “If no other route matched, just show this message.”
10.3 Custom Error Middleware
- Sometimes, we want to handle errors in our own way — like showing a friendly message or logging it.
- Express lets us do this with special error-handling middleware. It has 4 parameters:
(err, req, res, next)
- Example:
app.use((err, req, res, next) => {
console.error(err.stack); // Log the error stack
res.status(500).send("Something went wrong!");
});
If there's a bug or mistake, this will catch it and show a custom message.
10.4 Using next(err) for Custom Errors
- We can create our own errors and send them using
next().
- Then Express will send this to the custom error middleware we made.
app.get('/action', (req, res, next) => {
const myError = new Error("This is a custom error.");
next(myError); // Pass the error to the error handling middleware
});
10.5 Centralized Error Handling
- Instead of writing error handling in every route, we put one big error handler at the bottom of our app.
- This way, all errors go through this one place. It's neat and clean!
app.use((err, req, res, next) => {
res.status(500).json({ message: err.message });
});
11. TEMPLATING ENGINES
11.1 What is a Templating Engine?
- A templating engine helps us mix HTML with JavaScript data.
- Example: Instead of writing just this:
<h1>Hello User</h1>
We can write:
<h1>Hello <%= name %></h1>
And if name = "Rudra", the browser will show:
Hello Rudra
11.2 Installing and Using EJS (a template engine)
- To use EJS:
- Install it:
npm install ejs
- Tell Express to use EJS:
app.set('view engine', 'ejs');
- Create a folder called
views and make a file home.ejs:
<h1>Welcome, <%= username %>!</h1>
- Render it from Express:
app.get('/', (req, res) => {
res.render('home', { username: 'Rudra' });
});
11.3 Passing Data to Views
- You can send data from your route to the EJS file like this:
app.get('/profile', (req, res) => {
res.render('profile', { user: { name: 'Alice', age: 30 } });
});
Then inside profile.ejs, you can use:
<h1>User: <%= user.name %></h1>
<p>Age: <%= user.age %></p>
11.4 Pug and Handlebars (Other Options)
- Besides EJS, there are other templating engines too:
- Pug – uses indentation instead of HTML tags.
- Handlebars (hbs) – uses
{{name}} instead of <%= name %>
- All do the same job: mix HTML with your data.
12. CONNECTING TO MONGODB
12.1 Installing and Configuring Mongoose
- Mongoose is a tool that helps us talk to MongoDB easily.
- Install it:
npm install mongoose
Connect to MongoDB:
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/myapp')
.then(() => console.log('MongoDB connected!'))
.catch(err => console.log(err));
12.2 Creating Models and Schemas
- A schema tells how our data should look.
- A model lets us use this schema to make, find, and delete users.
const userSchema = new mongoose.Schema({
name: String,
age: Number
});
const User = mongoose.model('User', userSchema);
12.3 Connecting to MongoDB Atlas (Cloud Database)
mongodb+srv://<username>:<password>@cluster0.mongodb.net/
Use it in your mongoose.connect() like this:
mongoose.connect('YOUR_MONGODB_ATLAS_URL');
12.4 Basic CRUD with Mongoose
const newUser = new User({ name: 'Rudra', age: 25 });
newUser.save();
Read:
User.find().then(users => console.log(users));
Update:
User.updateOne({ name: "Rudra" }, { age: 26 }).then(() => console.log("User updated"));
Delete:
User.deleteOne({ name: "Rudra" }).then(() => console.log("User deleted"));
13. ENVIRONMENT VARIABLES
13.1 Why Use .env File?
- We don’t want to write passwords or secret keys in our code.
- We put them in a hidden file called
.env.
- Example:
DB_URL=mongodb+srv://user:pass@cluster.mongodb.net/
PORT=3000
JWT_SECRET=supersecretkey
13.2 Using dotenv Package
- Install:
npm install dotenv
- Use at the top of your file:
require('dotenv').config();
const DB_URL = process.env.DB_URL;
const PORT = process.env.PORT || 3000;
// Example usage:
// mongoose.connect(DB_URL);
// app.listen(PORT, () => console.log(`Server running on ${PORT}`));
- This way, your secrets stay safe and your code is clean.
13.3 Storing Secrets (DB_URL, JWT_SECRET)
This is covered in sections 13.1 and 13.2.
13.4 Accessing Env Vars in Code
This is covered in section 13.2.
14. RESTful API DESIGN
14.1 What is a REST API?
- REST stands for Representational State Transfer — a fancy way of saying:
- “We use simple rules to send and get data from a server using HTTP.”
- In a REST API:
- We use URLs like
/users, /products
- And methods like:
- GET (to get data)
- POST (to add data)
- PUT or PATCH (to update data)
- DELETE (to delete data)
- Imagine you have a toy box (database), and you want to:
- See toys →
GET /toys
- Add a toy →
POST /toys
- Update a toy →
PUT /toys/:id
- Delete a toy →
DELETE /toys/:id
14.2 Creating Routes for CRUD
- CRUD = Create, Read, Update, Delete
- Here's a simple example using Express:
const express = require('express');
const app = express();
app.use(express.json()); // To parse JSON body
let users = []; // Simple in-memory data store
// GET all users
app.get('/users', (req, res) => {
res.json(users);
});
// POST a new user (Create)
app.post('/users', (req, res) => {
users.push(req.body);
res.status(201).send('User added!');
});
// PUT to update a user (Update)
app.put('/users/:id', (req, res) => {
const userId = req.params.id;
// In a real app, you'd find and update the user by ID in a database
users[userId] = req.body; // Simplified for example
res.send('User updated!');
});
// DELETE a user (Delete)
app.delete('/users/:id', (req, res) => {
const userId = req.params.id;
users.splice(userId, 1); // Simplified for example
res.send('User deleted!');
});
14.3 Following REST Conventions
- Some basic REST rules:
- Use nouns in URLs, not verbs
- ✅
/users (not /getUsers)
- Use correct HTTP methods (GET, POST, PUT, DELETE)
- Don’t use too many query strings
- ✅
/products/123 instead of /getProduct?id=123
14.4 Versioning APIs
- When your API changes over time, you don’t want to break older apps.
- So use versions like:
/api/v1/users
/api/v2/users
That way, version 1 and 2 can both work independently.
15. AUTHENTICATION WITH JWT
15.1 What is JWT?
- JWT stands for JSON Web Token.
- It’s like a digital ID card.
- When a user logs in, we give them a token.
- Then they send this token every time they want to access protected data.
15.2 Generating and Verifying Tokens
npm install jsonwebtoken
Create a token:
const jwt = require('jsonwebtoken');
const token = jwt.sign({ userId: 123 }, 'SecretKey');
Verify the token:
jwt.verify(token, 'SecretKey', (err, decoded) => {
if (err) console.log('Token invalid');
else console.log(decoded);
});
15.3 Login and Register Routes
let users = []; // In-memory user store for example
app.post('/register', (req, res) => {
const { username, password } = req.body;
users.push({ username, password });
res.send('User registered!');
});
app.post('/login', (req, res) => {
const { username, password } = req.body;
const user = users.find(u => u.username === username && u.password === password);
if (!user) return res.status(401).send('Invalid login');
const secretKey = 'SecretKey'; // Should be in .env
const token = jwt.sign({ username }, secretKey);
res.json({ token });
});
15.4 Protected Routes Middleware
- Only logged-in users (with a token) can access these routes.
const verifyToken = (req, res, next) => {
const token = req.headers['authorization'];
if (!token) return res.status(403).send('No token provided');
jwt.verify(token.split(' ')[1], 'SecretKey', (err, decoded) => {
if (err) return res.status(401).send('Failed to authenticate token');
req.user = decoded; // Store decoded user info in request
next();
});
};
app.get('/protected', verifyToken, (req, res) => {
res.send(`Welcome, ${req.user.username}! This is protected data.`);
});
15.5 Token Expiration and Refresh Tokens
- You can make tokens expire:
const token = jwt.sign({ userId: 123 }, 'SecretKey', { expiresIn: '1h' });
After 1 hour, it won’t work. You can use a refresh token (a second longer living token) to get a new one.
16. FILE UPLOADS
16.1 Installing and Using multer
- Multer helps Express handle file uploads.
- Install it:
npm install multer
Basic setup:
const multer = require('multer');
const upload = multer({ dest: 'uploads/' }); // Files will be saved in 'uploads' folder
16.2 Single File Upload
- Use a tool like Postman to test by sending a file in
form-data with key name file.
app.post('/upload-single', upload.single('file'), (req, res) => {
res.send('File uploaded: ' + req.file.filename);
});
16.3 Multiple File Upload
- This allows up to 5 files at once.
app.post('/upload-multiple', upload.array('files', 5), (req, res) => {
res.send(`${req.files.length} files uploaded!`);
});
16.4 Serving Uploaded Files
- You can make uploaded files publicly available:
app.use('/uploads', express.static('uploads'));
Now, if someone uploaded photo.jpg, they can see it at:
18. SECURITY IN EXPRESS APPS
18.1 Using helmet for Security Headers
- Helmet adds small protections to your server to block bad behavior.
- Install:
npm install helmet
Use it:
const helmet = require('helmet');
app.use(helmet());
It sets HTTP headers that protect against:
- Clickjacking
- Script attacks
- MIME sniffing, etc.
18.2 CORS Setup with cors
- CORS = Cross-Origin Resource Sharing
- Sometimes you want your server to allow requests only from your app, not random websites.
- Install:
npm install cors
Use it:
const cors = require('cors');
app.use(cors()); // Allows all origins
// Or specific origins: app.use(cors({ origin: 'https://yourdomain.com' }));
18.3 Rate Limiting with express-rate-limit
- To stop bots or hackers from sending too many requests.
- Install:
npm install express-rate-limit
Use it:
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP, please try again after 15 minutes'
});
app.use(limiter); // Apply to all requests
// Or apply to specific routes: app.use('/login', limiter);
18.4 XSS and CSRF Protection
- XSS (Cross-site scripting) = Bad people try to send scripts like
<script>stealData()</script>.
- Avoid this with helmet, express-validator, and escaping input.
- CSRF (Cross-site request forgery) = Someone tricks you into doing an action without permission.
- Use the
csurf package for CSRF protection if you're using sessions or forms:
npm install csurf
Use:
const cookieParser = require('cookie-parser');
const csurf = require('csurf');
app.use(cookieParser());
app.use(csurf({ cookie: true }));
app.get('/form', (req, res) => {
res.render('form', { csrfToken: req.csrfToken() });
});
19. LOGGING AND DEBUGGING
19.1 Using morgan for Request Logging
- Morgan is a tool that shows who visited your app, what route they used, and their IP.
- Install:
npm install morgan
Use:
const morgan = require('morgan');
app.use(morgan('tiny')); // 'tiny' is a compact output format
This will log things like:
GET / 200 1.234 ms - 123
19.2 Debugging with Console Logs
- Sometimes things break. You can print stuff using:
console.log('Variable value:', myVariable);
console.error('An error occurred:', error);
You can check what’s going wrong by printing it in the terminal.
19.3 Debugging with VS Code
- If you're using Visual Studio Code, you can:
- Set breakpoints by clicking beside line numbers.
- Use the Debugger panel to run the code step-by-step.
- Hover over variables to see their value.
- This helps find bugs easily!
19.4 Postman or Thunder Client for Testing APIs
- Postman and Thunder Client (VS Code extension) let you test your API easily.
- You can:
- Send POST, GET, DELETE requests
- Add headers (like tokens)
- Upload files
- See full response (status, body, headers)
- It's like using a remote control to check your API.
20. EXPRESS APP DEPLOYMENT
20.1 Production Environment Setup
- Before sharing your app with the world, you need to prepare it for production.
- Basic things to do:
- Keep secrets in
.env (like DB_URL)
- Don’t show error messages to users
- Use tools like helmet, cors, and express-rate-limit for safety
- Also, make sure you have:
- (Implicitly: proper error handling, logging, performance optimizations)
20.2 Deploying to Vercel, Render, Railway
- You can host your app online without buying a server.
- Vercel (Best for Frontend like React, not ideal for Express backend)
- Use it mainly for frontend apps
- Not best for Node.js server (they sleep or timeout)
- Render
- Great for Express apps
- Steps:
- Push code to GitHub
- Create new Web Service in Render
- Connect your GitHub repo
- Set start script in
package.json and PORT
- Railway
- Super easy for Express + MongoDB/MySQL
- Steps:
- Login at : https://railway.app
- Click “New Project” → “Deploy from GitHub”
- Set up environment variables
- Done! App goes live
20.3 Using PM2 for Background Services
- If you’re using a VPS or real server like AWS or DigitalOcean:
- Install PM2:
npm install -g pm2
Run your app:
pm2 start index.js
This keeps your app running forever, even if it crashes.
You can even use:
pm2 startup
To auto-start on system reboot.
20.4 Reverse Proxy with NGINX (Optional)
- If you're using Linux server, you can use NGINX to:
- Forward traffic from
https://yourdomain.com to your app running on port 3000
- Handle HTTPS with SSL
- Basic NGINX config:
server {
listen 80;
server_name yourdomain.com;
location / {
proxy_pass http://localhost:3000; # Your Express app port
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
21. PERFORMANCE OPTIMIZATION
21.1 Enabling GZIP Compression
- Use compression middleware to make responses smaller.
- Install:
npm install compression
Use:
const compression = require('compression');
app.use(compression()); // Compress all responses
Now, your data loads faster!
21.2 Caching Responses
- For pages that don’t change often, use cache:
app.use((req, res, next) => {
res.set('Cache-Control', 'public, max-age=600'); // Cache for 10 minutes
next();
});
This means the browser will reuse the same response for 10 minutes.
21.3 Async/Await for Non-blocking Code
- Always use async functions when dealing with things like database or API calls:
app.get('/data', async (req, res) => {
try {
const data = await fetchDataFromDB(); // Async operation
res.json(data);
} catch (error) {
res.status(500).send('Error fetching data');
}
});
It helps your server keep running smoothly even when waiting for something.
21.4 Optimizing Middleware Chain
- Don’t add middleware you don’t need.
- Example:
// Bad: applying JSON parser to static files
// app.use(express.json());
// app.use(express.static('public'));
// Good: apply JSON parser only where needed (e.g., before routes that use req.body)
app.use(express.static('public'));
app.use(express.json());
Also: use app.use() in correct order (like helmet, cors first, routes last).
22. USING EXPRESS WITH SQL
22.1 Introduction to Sequelize ORM
- Sequelize is a tool that lets you talk to SQL databases (like MySQL/PostgreSQL) using JavaScript.
- Install it:
npm install sequelize mysql2 # or pg for PostgreSQL
Create Sequelize instance:
const { Sequelize, DataTypes } = require('sequelize');
const sequelize = new Sequelize('database', 'username', 'password', {
host: 'localhost',
dialect: 'mysql' // or 'postgres'
});
22.2 Connecting to MySQL or PostgreSQL
- This connects your Express app to SQL!
try {
await sequelize.authenticate();
console.log('Connection to DB has been established successfully.');
} catch (error) {
console.error('Unable to connect to the database:', error);
}
22.3 Defining Models
- Models are like tables in SQL.
const User = sequelize.define('User', {
firstName: {
type: DataTypes.STRING,
allowNull: false
},
lastName: {
type: DataTypes.STRING
}
});
Sync model to create table:
await User.sync(); // This creates the table if it doesn't exist
22.4 Performing SQL CRUD Operations
const jane = await User.create({ firstName: "Jane", lastName: "Doe" });
console.log("Jane's auto-generated ID:", jane.id);
Read:
const users = await User.findAll();
console.log(JSON.stringify(users, null, 2));
Update:
await User.update({ lastName: "UpdatedDoe" }, {
where: {
firstName: "Jane"
}
});
Delete:
await User.destroy({
where: {
firstName: "Jane"
}
});
23. USING EXPRESS WITH TYPESCRIPT
23.1 Setting Up TypeScript in Express
- TypeScript helps us write safer JavaScript by adding types.
- To start:
npm install typescript @types/express @types/node ts-node --save-dev
Then create a tsconfig.json using:
npx tsc --init
23.2 Typing req, res, and next
- In TypeScript, we write types like this:
import { Request, Response, NextFunction } from 'express';
app.get('/hello', (req: Request, res: Response, next: NextFunction) => {
res.send('Hello TypeScript!');
});
This helps catch mistakes before running the code.
23.3 TSConfig Best Practices
- Inside
tsconfig.json, use:
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules"]
}
This will:
- Check for errors
- Keep code neat and safe
- Output JS files into
dist/
23.4 Folder Structure in TS Projects
- Use a clear structure like:
src/
├── controllers/
├── models/
├── routes/
├── services/
└── app.ts
Use .ts files in src/, and compile them into JS in dist/.
24. ADVANCED API FEATURES
24.1 Pagination
- When you have lots of data, you show only a few items per page.
// Example: /products?page=1&limit=10
app.get('/products', (req, res) => {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const startIndex = (page - 1) * limit;
const endIndex = page * limit;
const results = {};
// Assuming 'products' is your data array
if (endIndex < products.length) {
results.next = { page: page + 1, limit: limit };
}
if (startIndex > 0) {
results.previous = { page: page - 1, limit: limit };
}
results.products = products.slice(startIndex, endIndex);
res.json(results);
});
24.2 Filtering and Searching
- Use query params to filter:
// Example: /items?category=electronics&search=laptop
app.get('/items', (req, res) => {
const { category, search } = req.query;
let filteredItems = items; // Assuming 'items' is your data array
if (category) {
filteredItems = filteredItems.filter(item => item.category === category);
}
if (search) {
filteredItems = filteredItems.filter(item => item.name.includes(search));
}
res.json(filteredItems);
});
24.3 Sorting with Query Parameters
- Sort by price, name, etc.:
// Example: /products?sort=price&order=asc
app.get('/products', (req, res) => {
const { sort, order } = req.query;
let sortedProducts = [...products]; // Assuming 'products' is your data array
if (sort === 'price') {
sortedProducts.sort((a, b) => (order === 'desc' ? b.price - a.price : a.price - b.price));
} else if (sort === 'name') {
sortedProducts.sort((a, b) => (order === 'desc' ? b.name.localeCompare(a.name) : a.name.localeCompare(b.name)));
}
res.json(sortedProducts);
});
24.4 Handling Large Data Sets
- Use:
- Pagination
- Indexes in the database
- Avoid sending unnecessary fields
- You can also cache results to reduce database load.
25. ROLE-BASED ACCESS CONTROL (RBAC)
25.1 Admin vs User Role Checks
- You give users roles like
user, admin, etc. in the database.
25.2 Middleware for Role Verification
- Reusable middleware to check roles:
const authorize = (roles) => {
return (req, res, next) => {
// Assuming req.user has been set by an authentication middleware
if (!req.user || !roles.includes(req.user.role)) {
return res.status(403).send('Access Denied');
}
next();
};
};
25.3 Protecting Routes Based on Roles
- You can protect:
- Admin-only routes
- Premium features
- Dashboard settings
- Just check roles before allowing access.
// Example with a user object like { username: 'admin', role: 'admin' }
app.get('/admin-dashboard', authorize(['admin']), (req, res) => {
res.send('Welcome to the Admin Dashboard!');
});
app.get('/user-profile', authorize(['user', 'admin']), (req, res) => {
res.send('Welcome to your User Profile!');
});
26. TESTING EXPRESS APPLICATIONS
26.1 Unit Testing with Jest
npm install jest supertest --save-dev
In a test file:
// math.js
const add = (a, b) => a + b;
module.exports = add;
// math.test.js
const add = require('./math');
test('adds 1 + 2 to equal 3', () => {
expect(add(1, 2)).toBe(3);
});
Run:
npm test
26.2 Integration Testing with Supertest
- Install: (already covered in 26.1)
- Example:
// app.js (your main Express app)
const express = require('express');
const app = express();
app.get('/test', (req, res) => res.send('Hello Test'));
module.exports = app;
// app.test.js
const request = require('supertest');
const app = require('./app');
test('GET /test returns "Hello Test"', async () => {
const response = await request(app).get('/test');
expect(response.statusCode).toBe(200);
expect(response.text).toBe('Hello Test');
});
26.3 Testing Routes and Controllers
- Write tests to check:
- GET returns data
- POST creates item
- PUT updates
- DELETE removes item
26.4 Mocking Database and JWT
- Instead of using a real DB:
- Use
jest.mock() to fake database functions
- For example:
// user.model.js
module.exports = {
find: jest.fn(() => Promise.resolve([{ name: 'MockUser' }])),
create: jest.fn(() => Promise.resolve({ name: 'NewMockUser' }))
};
// In your test file:
const User = require('./user.model');
test('should get users from mocked DB', async () => {
const users = await User.find();
expect(users[0].name).toBe('MockUser');
});
Same with JWT:
// Mocking jsonwebtoken
jest.mock('jsonwebtoken', () => ({
verify: jest.fn((token, secret, callback) => {
if (token === 'valid_token') {
callback(null, { userId: 1 });
} else {
callback(new Error('Invalid token'));
}
})
}));
// In your test for protected route
const jwt = require('jsonwebtoken');
test('protected route with valid token', async () => {
// Simulate a request with valid token
// ...
expect(jwt.verify).toHaveBeenCalledWith('valid_token', expect.any(String), expect.any(Function));
});
27. WEBSOCKETS WITH EXPRESS
27.1 What are WebSockets?
- WebSockets allow two-way communication between the server and client in real-time.
- Normal websites: client asks, server responds.
- With WebSockets: they can talk anytime, without reloading.
- Useful for:
- Chat apps
- Live updates
- Multiplayer games
27.2 Using socket.io with Express
npm install socket.io
Set up in your server:
const express = require('express');
const app = express();
const http = require('http');
const server = http.createServer(app);
const { Server } = require("socket.io");
const io = new Server(server);
app.get('/', (req, res) => {
res.sendFile(__dirname + '/index.html');
});
io.on('connection', (socket) => {
console.log('a user connected');
socket.on('disconnect', () => {
console.log('user disconnected');
});
});
server.listen(3000, () => {
console.log('listening on *:3000');
});
27.3 Real-time Messaging Example
- Client side (
index.html):
<!DOCTYPE html>
<html>
<head>
<title>Socket.IO chat</title>
<style>
body { margin: 0; padding-bottom: 3rem; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; }
#form { background: rgba(0, 0, 0, 0.15); padding: 0.25rem; position: fixed; bottom: 0; left: 0; right: 0; display: flex; height: 3rem; box-sizing: border-box; backdrop-filter: blur(10px); }
#input { border: none; padding: 0 1rem; flex-grow: 1; border-radius: 2rem; margin: 0.25rem; }
#input:focus { outline: none; }
#form > button { background: #333; border: none; padding: 0 1rem; margin: 0.25rem; border-radius: 3px; outline: none; color: #fff; }
#messages { list-style-type: none; margin: 0; padding: 0; }
#messages > li { padding: 0.5rem 1rem; }
#messages > li:nth-child(odd) { background: #eee; }
</style>
</head>
<body>
<ul id="messages"></ul>
<form id="form" action="">
<input id="input" autocomplete="off" /><button>Send</button>
</form>
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io();
var form = document.getElementById('form');
var input = document.getElementById('input');
var messages = document.getElementById('messages');
form.addEventListener('submit', function(e) {
e.preventDefault();
if (input.value) {
socket.emit('chat message', input.value);
input.value = '';
}
});
socket.on('chat message', function(msg) {
var item = document.createElement('li');
item.textContent = msg;
messages.appendChild(item);
window.scrollTo(0, document.body.scrollHeight);
});
</script>
</body>
</html>
Server side (add to io.on('connection') block):
io.on('connection', (socket) => {
console.log('a user connected');
socket.on('chat message', (msg) => {
io.emit('chat message', msg); // Emit to all connected clients
});
socket.on('disconnect', () => {
console.log('user disconnected');
});
});
27.4 Socket Events and Rooms
- You can send messages to specific rooms:
// Server side
io.on('connection', (socket) => {
socket.on('join room', (roomName) => {
socket.join(roomName);
console.log(`User joined room: ${roomName}`);
});
socket.on('message to room', (roomName, msg) => {
io.to(roomName).emit('chat message', msg); // Send to all in the room
});
});
// Client side (example usage)
// socket.emit('join room', 'general');
// socket.emit('message to room', 'general', 'Hello everyone!');
Useful for private chats or group chats.
28. CODE STRUCTURE AND BEST PRACTICES
28.1 Folder Structure for Large Projects
- A clean structure makes it easy to manage code:
src/
├── config/ // Database config, env vars
├── controllers/ // Request handling logic
├── models/ // Database schemas/models
├── routes/ // API routes definitions
├── services/ // Business logic, DB interactions
├── middleware/ // Custom middleware
└── app.js // Main Express app setup
28.2 Service vs Controller Layer
- Controllers: Handle HTTP request/response
- Services: Handle logic and database
- Example:
// controllers/userController.js
const userService = require('../services/userService');
exports.getUsers = async (req, res) => {
try {
const users = await userService.getAllUsers();
res.json(users);
} catch (error) {
res.status(500).send('Error fetching users');
}
};
// services/userService.js
const User = require('../models/User'); // Assuming Mongoose model
exports.getAllUsers = async () => {
return await User.find();
};
28.3 Using Async/Await for Non-blocking Code
- Always use
async/await to avoid .then() and .catch() chains:
// Old way (Callback Hell)
// getData(function(data) {
// processData(data, function(processed) {
// saveData(processed, function(result) {
// console.log(result);
// });
// });
// });
// New way (Async/Await)
async function performOperations() {
try {
const data = await getData();
const processed = await processData(data);
const result = await saveData(processed);
console.log(result);
} catch (error) {
console.error("Operation failed:", error);
}
}
28.4 Avoiding Callback Hell
// getData(function(data) {
// processData(data, function(processed) {
// saveData(processed, function(result) {
// console.log(result);
// });
// });
// });
Use:
// async function performOperations() {
// try {
// const data = await getData();
// const processed = await processData(data);
// const result = await saveData(processed);
// console.log(result);
// } catch (error) {
// console.error("Operation failed:", error);
// }
// }
30. COMMON MISTAKES BEGINNERS MAKE IN EXPRESS.JS
1. Forgetting to Start the Server
- Mistake: You write the whole app but forget
app.listen().
2. Not Using Middleware Correctly
- Mistake: Not calling
next() in custom middleware.
3. Not Handling Errors Properly
- Mistake: Ignoring errors or not sending a response back.
- You should use a central error handler.
4. Mixing Logic Inside Routes
- Mistake: Writing database code and response in one file.
- Better: Move DB logic to a service file.
5. Hardcoding Secrets
- Mistake:
const secret = "mysecret";
- Store it in a
.env file.
6. Not Using .env Files
- Mistake: Putting passwords or MongoDB URLs directly in the code.
- Use:
require('dotenv').config();
Then in code:
const DB_URL = process.env.DB_URL;
7. Forgetting to Parse Request Body
- Mistake: Not using
express.json() or express.urlencoded().
8. Using Wrong HTTP Methods
- Mistake: Using GET when you want to update data.
- Use:
- GET → Read
- POST → Create
- PUT → Update
- DELETE → Delete
9. Ignoring Async/Await or Promises
- Mistake: Not handling async properly.
- Use:
app.get('/users', async (req, res) => {
try {
const users = await User.find(); // Assuming User is a Mongoose model
res.json(users);
} catch (error) {
res.status(500).send('Error fetching users');
}
});
10. Not Using Route Files and Routers
- Mistake: All routes in
app.js file.
- Create separate files like:
users.js, products.js.
- Use
express.Router().
11. Missing return in Middleware
app.use((req, res, next) => {
if (!req.user) {
res.status(401).send('Unauthorized');
}
next(); // This will still run even after sending response
});
Add return after response.
app.use((req, res, next) => {
if (!req.user) {
return res.status(401).send('Unauthorized'); // Corrected
}
next();
});
12. Not Validating User Input
- Mistake: Allowing any data into the database.
- Use
express-validator to check data before saving.
13. Not Handling 404 Errors
- Mistake: If no route matches, nothing happens.
- Add this at the bottom:
app.use((req, res) => {
res.status(404).send('Not Found');
});
14. Not Securing API
- Mistake: Not using security packages like:
helmet, cors, express-rate-limit.
15. Not Using Proper Status Codes
- Mistake: Sending 200 (OK) even for errors.
- Use:
- 200 → Success
- 400 → Bad request
- 401 → Unauthorized
- 404 → Not found
- 500 → Server error
16. Not Closing Database Connections
- For long-running apps, make sure DB connections are properly handled and closed if needed.
17. Forgetting next() in Middleware
- Without
next(), request doesn't move forward and hangs forever.
18. Overusing Global Middleware
- Using
app.use() for all routes even when it's not needed.
- Use middleware only where required.
19. Not Handling File Uploads Carefully
- Uploading big files without limits can crash the server.
- Use
multer and set size limits.
20. Not Using try/catch in Async Functions
- If you don’t use
try/catch, errors crash your app.
- Always wrap async logic:
app.get('/data', async (req, res) => {
try {
const data = await fetchData();
res.json(data);
} catch (error) {
console.error(error);
res.status(500).send('Server Error');
}
});