Express.js


Beginners To Experts


The site is under development.

ExpressJS Tutorial

1. What is Express.js and Why Use It?

Express.js is a minimal and flexible Node.js web application framework that provides a robust set of features to develop web and mobile applications. It simplifies building server-side applications by abstracting the complexity of Node.js HTTP modules, offering routing, middleware support, and a clean API.


// Express helps handle routing, middleware, and server logic easily
// Think of it like jQuery for servers

// No code example here yet, this is conceptual
      
2. Installing Node.js and Express

To use Express.js, Node.js must be installed. After that, you can use npm (Node Package Manager) to install Express into your project directory.


// Open terminal and run these commands:

// Initialize a new Node.js project
npm init -y

// Install express as a dependency
npm install express
      
3. Setting Up a Basic Express Server

After installing Express, you can set up a basic server that listens on a specific port and responds to HTTP requests. This forms the core of your web app.


// Load the express module
const express = require('express');

// Create an express application
const app = express();

// Define a basic route
app.get('/', (req, res) => {
  res.send('Hello, Express!');
});

// Listen on port 3000
app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});
      
4. Project Folder Structure

A typical Express project has a structured layout. Common folders include routes for endpoints, views for templates, and public for static files like images and CSS.


// Example folder layout:
// my-express-app/
// ├── node_modules/
// ├── public/
// ├── routes/
// ├── views/
// ├── app.js
// └── package.json
      
5. Handling Requests and Responses

Express uses functions to handle incoming requests and send responses. Each HTTP verb (GET, POST, etc.) has a corresponding method in Express.


// Handle GET request
app.get('/about', (req, res) => {
  res.send('About Page');
});

// Handle POST request
app.post('/submit', (req, res) => {
  res.send('Form Submitted');
});
      
6. Using Middleware Basics

Middleware are functions that execute during the request-response cycle. They can modify requests, send responses, or pass control to the next middleware.


// Logging middleware
app.use((req, res, next) => {
  console.log(`${req.method} request for '${req.url}'`);
  next(); // Pass control to next middleware/route
});
      
7. Express vs Other Frameworks

Express is lightweight and unopinionated, giving developers flexibility to structure their apps as they like. In contrast, frameworks like Django or Ruby on Rails are more opinionated with built-in features.


// Express is minimal:
// You add only what you need (custom routing, middleware)

// Other frameworks may come with templating, ORM, etc., by default
      
8. Running Express on Different Ports

You can run your Express app on any available port by passing the port number to app.listen(). This is helpful when hosting multiple services.


const PORT = 5000; // Choose a custom port

app.listen(PORT, () => {
  console.log(`Server running on http://localhost:${PORT}`);
});
      
9. Basic Error Handling

You can handle errors in Express using middleware with four arguments: (err, req, res, next). This ensures your app doesn't crash on unexpected issues.


// Error handling middleware
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).send('Something broke!');
});
      
10. First Project: Hello World API

A simple Hello World project is the best way to test your Express setup. It returns a basic message when you access the root URL.


// Create a file named app.js and paste this:
const express = require('express');
const app = express();

app.get('/', (req, res) => {
  res.send('Hello World from Express!');
});

app.listen(3000, () => {
  console.log('App is running on http://localhost:3000');
});

// Run using: node app.js
      

1. Understanding Routes

In Express.js, routing refers to how an application’s endpoints (URIs) respond to client requests. A route defines a path and a callback function that handles the request.


// Basic route handling
app.get('/', (req, res) => {
  res.send('Home Page');
});

app.get('/contact', (req, res) => {
  res.send('Contact Page');
});
      
2. GET, POST, PUT, DELETE Methods

Express supports multiple HTTP methods. Each method serves a specific function in CRUD operations.


// GET - retrieve data
app.get('/items', (req, res) => {
  res.send('List of items');
});

// POST - create data
app.post('/items', (req, res) => {
  res.send('Item created');
});

// PUT - update data
app.put('/items/:id', (req, res) => {
  res.send(`Item ${req.params.id} updated`);
});

// DELETE - remove data
app.delete('/items/:id', (req, res) => {
  res.send(`Item ${req.params.id} deleted`);
});
      
3. Route Parameters and Query Strings

Route parameters are part of the URL path. Query strings are optional key-value pairs after a “?”.


// Route parameter
app.get('/user/:username', (req, res) => {
  res.send(`Hello, ${req.params.username}`);
});

// Query string
app.get('/search', (req, res) => {
  res.send(`Searching for: ${req.query.q}`);
});
      
4. Route Grouping and Modular Routing

For large apps, routes can be grouped in separate files and organized by topic. Express allows this using modules.


// In routes/products.js
const express = require('express');
const router = express.Router();

router.get('/', (req, res) => {
  res.send('All products');
});

module.exports = router;

// In main app.js
const productRoutes = require('./routes/products');
app.use('/products', productRoutes);
      
5. Creating Dynamic Routes

Dynamic routes allow flexible URL patterns by using placeholders (route parameters).


// Dynamic route example
app.get('/blog/:slug', (req, res) => {
  res.send(`Blog slug: ${req.params.slug}`);
});
      
6. Using express.Router()

express.Router() is used to create modular, mountable route handlers. It separates route logic cleanly.


// Create a router instance
const express = require('express');
const router = express.Router();

router.get('/login', (req, res) => {
  res.send('Login Page');
});

module.exports = router;
      
7. Middleware in Routes

Middleware can be added to specific routes to validate or modify requests before reaching the route handler.


// Middleware for logging
const logTime = (req, res, next) => {
  console.log('Time:', Date.now());
  next();
};

app.get('/time', logTime, (req, res) => {
  res.send('Time logged');
});
      
8. Error Handling in Routes

Errors in route handlers can be passed to Express's error middleware using next(err).


app.get('/error', (req, res, next) => {
  const err = new Error('Something went wrong!');
  next(err);
});

app.use((err, req, res, next) => {
  res.status(500).send(err.message);
});
      
9. Route Chaining

Route chaining allows handling multiple HTTP methods on a single route path using route().


app.route('/book')
  .get((req, res) => {
    res.send('Get a book');
  })
  .post((req, res) => {
    res.send('Add a book');
  })
  .put((req, res) => {
    res.send('Update the book');
  });
      
10. RESTful Routing Best Practices

RESTful routing aligns routes with standard CRUD actions. Use nouns, avoid verbs in routes, and maintain consistency.


// RESTful routes
app.get('/users', ...);        // Get all users
app.get('/users/:id', ...);    // Get user by ID
app.post('/users', ...);       // Create a user
app.put('/users/:id', ...);    // Update a user
app.delete('/users/:id', ...); // Delete a user
      

1. What is Middleware in Express

Middleware functions are functions that have access to the request, response, and next middleware in the cycle. They are used to modify requests/responses, execute code, and handle errors.


// Basic middleware structure
app.use((req, res, next) => {
  console.log('Middleware executed');
  next(); // Pass to next middleware/route
});
      
2. Application-Level Middleware

Application-level middleware is bound to the app object using app.use() or specific methods like app.get(). It runs for every matching request.


// Runs for every request
app.use((req, res, next) => {
  console.log('Request URL:', req.url);
  next();
});
      
3. Router-Level Middleware

Router-level middleware works the same way as app-level middleware but is bound to an instance of express.Router().


const router = express.Router();

// Router-level middleware
router.use((req, res, next) => {
  console.log('Router middleware');
  next();
});

router.get('/test', (req, res) => {
  res.send('Router working');
});

app.use('/api', router);
      
4. Built-in Middleware (express.static, express.json)

Express includes useful built-in middleware such as express.static to serve static files and express.json to parse JSON request bodies.


// Parse JSON bodies
app.use(express.json());

// Serve static files from "public" folder
app.use(express.static('public'));
      
5. Third-party Middleware (e.g., morgan, cors)

You can install third-party middleware like morgan for logging and cors for Cross-Origin Resource Sharing.


// Install first:
// npm install morgan cors

const morgan = require('morgan');
const cors = require('cors');

// Log requests
app.use(morgan('dev'));

// Enable CORS
app.use(cors());
      
6. Creating Custom Middleware

You can define your own middleware to perform specific tasks like authentication, logging, or request transformations.


// Custom authentication middleware
function checkAuth(req, res, next) {
  if (req.query.token === '123') {
    next();
  } else {
    res.status(401).send('Unauthorized');
  }
}

// Use it on a route
app.get('/secure', checkAuth, (req, res) => {
  res.send('Access granted');
});
      
7. Order of Middleware Execution

Middleware runs in the order it is defined in the code. This affects what gets executed and when. Place general middleware before routes.


// This runs before route
app.use((req, res, next) => {
  console.log('First middleware');
  next();
});

app.get('/', (req, res) => {
  res.send('Main Route');
});
      
8. Error-Handling Middleware

Error-handling middleware is defined with four parameters: (err, req, res, next). It catches errors passed by next(err).


// Route with error
app.get('/fail', (req, res, next) => {
  const err = new Error('Something failed!');
  next(err);
});

// Error handler
app.use((err, req, res, next) => {
  res.status(500).send(`Error: ${err.message}`);
});
      
9. Conditional Middleware Logic

Middleware can be conditionally applied using standard logic based on request content or paths.


// Conditional logging
app.use((req, res, next) => {
  if (req.url.startsWith('/admin')) {
    console.log('Admin area accessed');
  }
  next();
});
      
10. Middleware Use Cases in Real Projects

Middleware is used for authentication, logging, input validation, file handling, CORS setup, and more in real-world applications.


// Example: validate API key middleware
app.use('/api', (req, res, next) => {
  if (req.headers['x-api-key'] === 'abc123') {
    next();
  } else {
    res.status(403).send('Forbidden');
  }
});

app.get('/api/data', (req, res) => {
  res.send('Protected data');
});
      

1. What is a Templating Engine?

A templating engine allows you to generate dynamic HTML by embedding variables and logic directly in your view files. Popular ones include EJS, Pug, and Handlebars.


// In EJS: <h1>Hello, <%= name %></h1>
// In Pug: h1 Hello #{name}
      
2. Setting Up EJS with Express

EJS is one of the simplest templating engines. First install it via npm, then set it in your Express app.


// Install EJS
npm install ejs

// app.js
const express = require('express');
const app = express();

// Set view engine to EJS
app.set('view engine', 'ejs');

app.get('/', (req, res) => {
  res.render('home', { name: 'Majid' });
});
      
3. Using Pug and Handlebars

You can use other engines like Pug (indent-based) and Handlebars (logic-less). Install and configure accordingly.


// Install Pug
npm install pug
app.set('view engine', 'pug');

// Install Handlebars with express-handlebars
npm install express-handlebars
const { engine } = require('express-handlebars');
app.engine('handlebars', engine());
app.set('view engine', 'handlebars');
      
4. Passing Data to Views

Use res.render() to pass variables to templates. These variables can then be displayed in the HTML.


// app.js
app.get('/user', (req, res) => {
  res.render('profile', { username: 'Majid', age: 25 });
});

// EJS profile.ejs
// <p>Name: <%= username %></p>
// <p>Age: <%= age %></p>
      
5. Layouts and Partials

Layouts contain common markup like headers and footers. Partials are reusable chunks like navbars or footers.


// EJS: include partial
// <%- include('partials/header') %>

// Layout example with EJS:
<body>
  <%- include('partials/header') %>
  <%- body %>
  <%- include('partials/footer') %>
</body>
      
6. Loops and Conditionals in Templates

Templates support control logic like loops and conditionals. This is helpful for displaying lists or content conditionally.


// EJS: Loop and condition
<ul>
  <% users.forEach(user => { %>
    <li><%= user.name %></li>
  <% }); %>
</ul>

<% if (loggedIn) { %>
  <p>Welcome back!</p>
<% } else { %>
  <p>Please log in.</p>
<% } %>
      
7. Rendering Dynamic HTML

Templating engines help render HTML with live data like usernames, posts, or search results directly into the view file.


// app.js
app.get('/post', (req, res) => {
  res.render('post', { title: 'My Blog', content: 'Hello World' });
});

// post.ejs
// <h1><%= title %></h1>
// <p><%= content %></p>
      
8. Serving Static Files with Templates

You can serve CSS, JS, and images alongside templates using express.static(). These can be linked inside your template.


// app.js
app.use(express.static('public'));

// In EJS
// <link rel="stylesheet" href="/styles.css">
      
9. Organizing Views Folder

Keep views clean by organizing them into subfolders like layouts, partials, and pages. Express uses the views folder by default.


// Structure
/views
  /partials
    header.ejs
    footer.ejs
  /pages
    home.ejs
    about.ejs
  layout.ejs
      
10. Templating Best Practices

Use partials and layouts to reduce repetition. Avoid heavy logic in templates—keep them clean and readable. Sanitize input to prevent XSS.


// Best practice: keep templates simple
<%- include('partials/header') %>

<h2><%= pageTitle %></h2>

<%- include('partials/footer') %>
      

1. Handling HTML Forms in Express

Express can handle form submissions through routes that accept GET or POST requests. You use req.body to read form data (POST) and req.query for query parameters (GET).


// HTML form example (submit.html)
/*
<form action="/submit" method="POST">
  <input type="text" name="username" />
  <button type="submit">Submit</button>
</form>
*/

// Express POST route
app.post('/submit', (req, res) => {
  res.send(`Hello ${req.body.username}`);
});
      
2. Using body-parser or express.urlencoded()

Express needs middleware to parse form data. express.urlencoded() is built-in and replaces body-parser for URL-encoded forms.


// Enable parsing of form data
app.use(express.urlencoded({ extended: true }));
      
3. GET vs POST Form Submission

Use GET for simple, safe queries (data in URL). Use POST for sensitive or large form data (data in request body).


// GET: http://localhost:3000/form?name=Ali
app.get('/form', (req, res) => {
  res.send(`GET Received: ${req.query.name}`);
});

// POST (from HTML form)
app.post('/form', (req, res) => {
  res.send(`POST Received: ${req.body.name}`);
});
      
4. Validating Form Data

You can manually validate form data or use libraries like express-validator to ensure inputs meet specific criteria.


const { check, validationResult } = require('express-validator');

app.post('/register', [
  check('email').isEmail(),
  check('password').isLength({ min: 5 })
], (req, res) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.send('Validation failed');
  }
  res.send('Registration successful');
});
      
5. Displaying Validation Errors

When form validation fails, return the errors back to the client or render them in a template.


// Return errors as JSON or render to view
if (!errors.isEmpty()) {
  return res.status(400).json({ errors: errors.array() });
}
      
6. Redirecting After Form Submit

It's common to redirect after successful form submission using res.redirect() to avoid resubmission on refresh.


app.post('/submit', (req, res) => {
  // Do something with form data
  res.redirect('/thank-you'); // Redirect after POST
});
      
7. File Uploads using multer

Use the multer middleware to handle file uploads in forms. It stores files on disk or memory.


// Install multer
// npm install multer

const multer = require('multer');
const upload = multer({ dest: 'uploads/' });

app.post('/upload', upload.single('avatar'), (req, res) => {
  res.send(`File uploaded: ${req.file.originalname}`);
});
      
8. CSRF Protection

CSRF (Cross-Site Request Forgery) protection is critical for forms. Use csurf middleware to secure them.


// npm install csurf
const csrf = require('csurf');
const cookieParser = require('cookie-parser');

app.use(cookieParser());
app.use(csrf({ cookie: true }));

app.get('/form', (req, res) => {
  res.send(`<form method="POST">
    <input type="hidden" name="_csrf" value="${req.csrfToken()}" />
    <input type="text" name="data" />
    <button>Send</button>
  </form>`);
});
      
9. Form Handling with Ajax

You can submit forms without reloading the page using JavaScript's fetch() or jQuery's $.ajax().


// JavaScript Ajax (in browser)
fetch('/submit', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded'
  },
  body: 'name=Majid'
}).then(res => res.text()).then(alert);
      
10. Security Considerations with Forms

Always validate/sanitize input to prevent XSS, use HTTPS, enable CSRF protection, and limit file types/sizes in uploads.


// Sanitize input manually or with express-validator
// Limit file size in multer config
const upload = multer({ 
  dest: 'uploads/',
  limits: { fileSize: 1024 * 1024 } // 1MB limit
});
      

1. Connecting Express to MongoDB (with Mongoose)

Mongoose is an ODM (Object Data Modeling) library for MongoDB and Node.js. It simplifies schema definition and data interaction.


// Install Mongoose
// npm install mongoose

const mongoose = require('mongoose');

mongoose.connect('mongodb://localhost:27017/mydb')
  .then(() => console.log('MongoDB connected'))
  .catch(err => console.log(err));
      
2. Express with MySQL or PostgreSQL

For SQL databases like MySQL or PostgreSQL, use libraries like mysql2 or pg. Sequelize is a popular ORM for both.


// Install mysql2
// npm install mysql2

const mysql = require('mysql2');
const connection = mysql.createConnection({
  host: 'localhost',
  user: 'root',
  database: 'testdb'
});

connection.connect(err => {
  if (err) throw err;
  console.log('MySQL connected');
});
      
3. Setting Up Models

In Mongoose, you define a schema and create a model from it. Models represent collections in MongoDB.


const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
  name: String,
  email: { type: String, required: true },
  age: Number
});

const User = mongoose.model('User', userSchema);
      
4. CRUD Operations

Mongoose supports all CRUD operations (Create, Read, Update, Delete) using its model methods.


// CREATE
await User.create({ name: 'Ali', email: 'ali@example.com' });

// READ
const users = await User.find();

// UPDATE
await User.findByIdAndUpdate(id, { name: 'Updated' });

// DELETE
await User.findByIdAndDelete(id);
      
5. Querying the Database

You can filter, sort, and project data using Mongoose query methods.


// Find users older than 18
const adults = await User.find({ age: { $gt: 18 } }).sort({ name: 1 });
      
6. Handling Validation Errors

If required fields are missing or validation fails, Mongoose throws a validation error which you can catch in try/catch.


try {
  await User.create({ name: 'No Email' }); // Missing required email
} catch (err) {
  if (err.name === 'ValidationError') {
    console.log('Validation failed:', err.message);
  }
}
      
7. Using Async/Await in Routes

Use async/await with Express routes to simplify handling asynchronous database calls.


app.get('/users', async (req, res) => {
  try {
    const users = await User.find();
    res.json(users);
  } catch (err) {
    res.status(500).send('DB error');
  }
});
      
8. Database Error Handling

Always wrap DB logic in try/catch blocks and return appropriate HTTP status codes and error messages.


app.post('/user', async (req, res) => {
  try {
    const newUser = await User.create(req.body);
    res.status(201).json(newUser);
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});
      
9. Populating Related Data

Mongoose supports populate() to join related documents like SQL joins. Useful in one-to-many or reference relationships.


// Example: populate posts in user
const userWithPosts = await User.findById(id).populate('posts');
      
10. Environment Variables for DB Config

Use .env files and the dotenv package to safely store DB credentials and connection strings.


// .env file
DB_URI=mongodb://localhost:27017/mydb

// Load it
require('dotenv').config();
mongoose.connect(process.env.DB_URI);
      

1. What is a REST API?

A REST API (Representational State Transfer) is an architectural style for designing networked applications using HTTP requests to access and manipulate resources (data).


// REST uses verbs: GET, POST, PUT, DELETE
// And nouns: /users, /products
// Example: GET /api/users
      
2. Building Your First API Endpoint

In Express, API endpoints are routes that respond with JSON instead of rendering HTML.


app.get('/api/hello', (req, res) => {
  res.json({ message: 'Welcome to the API' });
});
      
3. Returning JSON Responses

Use res.json() to send JSON data. It's the standard format for API responses.


app.get('/api/user', (req, res) => {
  res.json({
    id: 1,
    name: 'Majid',
    email: 'majid@example.com'
  });
});
      
4. Using Postman to Test APIs

Postman is a GUI tool for sending HTTP requests to test API endpoints without needing a frontend.


// Example:
// - Open Postman
// - Select GET
// - Enter http://localhost:3000/api/user
// - Click Send and view response JSON
      
5. RESTful Routing Standards

RESTful APIs use clear and consistent routes based on resources. Avoid using verbs in URL paths.


GET    /api/users        // Get all users
GET    /api/users/:id    // Get single user
POST   /api/users        // Create new user
PUT    /api/users/:id    // Update user
DELETE /api/users/:id    // Delete user
      
6. Organizing Controllers

Controllers are functions that handle route logic. Separate them into files to keep code clean and modular.


// controllers/userController.js
exports.getUsers = (req, res) => {
  res.json([{ name: 'Ali' }]);
};

// routes/userRoutes.js
const express = require('express');
const router = express.Router();
const { getUsers } = require('../controllers/userController');

router.get('/', getUsers);
module.exports = router;
      
7. Input Validation with express-validator

Use express-validator to validate and sanitize incoming data from the client.


// npm install express-validator
const { check, validationResult } = require('express-validator');

app.post('/api/register', [
  check('email').isEmail(),
  check('password').isLength({ min: 5 })
], (req, res) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.status(400).json({ errors: errors.array() });
  }
  res.send('User registered');
});
      
8. Authentication Middleware

Add middleware to secure routes. This can check for tokens or session data before allowing access.


const auth = (req, res, next) => {
  if (req.headers.authorization === 'Bearer 123') {
    next();
  } else {
    res.status(401).json({ error: 'Unauthorized' });
  }
};

app.get('/api/secure', auth, (req, res) => {
  res.send('Secure data');
});
      
9. Versioning Your API

Use versioning in your API URLs to safely introduce changes without breaking existing clients.


// Example: /api/v1/users
app.get('/api/v1/users', (req, res) => {
  res.json(['User1', 'User2']);
});
      
10. Rate Limiting and API Security

Rate limiting prevents abuse of your API. Use express-rate-limit to limit requests per IP.


// npm install express-rate-limit
const rateLimit = require('express-rate-limit');

const limiter = rateLimit({
  windowMs: 1 * 60 * 1000, // 1 minute
  max: 5 // limit each IP to 5 requests/min
});

app.use('/api', limiter);
      

1. User Registration and Login Flow

A user registration flow creates a new account. Login validates credentials and issues a session or token.


// POST /register
app.post('/register', async (req, res) => {
  const { email, password } = req.body;
  // Save hashed password (see bcrypt below)
  const newUser = await User.create({ email, password });
  res.status(201).send('User registered');
});

// POST /login
app.post('/login', async (req, res) => {
  const { email, password } = req.body;
  const user = await User.findOne({ email });
  // Check password (see bcrypt below)
  res.send('Logged in');
});
      
2. Hashing Passwords with bcrypt

Passwords should never be stored as plain text. Use bcrypt to hash and compare passwords.


// npm install bcrypt
const bcrypt = require('bcrypt');

// Hashing
const hashed = await bcrypt.hash(password, 10);

// Comparing
const match = await bcrypt.compare(inputPassword, user.password);
if (!match) return res.status(401).send('Wrong password');
      
3. Session-based Authentication

With sessions, the server stores user info in memory or a database, and the client holds a session ID in a cookie.


// npm install express-session
const session = require('express-session');

app.use(session({
  secret: 'secret-key',
  resave: false,
  saveUninitialized: true
}));

app.post('/login', (req, res) => {
  req.session.user = { id: user._id };
  res.send('Session started');
});
      
4. Token-based Auth with JWT

JWT (JSON Web Token) is a compact, URL-safe token. The server signs a token with user info and the client stores it.


// npm install jsonwebtoken
const jwt = require('jsonwebtoken');

// Sign token
const token = jwt.sign({ id: user._id }, 'jwt-secret', { expiresIn: '1h' });

// Verify token middleware
const auth = (req, res, next) => {
  const token = req.headers.authorization?.split(" ")[1];
  try {
    req.user = jwt.verify(token, 'jwt-secret');
    next();
  } catch {
    res.status(401).send('Invalid token');
  }
};
      
5. Login Protection Middleware

Middleware ensures routes are only accessible to logged-in users, checking sessions or JWTs.


// Session-based check
const isAuthenticated = (req, res, next) => {
  if (req.session.user) next();
  else res.status(403).send('Login required');
};

// JWT-based: see "auth" middleware above
      
6. Role-Based Access Control

Protect resources based on user roles (e.g., admin, user). Store the role and check it during access.


const requireRole = role => (req, res, next) => {
  if (req.user?.role === role) next();
  else res.status(403).send('Forbidden');
};

app.get('/admin', auth, requireRole('admin'), (req, res) => {
  res.send('Admin access');
});
      
7. OAuth with Google/Facebook

Use Passport.js for third-party login with Google, Facebook, etc. It handles redirect, token, and user profile retrieval.


// npm install passport passport-google-oauth20
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;

passport.use(new GoogleStrategy({
  clientID: 'GOOGLE_CLIENT_ID',
  clientSecret: 'GOOGLE_SECRET',
  callbackURL: '/auth/google/callback'
}, (accessToken, refreshToken, profile, done) => {
  // Find or create user
  return done(null, profile);
}));
      
8. Securing Routes with Middleware

Middleware can restrict access, log actions, or enforce permissions. It's reusable and works on any route.


app.use('/dashboard', auth, (req, res) => {
  res.send('Secure dashboard');
});
      
9. Refresh Tokens and Logout

Refresh tokens allow renewing access tokens without re-authenticating. Store refresh tokens safely (e.g., in DB).


// Example: logout
app.post('/logout', (req, res) => {
  req.session.destroy(); // Session
  res.clearCookie('connect.sid').send('Logged out');
});
      
10. Storing Sessions in Redis or DB

Use Redis or a database to store sessions for scalability. connect-redis and connect-mongo are common.


// npm install connect-redis ioredis
const RedisStore = require('connect-redis').default;
const Redis = require('ioredis');

const redisClient = new Redis();

app.use(session({
  store: new RedisStore({ client: redisClient }),
  secret: 'secret',
  resave: false,
  saveUninitialized: false
}));
      

1. Error Logging with Winston/Morgan

Winston and Morgan are popular logging libraries. Morgan logs HTTP requests; Winston is for general logging with different levels and transports.


// npm install morgan winston
const morgan = require('morgan');
const winston = require('winston');

app.use(morgan('combined')); // HTTP request logger

const logger = winston.createLogger({
  level: 'info',
  transports: [
    new winston.transports.Console(),
    new winston.transports.File({ filename: 'error.log', level: 'error' })
  ]
});

logger.info('Server started');
      
2. Setting Up HTTPS with Express

Use HTTPS to secure your Express server with SSL certificates.


const https = require('https');
const fs = require('fs');

const options = {
  key: fs.readFileSync('key.pem'),
  cert: fs.readFileSync('cert.pem')
};

https.createServer(options, app).listen(443, () => {
  console.log('HTTPS Server running on port 443');
});
      
3. File System Operations

Use Node.js built-in fs module for reading/writing files, which can be useful in Express apps.


const fs = require('fs');

// Read file asynchronously
fs.readFile('data.txt', 'utf8', (err, data) => {
  if (err) throw err;
  console.log(data);
});

// Write file
fs.writeFile('output.txt', 'Hello World', err => {
  if (err) throw err;
  console.log('File saved');
});
      
4. Express with WebSockets

WebSockets enable real-time bi-directional communication. Use socket.io with Express for easy WebSocket integration.


// npm install socket.io
const http = require('http').createServer(app);
const io = require('socket.io')(http);

io.on('connection', socket => {
  console.log('User connected');
  socket.on('chat message', msg => {
    io.emit('chat message', msg);
  });
});

http.listen(3000, () => console.log('Server running'));
      
5. Using Redis for Caching

Redis is an in-memory data store used to cache responses and improve app performance.


// npm install redis
const redis = require('redis');
const client = redis.createClient();

app.get('/data', (req, res) => {
  client.get('key', (err, data) => {
    if (data) return res.send(data);
    // Fetch from DB or compute
    const result = 'some data';
    client.setex('key', 3600, result);
    res.send(result);
  });
});
      
6. Express in Microservices Architecture

Express apps can be split into microservices communicating via HTTP, messaging queues, or gRPC for scalability and modularity.


// Example microservice endpoint
app.get('/service1/data', (req, res) => {
  res.json({ message: 'Service 1 data' });
});
      
7. Performance Optimization Tips

Use caching, compression, load balancing, async code, and minimizing middleware to improve performance.


// Use compression middleware
const compression = require('compression');
app.use(compression());
      
8. Unit Testing with Jest and Supertest

Jest is a test framework, Supertest allows HTTP assertions for Express routes.


// npm install jest supertest
const request = require('supertest');
const app = require('./app');

test('GET / responds with 200', async () => {
  const res = await request(app).get('/');
  expect(res.statusCode).toBe(200);
});
      
9. Deploying Express with PM2 and NGINX

PM2 manages Node.js processes; NGINX acts as reverse proxy and load balancer.


// Start app with PM2
pm2 start app.js --name myapp

// Sample NGINX config snippet
/*
server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://localhost:3000;
        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;
    }
}
*/
      
10. Express.js with TypeScript

TypeScript adds static typing to JavaScript, improving code quality in Express apps.


// tsconfig.json (basic setup)
{
  "compilerOptions": {
    "target": "ES6",
    "module": "commonjs",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true
  }
}

// Example Express app in TypeScript (src/app.ts)
import express, { Request, Response } from 'express';
const app = express();

app.get('/', (req: Request, res: Response) => {
  res.send('Hello TypeScript!');
});

app.listen(3000, () => console.log('Server running'));
      

1. Blog Application with CRUD and Auth

This project is a simple blog platform where users can create, read, update, and delete posts. Authentication ensures only authors can modify their posts.


// Basic setup
const express = require('express');
const app = express();
const mongoose = require('mongoose');
const session = require('express-session');
const bcrypt = require('bcrypt');

app.use(express.json());
app.use(session({ secret: 'secret', resave: false, saveUninitialized: false }));

// Connect to MongoDB
mongoose.connect('mongodb://localhost/blogapp');

// User Schema and Model
const userSchema = new mongoose.Schema({
  username: { type: String, unique: true },
  password: String
});
const User = mongoose.model('User', userSchema);

// Post Schema and Model
const postSchema = new mongoose.Schema({
  title: String,
  content: String,
  author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }
});
const Post = mongoose.model('Post', postSchema);

// Register Route
app.post('/register', async (req, res) => {
  const hashedPassword = await bcrypt.hash(req.body.password, 10); // Hash password
  const user = new User({ username: req.body.username, password: hashedPassword });
  try {
    await user.save();
    res.status(201).send('User registered');
  } catch {
    res.status(400).send('Registration error');
  }
});

// Login Route
app.post('/login', async (req, res) => {
  const user = await User.findOne({ username: req.body.username });
  if (user && await bcrypt.compare(req.body.password, user.password)) {
    req.session.userId = user._id; // Store user ID in session
    res.send('Logged in');
  } else {
    res.status(401).send('Invalid credentials');
  }
});

// Middleware to check auth
function requireAuth(req, res, next) {
  if (!req.session.userId) return res.status(401).send('Login required');
  next();
}

// Create Post
app.post('/posts', requireAuth, async (req, res) => {
  const post = new Post({ title: req.body.title, content: req.body.content, author: req.session.userId });
  await post.save();
  res.status(201).send('Post created');
});

// Read Posts
app.get('/posts', async (req, res) => {
  const posts = await Post.find().populate('author', 'username');
  res.json(posts);
});

// Update Post
app.put('/posts/:id', requireAuth, async (req, res) => {
  const post = await Post.findById(req.params.id);
  if (!post) return res.status(404).send('Post not found');
  if (post.author.toString() !== req.session.userId) return res.status(403).send('Forbidden');
  post.title = req.body.title;
  post.content = req.body.content;
  await post.save();
  res.send('Post updated');
});

// Delete Post
app.delete('/posts/:id', requireAuth, async (req, res) => {
  const post = await Post.findById(req.params.id);
  if (!post) return res.status(404).send('Post not found');
  if (post.author.toString() !== req.session.userId) return res.status(403).send('Forbidden');
  await post.deleteOne();
  res.send('Post deleted');
});

app.listen(3000, () => console.log('Blog app running on port 3000'));
      
2. RESTful Todo API with MongoDB

A simple REST API to create, read, update, and delete todo items using Express and MongoDB.


// Setup
const express = require('express');
const app = express();
const mongoose = require('mongoose');

app.use(express.json());

// Connect to MongoDB
mongoose.connect('mongodb://localhost/todoapi');

// Todo Schema and Model
const todoSchema = new mongoose.Schema({
  task: { type: String, required: true },
  completed: { type: Boolean, default: false }
});
const Todo = mongoose.model('Todo', todoSchema);

// Create Todo
app.post('/todos', async (req, res) => {
  try {
    const todo = new Todo({ task: req.body.task });
    await todo.save();
    res.status(201).json(todo);
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});

// Get All Todos
app.get('/todos', async (req, res) => {
  const todos = await Todo.find();
  res.json(todos);
});

// Get Single Todo
app.get('/todos/:id', async (req, res) => {
  try {
    const todo = await Todo.findById(req.params.id);
    if (!todo) return res.status(404).send('Todo not found');
    res.json(todo);
  } catch {
    res.status(400).send('Invalid ID');
  }
});

// Update Todo
app.put('/todos/:id', async (req, res) => {
  try {
    const todo = await Todo.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true });
    if (!todo) return res.status(404).send('Todo not found');
    res.json(todo);
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});

// Delete Todo
app.delete('/todos/:id', async (req, res) => {
  try {
    const todo = await Todo.findByIdAndDelete(req.params.id);
    if (!todo) return res.status(404).send('Todo not found');
    res.send('Todo deleted');
  } catch {
    res.status(400).send('Invalid ID');
  }
});

app.listen(3000, () => console.log('Todo API running on port 3000'));
      
3. E-Commerce Backend with Product Catalog and Cart

This project covers a basic backend for an e-commerce site, allowing product management and a simple shopping cart using sessions.


// Setup and dependencies
const express = require('express');
const session = require('express-session');
const mongoose = require('mongoose');
const app = express();

app.use(express.json());
app.use(session({ secret: 'ecom-secret', resave: false, saveUninitialized: false }));

// Connect MongoDB
mongoose.connect('mongodb://localhost/ecommerce');

// Product Schema and Model
const productSchema = new mongoose.Schema({
  name: String,
  price: Number,
  description: String
});
const Product = mongoose.model('Product', productSchema);

// Add product (admin route)
app.post('/products', async (req, res) => {
  const product = new Product(req.body);
  await product.save();
  res.status(201).json(product);
});

// List all products
app.get('/products', async (req, res) => {
  const products = await Product.find();
  res.json(products);
});

// Initialize cart in session middleware
app.use((req, res, next) => {
  if (!req.session.cart) req.session.cart = [];
  next();
});

// Add product to cart
app.post('/cart/:productId', async (req, res) => {
  const product = await Product.findById(req.params.productId);
  if (!product) return res.status(404).send('Product not found');
  req.session.cart.push(product._id);
  res.send('Added to cart');
});

// View cart with product details
app.get('/cart', async (req, res) => {
  const products = await Product.find({ _id: { $in: req.session.cart } });
  res.json(products);
});

app.listen(3000, () => console.log('E-Commerce backend running on port 3000'));
      
4. Chat Application Using Socket.io and Express

This project demonstrates real-time chat functionality with Express and Socket.io for two-way communication.


// Setup
const express = require('express');
const http = require('http');
const { Server } = require('socket.io');

const app = express();
const server = http.createServer(app);
const io = new Server(server);

app.get('/', (req, res) => {
  res.sendFile(__dirname + '/index.html'); // Simple client HTML for chat
});

io.on('connection', (socket) => {
  console.log('User connected');

  // Listen for chat messages
  socket.on('chat message', (msg) => {
    io.emit('chat message', msg); // Broadcast to all clients
  });

  socket.on('disconnect', () => {
    console.log('User disconnected');
  });
});

server.listen(3000, () => {
  console.log('Chat server running on port 3000');
});
      
5. RESTful API with Pagination and Search

This project implements a REST API for items with pagination and search filtering.


// Setup
const express = require('express');
const mongoose = require('mongoose');
const app = express();

app.use(express.json());

mongoose.connect('mongodb://localhost/paginationdb');

// Item Schema and Model
const itemSchema = new mongoose.Schema({
  name: String,
  description: String
});
const Item = mongoose.model('Item', itemSchema);

// GET /items with pagination and search
app.get('/items', async (req, res) => {
  const { page = 1, limit = 10, search = '' } = req.query;

  // Build search query: case-insensitive regex on name or description
  const query = {
    $or: [
      { name: { $regex: search, $options: 'i' } },
      { description: { $regex: search, $options: 'i' } }
    ]
  };

  try {
    const items = await Item.find(query)
      .limit(Number(limit))
      .skip((Number(page) - 1) * Number(limit));

    const count = await Item.countDocuments(query);

    res.json({
      totalItems: count,
      totalPages: Math.ceil(count / limit),
      currentPage: Number(page),
      items
    });
  } catch (err) {
    res.status(500).send('Server error');
  }
});

app.listen(3000, () => console.log('Pagination API running on port 3000'));
      
6. Social Media API with Followers and Posts

A backend for a social media platform with user posts, follower relationships, and timelines.


// Setup & Schemas
const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
  username: String,
  followers: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }]
});
const postSchema = new mongoose.Schema({
  author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
  content: String,
  createdAt: { type: Date, default: Date.now }
});
const User = mongoose.model('User', userSchema);
const Post = mongoose.model('Post', postSchema);

// Follow a user
app.post('/follow/:id', async (req, res) => {
  const userId = req.session.userId; // assume logged-in user
  const followId = req.params.id;
  await User.findByIdAndUpdate(followId, { $addToSet: { followers: userId } });
  res.send('Followed user');
});

// Get timeline posts (user + followed)
app.get('/timeline', async (req, res) => {
  const user = await User.findById(req.session.userId);
  const followedIds = user.followers.concat([user._id]);
  const posts = await Post.find({ author: { $in: followedIds } }).sort({ createdAt: -1 });
  res.json(posts);
});
      
7. File Upload and Management API

Handle file uploads using multer, storing files and serving them securely.


// npm install multer
const multer = require('multer');
const upload = multer({ dest: 'uploads/' });

// Upload endpoint
app.post('/upload', upload.single('file'), (req, res) => {
  res.send(`File uploaded: ${req.file.originalname}`);
});

// Serve uploaded files
app.use('/files', express.static('uploads'));
      
8. GraphQL API with Express

Use GraphQL for flexible queries with a single endpoint.


// npm install express-graphql graphql
const { graphqlHTTP } = require('express-graphql');
const { buildSchema } = require('graphql');

const schema = buildSchema(`
  type Query {
    hello: String
  }
`);

const root = {
  hello: () => 'Hello GraphQL!'
};

app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true // UI for testing queries
}));
      
9. Email Sending with Nodemailer

Send emails from your Express app using Nodemailer.


// npm install nodemailer
const nodemailer = require('nodemailer');

const transporter = nodemailer.createTransport({
  service: 'gmail',
  auth: {
    user: 'youremail@gmail.com',
    pass: 'yourpassword'
  }
});

app.post('/send-email', (req, res) => {
  const mailOptions = {
    from: 'youremail@gmail.com',
    to: req.body.to,
    subject: req.body.subject,
    text: req.body.message
  };

  transporter.sendMail(mailOptions, (err, info) => {
    if (err) return res.status(500).send(err.toString());
    res.send('Email sent: ' + info.response);
  });
});
      
10. Scheduled Tasks with Node-cron

Run periodic tasks such as database cleanup or email reminders using node-cron.


// npm install node-cron
const cron = require('node-cron');

// Run every minute
cron.schedule('* * * * *', () => {
  console.log('Running a task every minute');
  // Place cleanup or report code here
});
      

1. What is GraphQL?

GraphQL is a query language for APIs and a runtime for executing those queries with your existing data. It allows clients to request exactly what they need, making APIs more efficient and flexible than REST.


// GraphQL lets clients specify precisely the data shape
// Example query:
// {
//   user(id: "1") {
//     name
//     email
//   }
// }
      
2. Setting Up GraphQL with Express

To use GraphQL with Express, install and configure packages like express-graphql or @apollo/server.


// npm install express express-graphql graphql
const express = require('express');
const { graphqlHTTP } = require('express-graphql');
const { buildSchema } = require('graphql');

const app = express();

const schema = buildSchema(`
  type Query {
    hello: String
  }
`);

const root = {
  hello: () => 'Hello GraphQL with Express!'
};

app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true, // Enable GraphiQL UI
}));

app.listen(4000, () => console.log('Server running on port 4000'));
      
3. Creating a GraphQL Schema

The schema defines types and operations (queries, mutations) your API supports.


const schema = buildSchema(`
  type User {
    id: ID
    name: String
    email: String
  }

  type Query {
    user(id: ID!): User
  }
`);
      
4. Writing Queries and Mutations

Queries fetch data, mutations modify data.


const root = {
  user: ({ id }) => {
    // Fetch user by id from DB or static data
    return { id, name: "Majid", email: "majid@example.com" };
  }
};

// Example mutation
// type Mutation {
//   createUser(name: String!, email: String!): User
// }
      
5. Using Apollo Server with Express

Apollo Server is a popular, fully featured GraphQL server that integrates well with Express.


// npm install @apollo/server graphql express
const { ApolloServer, gql } = require('apollo-server-express');
const express = require('express');

const typeDefs = gql`
  type Query {
    hello: String
  }
`;

const resolvers = {
  Query: {
    hello: () => 'Hello Apollo Server!'
  }
};

async function startApolloServer() {
  const app = express();
  const server = new ApolloServer({ typeDefs, resolvers });
  await server.start();
  server.applyMiddleware({ app, path: '/graphql' });

  app.listen(4000, () => {
    console.log('Apollo Server running on port 4000');
  });
}

startApolloServer();
      
6. Integrating Mongoose Models

Use Mongoose inside your resolvers to query MongoDB directly.


const UserModel = require('./models/User');

const resolvers = {
  Query: {
    user: async (_, { id }) => {
      return await UserModel.findById(id);
    }
  }
};
      
7. Authentication in GraphQL

Pass auth tokens via headers and verify in context function for resolvers.


const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: ({ req }) => {
    const token = req.headers.authorization || '';
    // Verify token and attach user info to context
    return { user: verifyToken(token) };
  }
});
      
8. Error Handling in GraphQL

Throw errors in resolvers to send meaningful error messages to clients.


const resolvers = {
  Query: {
    user: async (_, { id }) => {
      const user = await UserModel.findById(id);
      if (!user) throw new Error('User not found');
      return user;
    }
  }
};
      
9. File Uploads in GraphQL

Use graphql-upload package to handle file uploads in GraphQL mutations.


// npm install graphql-upload
const { GraphQLUpload } = require('graphql-upload');

const typeDefs = gql`
  scalar Upload

  type Mutation {
    uploadFile(file: Upload!): Boolean
  }
`;

const resolvers = {
  Upload: GraphQLUpload,
  Mutation: {
    uploadFile: async (_, { file }) => {
      const { createReadStream, filename } = await file;
      const stream = createReadStream();
      // Save stream to server storage
      return true;
    }
  }
};
      
10. GraphQL vs REST: When to Use

Use GraphQL for flexible queries and complex relationships with fewer requests. Use REST for simpler, standardized APIs and easier caching.


// Summary:
// GraphQL: Flexible queries, single endpoint, good for evolving APIs
// REST: Multiple endpoints, caching-friendly, simpler tooling
      

1. What is WebSocket?

WebSocket is a protocol that enables full-duplex communication channels over a single TCP connection. Unlike HTTP, it allows servers and clients to send messages to each other in real-time.


// WebSocket enables low-latency, bidirectional data exchange,
// ideal for chat, notifications, live updates, and games.
      
2. Installing and Using socket.io with Express

Socket.io is a popular library that abstracts WebSocket and provides fallback options. It integrates easily with Express.


// npm install express socket.io
const express = require('express');
const http = require('http');
const { Server } = require('socket.io');

const app = express();
const server = http.createServer(app);
const io = new Server(server);

server.listen(3000, () => {
  console.log('Server running on port 3000');
});
      
3. Broadcasting Messages

Broadcasting means sending a message from one client to all connected clients.


io.on('connection', socket => {
  socket.on('chat message', msg => {
    io.emit('chat message', msg); // Send to everyone including sender
  });
});
      
4. Private Messaging and Rooms

Socket.io supports rooms to group clients and send messages privately.


io.on('connection', socket => {
  // Join a room
  socket.join('room1');

  // Send message to room
  socket.to('room1').emit('room message', 'Hello room1');
});
      
5. Real-Time Notifications

Notifications can be sent instantly to specific users or groups.


// Emit notification to specific socket ID
io.to(socketId).emit('notification', { message: 'You have a new alert!' });
      
6. Real-Time Chat App with Express

Combine Express for serving pages and Socket.io for chat functionality.


app.get('/', (req, res) => {
  res.sendFile(__dirname + '/index.html'); // Simple chat client
});

io.on('connection', socket => {
  console.log('User connected');
  socket.on('chat message', msg => {
    io.emit('chat message', msg);
  });
  socket.on('disconnect', () => {
    console.log('User disconnected');
  });
});
      
7. Handling Disconnections

Detect when clients disconnect and clean up resources or notify others.


io.on('connection', socket => {
  socket.on('disconnect', () => {
    console.log('User disconnected');
    // Optionally notify others
  });
});
      
8. WebSocket Authentication

Authenticate users before allowing socket connection by checking tokens or sessions during connection.


io.use((socket, next) => {
  const token = socket.handshake.auth.token;
  if (isValidToken(token)) {
    return next();
  }
  return next(new Error('Authentication error'));
});
      
9. Scaling with Redis Adapter

Use socket.io-redis adapter to share socket events between multiple server instances.


// npm install socket.io-redis redis
const redisAdapter = require('socket.io-redis');
io.adapter(redisAdapter({ host: 'localhost', port: 6379 }));
      
10. Combining REST and WebSocket

Use REST for standard CRUD operations and WebSocket for real-time features in the same Express app.


app.get('/api/data', (req, res) => {
  // Regular REST endpoint
  res.json({ message: 'Data from REST API' });
});

// Real-time updates via WebSocket
io.on('connection', socket => {
  socket.emit('update', { message: 'Real-time data' });
});
      

1. Introduction to CI/CD Concepts

Continuous Integration (CI) and Continuous Deployment (CD) automate building, testing, and deploying code to improve software delivery speed and quality.


// CI/CD pipelines run tests and deploy code automatically on every code change.
      
2. Writing Unit & Integration Tests

Use testing frameworks like Jest or Mocha to write tests that verify individual functions (unit tests) and API endpoints or DB interactions (integration tests).


// npm install jest supertest
const request = require('supertest');
const app = require('./app');

describe('GET /', () => {
  it('responds with 200', async () => {
    const res = await request(app).get('/');
    expect(res.statusCode).toBe(200);
  });
});
      
3. Setting Up GitHub Actions

Automate your build and test processes using GitHub Actions workflows triggered on code push or pull requests.


# .github/workflows/nodejs.yml
name: Node.js CI

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Use Node.js
        uses: actions/setup-node@v2
        with:
          node-version: '16'
      - run: npm install
      - run: npm test
      - run: npm run build
      
4. Dockerizing an Express App

Containerize your app for consistent environments using Docker.


# Dockerfile
FROM node:16-alpine

WORKDIR /app

COPY package*.json ./
RUN npm install

COPY . .

EXPOSE 3000
CMD ["node", "app.js"]
      
5. Using Docker Compose for Services

Use Docker Compose to manage multi-container setups like app + database.


# docker-compose.yml
version: '3'
services:
  app:
    build: .
    ports:
      - "3000:3000"
    depends_on:
      - mongo
  mongo:
    image: mongo
    ports:
      - "27017:27017"
      
6. CI with Travis CI or CircleCI

Similar to GitHub Actions, these services automate builds/tests and can deploy apps on success.


# Example .travis.yml
language: node_js
node_js:
  - "16"
script:
  - npm test
      
7. Deployment on DigitalOcean/VPS

Deploy your Dockerized Express app to a VPS or cloud provider by pushing images and running containers.


// SSH to server
ssh user@your-vps-ip

// Pull image and run container
docker pull your-image
docker run -d -p 80:3000 your-image
      
8. Monitoring with PM2 & Logs

PM2 manages Node.js processes with automatic restarts and provides logging.


// Install PM2 globally
npm install pm2 -g

// Start app with PM2
pm2 start app.js

// View logs
pm2 logs
      
9. Auto-Restart & Load Balancing

PM2 can auto-restart crashed apps and run multiple instances to utilize CPU cores.


// Start clustered app (max CPU cores)
pm2 start app.js -i max
      
10. Zero Downtime Deployment Techniques

Use PM2’s graceful reload to update apps without downtime.


// Reload app without downtime
pm2 reload app
      

1. OWASP Top 10 for Express Apps

The OWASP Top 10 highlights the most critical security risks for web apps, such as Injection, Broken Authentication, and Cross-Site Scripting (XSS). Understanding these risks helps secure Express applications.


// Familiarize with OWASP Top 10 to proactively protect your Express app.
      
2. Helmet Middleware for Security Headers

Helmet helps secure Express apps by setting HTTP headers like Content Security Policy, X-Frame-Options, and more.


// npm install helmet
const helmet = require('helmet');
app.use(helmet());
      
3. Input Sanitization and Escaping

Prevent injection attacks by sanitizing and escaping user inputs using libraries like express-validator or validator.


// npm install express-validator
const { body, validationResult } = require('express-validator');

app.post('/submit', [
  body('email').isEmail().normalizeEmail(),
  body('username').trim().escape()
], (req, res) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.status(400).json({ errors: errors.array() });
  }
  // Process sanitized input
});
      
4. Rate Limiting and Brute-Force Prevention

Use rate limiting to block excessive requests, preventing brute-force attacks.


// npm install express-rate-limit
const rateLimit = require('express-rate-limit');

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100 // limit each IP to 100 requests per window
});

app.use(limiter);
      
5. Cross-Origin Resource Sharing (CORS)

Control which domains can access your API using CORS middleware.


// npm install cors
const cors = require('cors');

app.use(cors({
  origin: 'https://yourdomain.com',
  optionsSuccessStatus: 200
}));
      
6. Secure Cookie Settings

Use secure flags for cookies to protect session data.


app.use(session({
  secret: 'secret',
  resave: false,
  saveUninitialized: false,
  cookie: {
    httpOnly: true,
    secure: true, // use only over HTTPS
    sameSite: 'strict'
  }
}));
      
7. Avoiding Directory Traversal

Prevent users from accessing files outside intended directories by sanitizing paths.


const path = require('path');

app.get('/files/:filename', (req, res) => {
  const fileName = path.basename(req.params.filename); // Prevent path traversal
  const filePath = path.join(__dirname, 'uploads', fileName);
  res.sendFile(filePath);
});
      
8. Protecting File Upload Endpoints

Validate and restrict uploaded file types and sizes to avoid malicious uploads.


// Using multer with file filter
const multer = require('multer');
const upload = multer({
  dest: 'uploads/',
  limits: { fileSize: 1024 * 1024 }, // 1 MB limit
  fileFilter: (req, file, cb) => {
    if (file.mimetype === 'image/png' || file.mimetype === 'image/jpeg') {
      cb(null, true);
    } else {
      cb(new Error('Only PNG and JPEG allowed'));
    }
  }
});

app.post('/upload', upload.single('file'), (req, res) => {
  res.send('File uploaded');
});
      
9. HTTPS Setup with Let's Encrypt

Secure your Express app with free SSL certificates from Let's Encrypt.


// Use Certbot to obtain certs, then configure HTTPS server

const https = require('https');
const fs = require('fs');

const options = {
  key: fs.readFileSync('/path/to/privkey.pem'),
  cert: fs.readFileSync('/path/to/fullchain.pem')
};

https.createServer(options, app).listen(443, () => {
  console.log('HTTPS server running');
});
      
10. Security Audits with npm audit and Best Practices

Regularly run npm audit to check for vulnerabilities and follow secure coding practices.


// Run in terminal:
npm audit

// Fix issues automatically
npm audit fix
      

1. Monolithic vs Microservices with Express

Monolithic architecture bundles all functionality in one codebase, easier to start but harder to scale. Microservices break the app into small, independent services communicating over APIs, enabling scalability and flexibility.


// Monolithic Express app: single project handles all features
// Microservices: multiple small Express apps communicate via HTTP or messaging
      
2. Organizing Large-Scale Apps

Use modular folder structures separating routes, controllers, services, models, and utils for maintainability.


/project-root
  /controllers
  /models
  /routes
  /services
  /middlewares
  app.js
      
3. MVC and Clean Code Principles

MVC divides application into Model (data), View (UI), and Controller (logic). Keeping controllers thin and separating concerns improves readability and testing.


// Example Controller
exports.getUsers = async (req, res) => {
  const users = await User.find();
  res.json(users);
};
      
4. Express with Message Queues (e.g., RabbitMQ)

Message queues help decouple services, enabling asynchronous processing and improved fault tolerance.


// Use amqplib for RabbitMQ
const amqp = require('amqplib');

async function sendMessage(msg) {
  const conn = await amqp.connect('amqp://localhost');
  const channel = await conn.createChannel();
  const queue = 'tasks';
  await channel.assertQueue(queue);
  channel.sendToQueue(queue, Buffer.from(msg));
  setTimeout(() => { conn.close(); }, 500);
}
      
5. Caching with Redis and MemoryStore

Cache frequent data to reduce database load and improve response times.


// npm install redis
const redis = require('redis');
const client = redis.createClient();

app.get('/data', async (req, res) => {
  client.get('key', async (err, cachedData) => {
    if (cachedData) return res.json(JSON.parse(cachedData));
    const data = await fetchDataFromDB();
    client.setex('key', 3600, JSON.stringify(data));
    res.json(data);
  });
});
      
6. Load Balancing Express Apps

Distribute incoming traffic across multiple app instances to improve performance and availability.


// Use PM2 or external load balancers like NGINX
pm2 start app.js -i max
      
7. Using NGINX as a Reverse Proxy

NGINX can route requests, handle SSL termination, and balance loads to Express instances.


# Sample NGINX config snippet
server {
  listen 80;
  server_name example.com;

  location / {
    proxy_pass http://localhost:3000;
    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;
  }
}
      
8. Separating Services (Auth, DB, API)

Create dedicated services for authentication, database operations, and APIs to isolate responsibilities and ease scaling.


// Auth service handles login/signup and token management
// API service handles client requests and business logic
      
9. GraphQL Gateway or BFF with Express

Use GraphQL gateway or Backend-for-Frontend (BFF) pattern to aggregate multiple microservices into a unified API.


// Express GraphQL server fetches data from multiple microservices and exposes a single schema.
      
10. Future-Proofing Your Express Stack

Adopt containerization, automated testing, CI/CD, monitoring, and stay updated with ecosystem changes to ensure your Express app scales and adapts over time.


// Regularly update dependencies, monitor performance, and plan for scaling.
      

1. Introduction to i18n and l10n Concepts

Internationalization (i18n) is designing your app to support multiple languages and cultural formats. Localization (l10n) is the process of adapting your app to a specific language and culture.


// i18n enables multiple language support;
// l10n adapts content, dates, currencies to users' locale.
      
2. Setting Up i18next with Express

i18next is a popular library to add internationalization to Node.js/Express apps.


// npm install i18next i18next-http-middleware i18next-fs-backend
const i18next = require('i18next');
const Backend = require('i18next-fs-backend');
const middleware = require('i18next-http-middleware');

i18next
  .use(Backend)
  .use(middleware.LanguageDetector)
  .init({
    fallbackLng: 'en',
    backend: {
      loadPath: './locales/{{lng}}/{{ns}}.json'
    }
  });

app.use(middleware.handle(i18next));
      
3. Detecting User Language Preferences

i18next-http-middleware detects user language via headers, query strings, cookies, or session.


// The middleware automatically detects language from:
// - Accept-Language header
// - URL query (?lng=fr)
// - Cookies or session if configured
      
4. Managing Translation Files and JSON Resources

Store translations in JSON files inside language folders with namespaces for organization.


// Example folder structure:
// locales/
//   en/
//     translation.json
//   fr/
//     translation.json

// translation.json content:
{
  "welcome": "Welcome",
  "goodbye": "Goodbye"
}
      
5. Middleware for Language Handling

Use i18next middleware in Express to attach translation functions to requests.


app.get('/', (req, res) => {
  const welcome = req.t('welcome'); // translate key 'welcome'
  res.send(welcome);
});
      
6. Formatting Dates, Numbers, and Currencies

Use libraries like Intl API or i18next extensions for locale-sensitive formatting.


// Example using Intl API
const date = new Date();
const formattedDate = new Intl.DateTimeFormat(req.language).format(date);
res.send(`Date: ${formattedDate}`);
      
7. Handling Plurals and Gender in Translations

i18next supports pluralization and context-based translations (e.g., gender).


// translation.json example with plurals:
{
  "item": "You have {{count}} item",
  "item_plural": "You have {{count}} items"
}
      
8. Dynamic Content Translation in Views

Use templating engines or React/Vue integrations to translate dynamic UI content.


// In Pug template example:
p= t('welcome')
      
9. Fallback Language Strategies

If translation is missing in user’s language, fallback to a default language like English.


// fallbackLng: 'en' option ensures fallback translation.
      
10. Testing and Debugging Localization

Test translations by switching languages and verifying content; use logging or debug flags in i18next.


i18next.init({
  debug: true,
  // other config
});
      

1. What is Server-Side Rendering?

Server-Side Rendering (SSR) is the technique where HTML is generated on the server and sent to the client, improving initial load times and SEO compared to client-side rendering.


// SSR renders full HTML on server before sending to browser,
// unlike SPA which renders mostly in browser.
      
2. Setting Up SSR with React and Express

Use React’s renderToString method to generate HTML on server and send it as a response.


const express = require('express');
const React = require('react');
const ReactDOMServer = require('react-dom/server');

const App = require('./App').default;

const app = express();

app.get('/', (req, res) => {
  const appHtml = ReactDOMServer.renderToString(React.createElement(App));
  res.send(`
    <!DOCTYPE html>
    <html>
      <head><title>SSR Example</title></head>
      <body>
        <div id="root">${appHtml}</div>
        <script src="/client.bundle.js"></script>
      </body>
    </html>
  `);
});

app.listen(3000);
      
3. Rendering Views with Pug/EJS and SSR Basics

Express supports templating engines like Pug and EJS for SSR by rendering templates with dynamic data.


// Setup Pug
app.set('view engine', 'pug');

app.get('/', (req, res) => {
  res.render('index', { title: 'SSR with Pug', message: 'Hello from server!' });
});
      
4. Hydrating Client-Side Applications

After SSR, client-side JavaScript “hydrates” the static HTML, attaching event listeners and making it interactive.


// React hydrate example
import ReactDOM from 'react-dom';

ReactDOM.hydrate(
  React.createElement(App),
  document.getElementById('root')
);
      
5. Data Fetching for SSR

Fetch data on the server before rendering to send fully populated pages to clients.


app.get('/user/:id', async (req, res) => {
  const userData = await getUserFromDB(req.params.id);
  const appHtml = ReactDOMServer.renderToString(
    React.createElement(App, { user: userData })
  );
  res.send(`
    <html><body><div id="root">${appHtml}</div></body></html>
  `);
});
      
6. SEO Benefits of SSR

SSR improves SEO by delivering fully rendered pages with meta tags and content readable by search engines.


// Search engines crawl SSR pages better than client-only rendered SPAs.
      
7. Handling Routing in SSR

Implement server-side routing to match client routes and send correct HTML for each path.


app.get('*', (req, res) => {
  // Match route and render corresponding React components
  // or templates based on req.url
});
      
8. Caching SSR Responses

Cache rendered HTML pages to improve response times and reduce server load.


// Use in-memory cache or Redis to store HTML output keyed by URL.
      
9. Error Handling in SSR

Gracefully handle rendering errors and send fallback content or error pages.


try {
  // SSR rendering code
} catch (err) {
  res.status(500).send('Error rendering page');
}
      
10. Performance Considerations in SSR

Optimize SSR by minimizing data fetching time, using caching, and keeping server rendering fast to avoid slow responses.


// Avoid blocking operations; use async/await and cache rendered HTML.
      

1. Introduction to Graph Databases (Neo4j)

Graph databases store data as nodes, relationships, and properties, ideal for highly connected data. Neo4j is a leading graph database using the Cypher query language.


// Graph DBs model relationships explicitly,
// enabling complex queries about connections.
      
2. Setting Up Neo4j Driver in Express

Use the official Neo4j JavaScript driver to connect your Express app to a Neo4j database.


// npm install neo4j-driver
const neo4j = require('neo4j-driver');

const driver = neo4j.driver(
  'bolt://localhost:7687',
  neo4j.auth.basic('username', 'password')
);
const session = driver.session();
      
3. CRUD Operations with Cypher Queries

Create, Read, Update, and Delete nodes and relationships using Cypher queries.


// Create node
await session.run(
  'CREATE (p:Person {name: $name, age: $age}) RETURN p',
  { name: 'Alice', age: 30 }
);

// Read node
const result = await session.run('MATCH (p:Person) RETURN p');
const persons = result.records.map(record => record.get('p').properties);
      
4. Modeling Relationships in Graph DB

Define relationships like FRIENDS_WITH, WORKS_AT between nodes to represent connections.


// Create relationship
await session.run(
  'MATCH (a:Person {name: $name1}), (b:Person {name: $name2}) CREATE (a)-[:FRIENDS_WITH]->(b)',
  { name1: 'Alice', name2: 'Bob' }
);
      
5. Integrating Neo4j with REST APIs

Expose graph database operations via RESTful endpoints in Express.


app.get('/persons', async (req, res) => {
  const result = await session.run('MATCH (p:Person) RETURN p');
  const persons = result.records.map(r => r.get('p').properties);
  res.json(persons);
});
      
6. Advanced Querying Techniques

Use Cypher features like variable length paths, pattern matching, and aggregations for complex queries.


// Find friends of friends up to 2 hops
await session.run(
  'MATCH (p:Person)-[:FRIENDS_WITH*1..2]-(fof) WHERE p.name = $name RETURN fof',
  { name: 'Alice' }
);
      
7. Transaction Management

Execute multiple Cypher queries atomically using sessions and transactions.


const tx = session.beginTransaction();

try {
  await tx.run('CREATE (p:Person {name: $name})', { name: 'Carol' });
  await tx.commit();
} catch (error) {
  await tx.rollback();
}
      
8. Performance Optimization for Graph Queries

Use indexes, constraints, and query profiling to optimize performance.


// Create index
await session.run('CREATE INDEX person_name IF NOT EXISTS FOR (p:Person) ON (p.name)');
      
9. Using GraphQL with Graph Databases

Combine GraphQL APIs with Neo4j for flexible querying and schema definition.


// Use neo4j-graphql-js or Apollo Server for integration
      
10. Security and Access Control in Graph DB

Implement role-based access control and secure connections to restrict database access.


// Use Neo4j’s built-in user roles and secure connections with TLS.
      

1. Why Build CLI Tools?

CLI (Command Line Interface) tools automate repetitive tasks, improve developer productivity, and can scaffold projects or run scripts efficiently.


// Example: Automate Express app creation or run build scripts from terminal.
      
2. Using commander or yargs for CLI Arguments

Libraries like commander and yargs simplify parsing and handling command line arguments.


// npm install commander
const { program } = require('commander');

program
  .option('-p, --port <number>', 'Port number', '3000')
  .parse(process.argv);

console.log(`Starting server on port ${program.port}`);
      
3. Creating Express Apps via CLI

CLI tools can scaffold Express apps by generating project files and folders based on user input.


const fs = require('fs');
const path = require('path');

function createExpressApp(name) {
  const dir = path.join(process.cwd(), name);
  if (!fs.existsSync(dir)) {
    fs.mkdirSync(dir);
  }
  fs.writeFileSync(path.join(dir, 'app.js'), `const express = require('express');\nconst app = express();\napp.listen(3000);\n`);
  console.log(`Express app created at ${dir}`);
}
      
4. Automating Development Tasks

Use CLI scripts to automate tasks like linting, testing, building, or deploying.


// package.json scripts section example
"scripts": {
  "lint": "eslint .",
  "test": "jest",
  "build": "webpack"
}
      
5. Interactive Prompts with inquirer

Use inquirer to interactively ask users questions during CLI execution.


// npm install inquirer
const inquirer = require('inquirer');

inquirer.prompt([
  { type: 'input', name: 'appName', message: 'Enter app name:' }
]).then(answers => {
  console.log(`Creating app: ${answers.appName}`);
});
      
6. File System Manipulation from CLI

Read, write, copy, and delete files using Node’s fs module to generate and modify projects.


const fs = require('fs');
const fileContent = 'console.log("Hello from CLI tool");';
fs.writeFileSync('hello.js', fileContent);
      
7. Running and Monitoring Scripts

Execute external commands and monitor output using child_process.


const { exec } = require('child_process');

exec('node hello.js', (error, stdout, stderr) => {
  if (error) {
    console.error(`Error: ${error.message}`);
    return;
  }
  console.log(`Output: ${stdout}`);
});
      
8. Packaging CLI Tools for Distribution

Prepare your CLI tool with a bin entry in package.json and a proper shebang in your main script.


// package.json example
"bin": {
  "mycli": "./cli.js"
}

// cli.js first line
#!/usr/bin/env node
      
9. Testing CLI Commands

Use testing frameworks like Jest and simulate CLI inputs to test your commands.


// Example: spawn CLI command and check output in tests.
      
10. Publishing CLI Tools to npm

Publish your CLI tool as an npm package to share with the community.


// Login to npm
npm login

// Publish package
npm publish
      

1. What is a PWA?

A Progressive Web App (PWA) is a web application that uses modern web capabilities to deliver an app-like experience with offline support, push notifications, and home screen installation.


// PWAs combine the best of web and mobile apps.
      
2. Setting Up Service Workers in Express

Service workers intercept network requests and enable offline caching and background tasks.


// Serve the service worker file statically
app.use(express.static('public'));

// Example service-worker.js registration in client
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/service-worker.js')
    .then(() => console.log('Service Worker Registered'))
    .catch(err => console.error('Service Worker Registration Failed', err));
}
      
3. Caching Strategies for Offline Support

Use caching strategies like Cache First, Network First, or Stale-While-Revalidate to optimize offline usage.


// Example cache first in service worker
self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request).then(response => {
      return response || fetch(event.request);
    })
  );
});
      
4. Web App Manifest Setup

The manifest file defines app icons, name, start URL, and display settings to enable installability.


{
  "name": "My PWA",
  "short_name": "PWA",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#000000",
  "icons": [
    {
      "src": "/icons/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    }
  ]
}
      
5. Push Notifications with Express and Web Push API

Use Web Push API on the client and Express server with libraries like web-push to send push notifications.


// npm install web-push
const webpush = require('web-push');

webpush.setVapidDetails(
  'mailto:example@domain.com',
  'PUBLIC_VAPID_KEY',
  'PRIVATE_VAPID_KEY'
);

app.post('/subscribe', (req, res) => {
  const subscription = req.body;
  webpush.sendNotification(subscription, 'Push message')
    .catch(err => console.error(err));
  res.status(201).json({});
});
      
6. Background Sync and Data Syncing

Background Sync API allows deferred syncing of data when connectivity returns.


// Register sync event in service worker
self.addEventListener('sync', event => {
  if (event.tag === 'sync-data') {
    event.waitUntil(syncData());
  }
});
      
7. Responsive Design Integration

Ensure your PWA layout adapts to various screen sizes using CSS media queries or frameworks like Bootstrap.


/* Example CSS */
@media (max-width: 600px) {
  .container {
    padding: 10px;
  }
}
      
8. Handling Updates and Cache Management

Implement strategies to update service workers and manage caches to deliver fresh content.


// Listen for new service worker and prompt user to refresh.
self.addEventListener('install', event => {
  self.skipWaiting();
});
      
9. Analyzing PWA Performance with Lighthouse

Use Google Lighthouse to audit your PWA for performance, accessibility, SEO, and best practices.


// Run Lighthouse in Chrome DevTools or CLI
lighthouse https://your-pwa-url.com --view
      
10. Deploying and Publishing PWAs

Host your PWA on HTTPS-enabled servers, and submit to app stores or share install links.


// Deploy to platforms like Vercel, Netlify, or your own HTTPS server.