Node.js

Node.js is a cross-platform runtime environment that allows you to run JavaScript outside of a web browser—usually on a server. It enables developers to build fast, scalable server-side and network applications using JavaScript.
extension → .js (any js file).

node REPL

REPL stands for: Read → Eval → Print → Loop. The Node.js REPL is an interactive shell that lets you run JavaScript line-by-line directly in the terminal.

How to Start REPL:

  1. Open your terminal
  2. Type: node
  3. You'll see a > prompt. Start typing JavaScript code.
  4. To exit REPL, press: Ctrl + C (twice) or type .exit

to run Javascript with node in terminal use "node fileName.js". You need to be in same folder where file is present

process Object

In Node.js, the process object is a global object that provides information and control over the current Node.js process. It's part of the core process module, so you don't need to import it.

Key Properties of the process Object

process.argv: An array containing the command-line arguments passed when the Node.js process was launched. The first element is the path to the Node.js executable, the second is the path to the script, and subsequent elements are additional arguments.

Example: // Example: node script.js arg1 arg2 console.log(process.argv); //Output like: ['node', 'path/to/script.js', 'arg1', 'arg2']

There are many more properties and Methods in process Object...

Export in files

In Node.js, module.exports and require() are core concepts for creating and using modules to organize and share code.

module.exports

module.exports is an object in a Node.js module that defines what a module exposes to other parts of the application. By default, it's an empty object ({}), but you can assign functions, objects, or values to it.

Purpose: It determines what gets returned when another file uses require() to import the module.
Location: Every Node.js file has a module object, and module.exports is a property of it.

1. Example: Exporting a Single Function // math.js function add(a, b) {   return a + b; } module.exports = add; // Export the function 2. Example: Exporting Multiple Items const add = (a, b) => a + b; const subtract = (a, b) => a - b; module.exports = {   add,   subtract }; // Export an object with multiple functions 3. Alternative: Using exports Shorthand exports is an alias for module.exports. You can use it to add properties but cannot reassign it directly. exports.add = (a, b) => a + b;

require()

require() is a function used to import modules, whether they are built-in (e.g., fs, http), installed via npm, or local files.

Syntax:

const moduleName = require('path-or-module-name');

1. Example: Importing a Module // app.js const math = require('./math'); // Import math.js (local file, relative path) console.log(math.add(2, 3)); // 5
2. Importing Built-in or npm Modules const fs = require('fs'); // Built-in module const express = require('express'); // npm-installed module

import

the import statement is used to bring in modules, functions, or variables from other files or packages, is part of the ECMAScript module system, introduced to standardize module handling in JavaScript. It's an alternative to the CommonJS require() system used in Node.js by default.

To use import, you must configure your project for ES Modules:

  • Option 1: Set "type": "module" in package.json. (All .js files are treated as ES Modules, and you use import/export instead of require()/module.exports.)
  • Option 2: Use .mjs Extension. Name your files with .mjs to explicitly indicate ES Modules, even without "type": "module". Example: app.mjs instead of app.js.

import vs require()

  • We can't selectively load only the pieces we need with require but with import, we can selectively load only the pieces we need, which can save memory.
  • Loading is synchronous for 'require' but can be asynchronous for 'import'. there are other differences also.
Example: (use .js if "type": "module" is set in package.json file OR use .mjs) 1. Importing from a Local File: // utils.js export const add = (a, b) => a + b; export const subtract = (a, b) => a - b; // app.js import { add } from './utils.js'; //we can import only selective things (import add here but not subtract). console.log(add(2, 3)); // 5
2.Importing from an npm Package: npm install lodash // app.js import { map, filter } from 'lodash'; //imported multiple console.log(map([1, 2, 3], n => n * 2)); // [2, 4, 6]

Export in directories

When organizing large projects, you often group files into folders. You can export modules from an entire directory using a special file: index.js. It acts as the entry point of the folder.

Steps:

  1. Create an index.js file inside the directory from which you want to export modules.
  2. Import the modules (functions, variables, classes, etc.) from other files in that directory into index.js, and export them together using module.exports.
  3. Import the directory (folder) wherever needed — Node.js will automatically look for index.js inside that folder.
Example: //file structure project/ ├── utils/ │ ├── math.js │ ├── string.js │ └── index.js ├── app.js //utils/math.js const add = (a, b) => a + b; module.exports = { add }; // utils/string.js const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1); module.exports = { capitalize }; // utils/index.js const math = require('./math'); import all in one file const string = require('./string'); module.exports = { //export math: math, string: string }; // app.js const utils = require('./utils'); //import folder console.log(utils.math.add(2, 3)); // 5 console.log(utils.string.capitalize('hello')); // 'Hello'

There are other way also other than index.js...

NPM (Node Package Manager)

It is default package manager for Node.js, A large online library of packages (modules), A command-line tool to install, manage, and publish packages

A package is like a ready-made tool or library that you can install and use in your project instead of writing the code yourself.

Registry: The npm registry (https://www.npmjs.com) hosts over 2 million packages, which are reusable JavaScript modules.

Installing Packages

Locally(Preffered way):
npm install package-name
OR npm i package-name (i is alias for install)

Install in same folder of project. Locak install not work outside.

Example: npm install express

To Install globally:
npm install -g package-name
then use - npm link package-name (need to link with the folder in which we want to use)

files/folders while installing

node_modules/ is the folder where all installed packages are stored. It also includes sub-dependencies — packages required by other packages. You should NOT edit anything inside this folder manually. The node modules folder contains every installed dependency for your.

package-lock.json It records the exact version of every installed dependency, including its sub-dependencies and their versions.
there may many other things may install...

package.json Purpose: package.json is a JSON file that stores metadata about a Node.js project, including: Project name, version, description and Dependencies e.t.c
There's no need to transfer the node_modules folder if package.json is present — just run npm install to reinstall all dependencies.

Creating package.json with npm init: Command: npm init
It Interactively prompts for details like name, version, description, entry point, etc. Generates a package.json file based on your input.

Express.js

Express is a minimal and flexible web application framework for Node.js, designed to simplify the process of building server-side applications and APIs.

Library

A library is a set of reusable code that you call to perform specific tasks — you control the flow of the program. e.g - axios e.t.c.

Framework

A framework is a complete structure that calls your code at the right time — it controls the flow, and you follow its rules. e.g - express e.t.c.

Setup for express:

  • Step 1: Create a New Project Folder
  • Step 2: Initialize package.json
  • Step 3: Install Express.js: Install Express as a dependency.
  • Step 4: Create a js file In the same folder.
  • Step 5: Run Your Express App:
    In the terminal - node filename OR
    Open a browser and go to: http://localhost:portNumber
    (you can acces you local computer server using localhost)
Example code in js file: const express = require('express'); const app = express(); //name "app" is used generally we can also use other name console.log(app); //it shows a large object that represents the internal structure of the Express app. It includes: All built-in methods, Properties, Event listeners e.t.c. // Define a route app.get('/', (req, res) => {   res.send('Hello from Express!'); }); // Start the server app.listen(3000, () => {   console.log('Server is running at http://localhost:3000'); });

listen()

listen() is a method that starts your Express server and makes it listen for incoming requests on a specified port.
Syntax - app.listen(port, [callback])

nodemon

nodemon is a utility for Node.js that automatically restarts your application when file changes in the directory are detected.

  • To install - npm install -g nodemon (installing globally recommended)
  • To run file with nodemon - nodemon filename.js

Handling Requests in Express.js

In Express, handling requests means defining what your server should do when it receives a specific type of request (like a user visiting a page or submitting a form).

Basic Syntax:

app.METHOD(PATH, HANDLER)

  • METHOD: HTTP method (get, post, put, delete, etc.)
  • PATH: URL path (like '/', '/login', '/products')
  • HANDLER: Function that runs when the route is matched
Example: Handling a GET Request: app.get('/', (req, res) => {   res.send('Welcome to the homepage!'); });

Understanding Request & Response Objects

  • req → request info from the client
  • res → used to send a response back

app.use()

app.use() is used to handle all types of requests (GET, POST, etc.) for a specific path or globally — especially useful when you want to do something before reaching the final route (like logging, authentication, etc.).

Basic Structure example: app.use((req, res) => {   console.log('Middleware triggered'); });

Sending Responses in Express.js

In Express, you send responses using the res (response) object, which is provided in every route handler.

Common Methods to Send Responses

1. res.send()

Sends a text, HTML, or object e.t.c response. Automatically sets the Content-Type based on the data type.

example: app.get('/', (req, res) => {   res.send('/ means Home Page in paths'); });

2. res.redirect()

Redirects the client to another URL. Default status is 302 (temporary redirect).

example: app.get('/old-route', (req, res) => {   res.redirect('/new-route'); });

There are other methods/ways also...

Single Response: Only one response can be sent to one path per request. Calling res.send or similar methods multiple times causes an error.

Routing

Routing in Express.js is the process of defining how an application responds to client requests to specific URLs (paths) using specific HTTP methods like GET, POST, etc.

  • Routes are defined using methods of the app object corresponding to HTTP methods (app.get(), app.post(), app.put(), app.delete(), etc.).
  • Each route maps a URL pattern to a handler function that processes the request and sends a response.
  • Route paths can be strings, string patterns, or regular expressions. e.g - / matches the root path, /users matches requests to /users, /user/:id matches dynamic paths like /user/123, where :id is a route parameter.
example: // GET request to the root path app.get('/', (req, res) => {   res.send('Welcome to the homepage!'); }); // request with a route parameter app.get('/user/:id', (req, res) => {   res.send(`User ID: ${req.params.id}`); }); // Catch-all route for all unmatched paths app.get('*', (req, res) => {   res.status(404).send('404 - Page Not Found'); });

The app.get('*', ...) route matches any GET request to any path that hasn't been handled by previous routes.

Route Parameters

In Express, route parameters (also called path parameters) allow you to capture dynamic values from the URL.

They are defined in the route path using a colon (:) followed by the parameter name. These parameters become available in the req.params object in your route handler.

Syntax:

app.get('/route/:paramName', (req, res) => {
  // to Access full object - req.params
  // to only Access paramName - req.params.paramName
});

example: // Route with a path parameter app.get('/users/:userId', (req, res) => {   const userId = req.params.userId; // Access the parameter   res.send(`User ID: ${userId}`); }); Explanation: :userId in the route path captures the value 123 from the URL and stores it in req.params.userId.
//Multiple Path Parameters: app.get('/users/:userId/posts/:postId', (req, res) => {   const { userId, postId } = req.params;   res.send(`User ID: ${userId}, Post ID: ${postId}`); });

Optional Parameters: To make a parameter optional, add a ? after it.

example: //optional perematers app.get('/files/:fileName?', (req, res) => {   const fileName = req.params.fileName || 'default.txt';   res.send(`File: ${fileName}`); });
//Regular Expression in Parameters app.get('/items/:id(\\d+)', (req, res) => {   const id = req.params.id;   res.send(`Item ID: ${id}`); });

Note - Path parameters are different from query parameters (e.g., ?key=value), which are accessed via req.query.

Query Strings in Express

In Express, query strings are key-value pairs in a URL that come after a ? (e.g., ?key1=value1&key2=value2). They are used to pass additional data to the server and are accessible in Express via the req.query object.

//example syntax app.get('/route', (req, res) => {   // Access query parameters via req.query   const queryParams = req.query;   res.send(queryParams); });
example: app.get('/search', (req, res) => {   const { q, page } = req.query; // Destructure query parameters   res.send(`Search Query: ${q}, Page: ${page || 1}`); });
// Default values app.get('/filter', (req, res) => {   const { sort = 'asc', limit = 10 } = req.query; // Default values   res.send(`Sort: ${sort}, Limit: ${limit}`); });

Optional Query Parameters: Query parameters are inherently optional. If not provided, req.query.key is undefined.

Query Strings vs. Path Parameters
Query Strings Path Parameters
Optional, used for filtering, sorting, or searching (e.g., /search?q=term) Part of the URL path, used for identifying resources (e.g., /users/:id)
req.query req.perams

GET/POST Request in express

Get Request

An HTTP method used to retrieve data from a server. It sends parameters via the URL query string (e.g., /resource?key=value).

In Express.js, app.get('/path', (req, res) => {...}) handles these requests, accessing parameters through req.query.

It's idempotent (repeated requests yield the same result), suitable for fetching resources like web pages or API data, but less secure for sensitive data as parameters are visible in URLs.

//example: //html: <h2>GET Form</h2> <form action="http:localhost:3000/register" method="GET">   <label>User: <input type="text" name="user"></label>   <label>Password: <input type="password" name="password"></label>   <button type="submit">Submit (GET)</button> </form>
// server.js const express = require('express'); const app = express(); const port = 3000; // GET: Handle form submission app.get('/register', (req, res) => {   const { user, password } = req.query; //access data using req.query   res.send(`GET: Welcome ${user}!`); }); app.listen(port, () => console.log(`Server on port ${port}`));

Post Request

An HTTP method used to submit data to a server to create or update resources. Data is sent in the request body, supporting formats like JSON or URL-encoded form data.

In Express.js, app.post('/path', (req, res) => {...}) handles these requests, accessing data via req.body (requires middleware like express.json() or express.urlencoded()).

It's non-idempotent (repeated requests may create multiple resources) and ideal for secure data submission, such as forms or API payloads.

//example: //html: <h2>POST Form</h2> <form action="http:localhost:3000/register" method="POST">   <label>User: <input type="text" name="user"></label>   <label>Password: <input type="password" name="password"></label>   <button type="submit">Submit (GET)</button> </form>
// server.js const express = require('express'); const app = express(); const port = 3000; // Middleware to parse form data and JSON app.use(express.urlencoded({ extended: true })); app.use(express.json()); //use these otherwise data is not accessible and it shows Undefined // POST: Handle form submission app.post('/register', (req, res) => {   const { user, password } = req.body; // access data using req.body   res.send(`POST: Welcome ${user}!`); }); app.listen(port, () => console.log(`Server on port ${port}`));

Middlewares

Middleware refers to functions that execute during the request-response cycle. They are used to process incoming requests before they reach the route handler or send back a response.

Middleware is a function that has access to:

  • req (the request object)
  • res (the response object)
  • next (a function that passes control to the next middleware)

//example syntax: function middlewareName(req, res, next) {   // Do something   next(); // Pass control to next middleware }

Purpose: Middlewares are used for tasks like logging, authentication, error handling, parsing request bodies, serving static files, or modifying request/response data.

A middleware function must either: Send a response, OR Call next() to pass control to the next middleware or route handler. If it does neither, the request will hang and the client will never get a response.

//example: const express = require('express'); const app = express(); // Middleware using app.use() app.use((req, res, next) => { const isBlocked = true; if (isBlocked) {   // Middleware sends a response and stops further handling   res.status(403).send('Access Denied by Middleware'); //response sent } else {   // Pass to the next middleware or route if not blocked   next(); } }); // This route will never run if isBlocked is true app.get('/', (req, res) => {   res.send('Welcome to the homepage!'); }); app.listen(3000, () => {   console.log('Server running on http://localhost:3000'); }); //app.use - Registers a middleware for all routes (/, /about, etc.)
//using multiple middlewares: const express = require('express'); const app = express(); // Middleware 1 (runs first) app.use((req, res, next) => {   console.log('Middleware 1');   req.msg = 'Hello';   next(); // Go to Middleware 2   //we can also write code after next here but that is not preffered as good way. }); // Middleware 2 (runs second) app.use((req, res, next) => {   console.log('Middleware 2');   req.msg += ' from Middleware';    next(); // Go to Route Handler }); // Route Handler (runs last) app.get('/', (req, res) => {   console.log('Route Handler');   res.send(req.msg + ' and Route'); }); app.listen(3000);
//middleware for specific route only: app.use('/about', (req, res, next) => { //for /about route only   res.send('middleware for /about route only'); //also for like /about/xyz... });

Using Middleware as Function or Variable e.t.c

//example: // Middleware as function function logger(req, res, next) {   console.log('Logger Middleware');   req.msg = 'Hello';   next(); } // Middleware as variable (arrow function) const addText = (req, res, next) => {   console.log('AddText Middleware');   req.msg += ' from Variable';   next(); }; // Route using both middlewares app.get('/', logger, addText, (req, res) => {   // pass middleware as parameter   console.log('Route Handler');   res.send(req.msg + ' and Route'); }); Output when visiting / Console: Logger Middleware AddText Middleware Route Handler Browser Response: Hello from Variable and Route

Error Handeling

Error Handling refers to how Express catches and processes errors that occur both synchronously and asynchronously. Express also comes with a default error handler.

Default Error Handler

If a middleware or route throws an error or calls next(err) and you don't define a custom error handler, Express automatically returns a default 500 Internal Server Error and logs the error in the console (in development mode).

This default error-handling middleware function is added at the end of the middleware function stack.

How Does It Work Internally? - When you throw an error or call next(err), Express Skips any remaining normal middleware and Looks for error-handling middleware (functions with 4 args: err, req, res, next) If none are found, it uses the default error handler.

//example: //No Custom Error Handler const express = require('express'); const app = express(); app.get('/', (req, res) => {   throw new Error('Something broke!'); }); app.listen(3000); Output: Console (in development mode): Error: Something broke! at ... Browser: Cannot GET /
//we can use also like app.get('/', (req, res, next) => {   const err = new Error('Something went wrong!');   next(err); // Passes the error to Express's default error handler });

Custom Error Handler

To override the default error handler, define a middleware with the four arguments (err, req, res, next) typically at the end of your middleware stack.

//example: const express = require('express'); const app = express(); // Normal route app.get('/', (req, res) => {   throw new Error('Something went wrong!');   //Error middleware is called only if next(err) is used, or an error is thrown. }); // Custom error-handling middleware app.use((err, req, res, next) => {   console.error('Error:', err.message);   res.status(500).json({ status: 'error', message: err.message }); }); app.listen(3000);

Custom Error Class

A custom error class is a user-defined class that extends JavaScript's built-in Error class. It lets you attach custom properties like statusCode, status, or isOperational e.t.c to better control how errors are handled and responded to in your application.

//example: // utils/CustomError.js class CustomError extends Error {   constructor(message, statusCode = 500) { //also we can set default parameters     super(message); // Call the built-in Error constructor     this.statusCode = statusCode; // HTTP status (e.g., 404, 500)     this.message = message;     this.isOperational = true; // Differentiates handled errors from code bugs   } } module.exports = CustomError; //app.js const CustomError = require('./utils/CustomError'); app.get('/fail', (req, res, next) => {   // Trigger a custom error   return next(new CustomError('Something went wrong!', 400)); });

Handling Async errors

In Express.js, when you use async/await, any error thrown inside an async function (like a failed database query or undefined access) won't automatically be caught by Express. That's why you need to manually catch these errors and pass them to Express using next(error).

//example 1: Using try-catch inside an async route app.get("/chats", async (req, res, next) => { try {   let chats = await Chat.find({});   res.render("index.ejs", { chats }); } catch (err) {   next(err); // Pass any error to the default or custom error handler } }); //using try-catch in async route handlers allows you to handle every different type of error that may occur in that block
//example 2: Without try-catch — using next(new ExpressError(...)) app.get("/chats/:id", async (req, res, next) => {   let { id } = req.params;   let chat = await Chat.findById(id);   if (!chat) {     return next(new ExpressError(404, "Chat not Found or Deleted"));   } //  Here, we are not able to handle all possible errors, just a specific one (chat not found).   res.render("edit.ejs", { chat }); }); //we can use both together also like defining specific error in try block

Async Error Wrapper

An async error wrapper is a higher-order function that wraps any async route and automatically catches errors from it and forwards them to Express's error handler.

The ideal approach is to use an async error wrapper, so you don't need to write try-catch everywhere.

example syntax: const asyncWrapper = (fn) => {   return (req, res, next) => {     Promise.resolve(fn(req, res, next)).catch(next);   }; }; //If fn throws an error (sync or async), .catch(next) sends it to Express's error handler. //Wraps it with Promise.resolve(): Ensures that even if fn doesn't return a promise (e.g., not marked async), it's still treated as one. Safely handles both async and sync functions.
//example: //Define the Wrapper Function const asyncWrapper = (fn) => {   return (req, res, next) => {     Promise.resolve(fn(req, res, next)).catch(next);   }; }; //Use it in a Route app.get('/example', asyncWrapper(async (req, res, next) => { //use it like this   const data = await someAsyncFunction();   res.json(data); }));

Schema Validation

Joi

Joi is a JavaScript object schema validation library, mainly used to validate incoming request data like: req.body (POST/PUT requests), req.params (URL parameters), req.query (query strings), This is especially useful before inserting/updating data in MongoDB, so we can prevent saving invalid or incomplete data.

//Install: npm install joi
//Example: //Joi Schema (for input validation) const Joi = require('joi'); const userJoiSchema = Joi.object({   name: Joi.string().min(3).max(30).required(),   email: Joi.string().email().required(),   password: Joi.string().min(6).required(),   age: Joi.number().integer().min(18).max(100).required() }); Used to validate this incoming req.body: {   "name": "Inderjit",   "email": "inderjit@example.com",   "password": "secure123",   "age": 21 } //exampel Mongoose Schema according to Joi const mongoose = require('mongoose'); const userMongoSchema = new mongoose.Schema({   name: { type: String,required: true,minlength: 3,maxlength: 30 },   email: { type: String,required: true,unique: true // extra: ensures no duplicate emails },   password: { type: String,required: true,minlength: 6 },   age: { type: Number,min: 18,max: 100,required: true } }); // We use Joi so that we don't need to manually validate each field of incoming data. Joi makes validation easier, cleaner, and centralized using a schema. //we use joi so that we not need to validate every incoming data object field seprately // Generally, the Joi schema and Mongoose schema need to be the same, because both describe the structure and rules for the same data model
//Without Joi: if (!req.body.name) return res.status(400).send("Name is required"); if (!['male', 'female'].includes(req.body.gender)) return res.status(400).send("Invalid gender"); // ... manual checks for every field //With Joi: const schema = Joi.object({   name: Joi.string().required(),   age: Joi.number().min(0).required(),   gender: Joi.string().valid('male', 'female').required() }); const { error } = schema.validate(req.body); if (error) return res.status(400).send(error.details[0].message);

There are other methods and libraries to validate Schema ...

Express Router

Express Router is a mini Express application that provides a way to group route handlers, middleware, and sub-routes in a modular and reusable way.

It helps structure your application by separating routes into different files or modules, instead of writing everything in app.js or server.js, improving maintainability and scalability. Keeps your app.js file clean.

It works just like the main app object, meaning you can define .get(), .post(), .put(), .delete(), etc., on it.

//Steps: 1. Create a Router file in routes folder 2. Usage: //in router file const express = require('express'); const router = express.Router(); //require router.get('/', (req, res) => { //we can use any method like get, post...   //do something }); //in main app.js file use like: const booksRouter = require('./routes/books'); require app.use('/startingCommonRoute', booksRouter); // Mount the router //example document structure: project/ │ ├── app.js ├── routes/ │  └── example.js //router files inside router folder └── package.json
//Example: //routes/books.js const express = require('express'); const router = express.Router(); //require // Sample GET route router.get('/', (req, res) => { //use router... as it is name here of router object   res.send('List of books'); }); // Sample GET route with parameter router.get('/:id', (req, res) => {   res.send(`Details of book with ID: ${req.params.id}`); }); // Sample POST route router.post('/', (req, res) => {   res.send('New book added'); }); module.exports = router; //export //app.js const express = require('express'); const app = express(); const booksRouter = require('./routes/books'); //require app.use(express.json()); // For parsing application/json // Mount the router (app.use) app.use('/books', booksRouter); // handles - /books/* - all paths starting from books app.listen(3000, () => {   console.log('Server started on port 3000'); }); //just work like app.js just replace app... with router... (or any name of router valiable).

Path Combination in Express Router - When you use a router in app.js, Express automatically combines: the base path in `app.use()` + the path inside `router.<method>()`

In app.js: app.use('/books', booksRouter); //All paths in booksRouter will be prefixed with /books. routes/books.js: router.post('/:id', (req, res) => {   //do something }); //Final Path - /books/:id

Router Options

These options customize how your router behaves. syntax - express.Router([options])

1. Merging params (Using mergeParams: true)

By default, if you create a nested router, the child router cannot access params defined in the parent router unless you explicitly enable it.

Solution: mergeParams: true - It allows a child router to access req.params defined in the parent router path.

//require router like this in routes file: const router = express.Router({ mergeParams: true }); //add this option //now if route in parent file(e.g - app.js) like this - /example/:id ... now id paramater is also accessibe in child file.

2. caseSensitive and 3. strict

Option Type Default Description
caseSensitive boolean false Makes routing case-sensitive (/Book/book)
strict boolean false Trailing slash matters (/book//book)

Cookies

Cookies are small pieces of data that a server sends to the user's browser. The browser stores them and sends them back with subsequent requests to the same server.

Cookies are mainly used for: Session management (logins, shopping carts), Personalization (themes, preferences), Tracking (analytics, user behavior).

Sending Cookies - res.cookie()

This is used to set a cookie from the server. It sends a Set-Cookie header in the HTTP response so that the client browser stores the cookie.

Think of it like: Hey browser! Save this piece of data for me and send it back with every future request.

res.cookie(name, value, [options])

//Example: res.cookie('username', 'Inderjit'); //we can send like: app.get("/exampleRoute", (req, res) => {   res.cookie ("madeIn", "Punjab");   //like this we can send when someone come to specific path then it stores in all paths of website.   res.send ("sent"); }) :

Receiving Cookies - req.cookies

This is used to read cookies that the browser sends back to the server with each request.

//Usage: const user = req.cookies.username; //Means give me the cookie named username that the client sent back.
//Full Flow Example (sending and Receiving): const express = require('express'); const cookieParser = require('cookie-parser'); const app = express(); app.use(cookieParser()); app.get('/set', (req, res) => {   res.cookie('language', 'English', { maxAge: 60000 });   res.send('Cookie has been sent to your browser'); }); app.get('/read', (req, res) => {   const lang = req.cookies.language;   res.send(`Your preferred language is: ${lang}`); });

cookie-parser

To read cookies with req.cookies we need use cookie-parser npm package.

//Steps: 1. npm install cookie-parser 2. const cookieParser = require('cookie-parser'); //require 3. app.use(cookieParser()); // Parse cookies on every request

Signed Cookies

A signed cookie is a cookie that includes a signature to ensure it hasn't been tampered with. The value is encrypted using a secret key. If someone changes the cookie on the client-side, the signature won't match, and Express will ignore the cookie.

//Usage - Use cookie-parser with a Secret Key: app.use(cookieParser('mySecretKey')); // Pass the secret key here //this key used to sign and verify the cookies internally
//Sending signed cookies - Use the {signed: true} option: res.cookie('userID', '12345', { signed: true }); //This sets a cookie like: Set-Cookie: userID=s%3A12345.signature_hash

Receiving a Signed Cookie - req.signedCookies

You must use req.signedCookies (not req.cookies) to read signed cookies.

req.cookies - Unsigned Cookies
req.signedCookies - Signed Cookies

//Example: const id = req.signedCookies.userID; res.send(`Your ID: ${id}`);
//Complete Example - Signed Cookie Flow: const express = require('express'); const cookieParser = require('cookie-parser'); const app = express(); app.use(cookieParser('mySecretKey')); app.get('/set', (req, res) => {   res.cookie('session', 'abc123', { signed: true, maxAge: 60000 });   res.send('Signed cookie set'); }); app.get('/get', (req, res) => {   const session = req.signedCookies.session;   if (session) {     res.send(`Your session ID: ${session}`);   } else {     res.send('No valid signed cookie found');   } });

Deleting a Cookie

To delete a cookie in Express, use - res.clearCookie('cookieName', [options])

//Example: res.clearCookie('username'); //This tells the browser to Remove the cookie named username.
//Deleting a Signed Cookie: //When deleting a signed cookie, make sure you pass the same signed: true option: Because the cookie was signed when it was set res.clearCookie('userID', { signed: true });
//Full Example: const express = require('express'); const cookieParser = require('cookie-parser'); const app = express(); app.use(cookieParser('secret123')); app.get('/set', (req, res) => {   res.cookie('username', 'Inderjit', { maxAge: 60000 });   res.cookie('userID', '12345', { signed: true, maxAge: 60000 });   res.send('Cookies set'); }); app.get('/delete', (req, res) => {   res.clearCookie('username');   res.clearCookie('userID', { signed: true }); // Needed for signed cookies   res.send('Cookies deleted'); });

Cookie Options

Option Type Description
maxAge number Cookie expiration time in milliseconds (from now). E.g., 1000 * 60 * 60 = 1 hour
expires Date Exact date/time when the cookie expires. E.g., new Date(Date.now() + 3600000)
httpOnly boolean If true, cookie cannot be accessed by JavaScript on the client-side (helps prevent XSS)
secure boolean If true, cookie is sent only over HTTPS
path string URL path where cookie is valid (default is /)
domain string Domain where the cookie is available (e.g., .example.com)
signed boolean If true, signs the cookie with a secret (requires cookie-parser)
sameSite boolean or 'lax' | 'strict' | 'none' Controls cross-site cookie behavior (prevents CSRF)
priority 'low' | 'medium' | 'high' Suggests priority of cookie (not widely used)
encode function Custom function to encode cookie value
//Example: app.get('/set-cookie', (req, res) => {   res.cookie('sessionToken', 'abc123', {     maxAge: 1000 * 60 * 60 * 2, // 2 hours     expires: new Date(Date.now() + 7200000), // Same as above, 2 hours     httpOnly: true, // Not accessible via client-side JS     secure: false, // Set to true if using HTTPS     path: '/', // Valid for the entire site     domain: 'localhost', //Set domain (use your own domain in real apps)     signed: true, // Protect from tampering     sameSite: 'lax', // Helps prevent CSRF     priority: 'high', // Optional (not widely supported)     encode: (val) => encodeURIComponent(val), // Optional custom encoder   });   res.send('All cookie options applied'); });

Sessions

Sessions are a way to store and manage user-specific data across multiple requests or page visits during a user's interaction with a website. Since HTTP is a stateless protocol, sessions help maintain state, allowing servers to recognize and track users between requests.

  • Purpose - Store user-specific data (like login info, cart items) temporarily.
  • Storage - Data is stored on the server, and a unique ID (Session ID) is stored in the client's cookie.
  • Lifespan - Lasts until the user closes the browser or after a timeout.
  • Secure - More secure than cookies because sensitive data is not stored on the client.

How Sessions Work:

  1. User logs in → Server creates a session (e.g., for authentication).
  2. Server sends back a Session ID → Stored in the client's browser as a cookie.
  3. For each new request → Browser sends the Session ID to the server.
  4. Server uses the ID to fetch the correct session data.

State

State refers to remembering information about a user or application between different interactions.

Examples of State: A user is logged in or logged out, Items added to a shopping cart, A user's language preference. Without state, each new request would "forget" the user's actions.

Statefull Protocol e.g - FTP, Telnet, HTTPS (with sessions).


Stateless Protocols - forgets everything between requests. e.g - HTTP, UDP.

Since http is a stateless protocol that's why we use sessions to add state to stateless HTTP.

express-session

The most popular middleware to manage sessions in Express.

//Installation: npm install express-session //Setup: const session = require('express-session'); //require //Creating Session: app.use(session({   secret: 'your-secret-key', // used to sign the session ID cookie   resave: false, // don't save session if unmodified   saveUninitialized: false, // don't save new sessions unless something is stored   cookie: { secure: false } // set true if using HTTPS }));
//Example Use Case: Login //1. Store session data app.post('/login', (req, res) => {   // After verifying user credentials   req.session.username = 'Inderjit'; //we can create variables in session object like this   res.send('Logged in'); }); //2. Access session data app.get('/dashboard', (req, res) => {   if (req.session.username) {     res.send(`Welcome ${req.session.username}`);   } else {     res.send('Please log in first');   } }); //3. Destroy session (logout) app.get('/logout', (req, res) => {   req.session.destroy(err => { //use .destroy   if (err) return res.send('Error');     res.send('Logged out');   }); });

Session Storage

  • Session data is stored on the server — by default, it's saved in memory. This is fine for development but not suitable for production.
  • For production, use a persistent session store like Redis, MongoDB, or SQL DB to avoid data loss and support multiple servers.
  • Only the Session ID is stored in a cookie on the client-side. The actual session data stays on the server, linked by this ID.
  • A session ID remains the same across different tabs of the same browser, but it changes when accessed from a different browser or incognito/private mode.

Session Options

Option Suggested Value Description
secret (required) 'your-secret-key' Used to sign the session ID cookie. Should be a strong, random string.
resave (required) false Prevents unnecessary session save if not modified.
saveUninitialized (required) false Prevents saving empty sessions. Good for login-based apps.
cookie.secure true (HTTPS), false (HTTP) Send cookies only over HTTPS. Must be true in production with HTTPS.
cookie.maxAge 1000 * 60 * 60 (1 hour) Sets session expiry time (in milliseconds).
name 'sid' Custom name for the session ID cookie. Default is connect.sid.
rolling true Resets maxAge countdown on each request. Keeps user logged in.
store RedisStore, MongoStore, etc. Defines where to store session data (recommended in production).

there may other options also...

//Example: app.use(session({   secret: 'your-secret-key',   resave: false,   saveUninitialized: false,   name: 'sid',   cookie: {     secure: false, // Set to true if using HTTPS     maxAge: 1000 * 60 * 60 // 1 hour   },   rolling: true }));

connect-flash

connect-flash is a middleware used in Express.js to store and display flash messages — temporary messages used typically for notifications like success or error alerts (e.g., after a form submission or login attempt). These messages are stored in the session and removed after being displayed once.

Flash messages are stored in the session. First, must setup sessions as usual by enabling cookieParser and session middleware.

//Installation: npm install connect-flash //Setup: const flash = require('connect-flash'); //require // Setup flash middleware app.use(flash()); //Set a flash message: req.flash('key', 'message'); Flash Message Flow: Set a flash message: req.flash('key', 'message') Redirect to a page On the next page load, use req.flash('key') to get the message The message is removed from the session after being accessed once
//Example: //Register route app.get("/register", (req, res) => {   let { name = "anonymous" } = req.query;   req.session.name = name;   req.flash("success", "User registered successfully!"); //create flash message like this   res.redirect("/hello"); }); //Hello route app.get("/hello", (req, res) => {   res.render("page", { name: req.session.name, message: req.flash("success") });   // pass flash message like req.flash("key") to ejs file. }); //in ejs file use this message and style... <% message %>

res.locals

res.locals in Express.js is an object that contains local variables available only to the current response (i.e., for the current request). It's commonly used to pass data (like flash messages, user info, or settings) to views rendered by a template engine like EJS, Pug, etc.

You use res.locals to make data accessible in your EJS (or other view) templates without having to explicitly pass it in every res.render() call.

//Create res.local variable like this: res.locals.variableName = value; //Example: // Make flash messages available in all views - app.use app.use((req, res, next) => {   res.locals.success_msg = req.flash('success');   res.locals.error_msg = req.flash('error');   next(); }); // Routes: // Home Page app.get('/', (req, res) => {   res.render('home'); }); // Simulated Register Route app.post('/register', (req, res) => {   const { name } = req.body;   if (!name || name.trim() === '') {     req.flash('error', 'Name is required!');     return res.redirect('/');   }   req.session.name = name;   req.flash('success', `Welcome, ${name}! You have registered successfully.`);   res.redirect('/'); }); //in ejs something like: <% if (success_msg) { %>   <p style="color: green;">     <%= success_msg %>   </p> <% } %> <% if (error_msg) { %>   <p style="color: red;">     <%= error_msg %>   </p> <% } %>
without ejs we need to pass in res.render object

Authentication

Authentication is the process of verifying the identity of a user, device, or system, typically to grant access to resources or services. It ensures that the entity requesting access is who or what it claims to be.

Authentication Types:

  • Basic Authentication
  • Form-based Authentication
  • Session-based Authentication
  • Token-based Authentication (e.g., JWT)
  • OAuth 2.0 & OpenID Connect
  • Social Login (Google, Facebook, etc.)
  • Multi-Factor Authentication (MFA)
  • Biometric Authentication (Face ID, Fingerprint)

Common Technologies/Tools : Hashing Libraries (e.g., bcrypt, argon2), JWT Libraries (e.g., jsonwebtoken in Node.js), Passport.js (Node.js authentication middleware), Firebase Authentication, Auth0 / Okta / Supabase Auth, Django Authentication System, Laravel Breeze/Fortify/Jetstream e.t.c.

Security Concepts : Password Hashing & Salting, HTTPS (SSL/TLS), Cross-Site Request Forgery (CSRF), Cross-Site Scripting (XSS), Brute Force Attacks, Rate Limiting / Throttling, CAPTCHA Integration e.t.c.

There any many concepts/Technologies we can study about authentication later...

Storing Password

Hashing

Hashing is the process of converting data (like a password) into a fixed-length string of characters using a hash function. The output is called a hash or digest.

One-way process: You cannot reverse the hash to get the original input. Prefer slow algorithms in hashing.

Same output on same input so When logging in, the input password is hashed again and compared with the stored hash.

//means: User sets password: mypassword123 Server hashes it using gets something like: $2b$12$KIXQfzcS5CeDZDHLX0QWuOItCqx //Stores this hash in the database

Salting

A salt is random data added to the password before hashing. Prevents rainbow table attacks (precomputed hash lookup) it make hash unique.

hash(password + salt) → unpredictable hash value //means: hash(password123 + randomSalt) randomSalt can be any string character combination of any length. e.g - @#AEJ!#@H!@#@... On login: Retrieve salt Combine input password + salt Hash again and compare with stored hash

Passport.js

Passport.js is a popular authentication middleware for Node.js, designed to simplify the process of implementing user authentication in web applications. It's highly flexible, modular, and integrates seamlessly with Express-based apps, supporting over 500 authentication strategies, including local username/password, OAuth (e.g., Facebook, Google, Twitter), JWT, and more

Key Features of Passport.js:

  • Modular Design: Uses "strategies" as plugins to handle different authentication methods, allowing developers to pick and choose based on their needs.
  • Express-Compatible: Works as middleware in Express or Connect-based applications, requiring passport.initialize() and optionally passport.session() for persistent login sessions.
  • Session Management: Supports session-based authentication by serializing/deserializing user data, typically storing the user ID in the session and retrieving user details on subsequent requests.

Strategies

A strategy is a plugin that tells Passport how to authenticate a user. Each strategy represents a different way of logging in — like via username/password, Google, Facebook, or JWT tokens.

Example Strategies: passport-local(Username + password login), passport-jwt (JWT token-based login APIs), passport-google-oauth20, passport-facebook e.t.c we can explore on passportjs website.

To use specific strategy install it: npm i strategy_name. e.g - npm i passport-local. We can use multiple strategies also to give different authentication methods.

passport-local-mongoose

passport-local-mongoose is a Mongoose plugin that simplifies using Passport.js with Mongoose for local authentication (i.e., username & password).

What It Does:

  • Adds username + hashed password fields to your schema.
  • Adds methods like: .register() - to create users, .authenticate() - for login, .serializeUser() / .deserializeUser() - for session handling
  • Automatically hashes passwords using bcrypt under the hood.

npm i passport-local-mongoose //How to Use: //User Schema with Plugin: const mongoose = require('mongoose'); const passportLocalMongoose = require('passport-local-mongoose'); const UserSchema = new mongoose.Schema({}); UserSchema.plugin(passportLocalMongoose); //use plugin
//full example - login/signup with passport-local: npm install express mongoose passport passport-local passport-local-mongoose express-session connect-flash ejs //Mongoose User Model (models/user.js): const mongoose = require('mongoose'); const passportLocalMongoose = require('passport-local-mongoose'); const UserSchema = new mongoose.Schema({   email: String }); UserSchema.plugin(passportLocalMongoose); // Adds username + password hashing module.exports = mongoose.model('User', UserSchema); //app.js: const express = require('express'); const mongoose = require('mongoose'); const session = require('express-session'); const flash = require('connect-flash'); const passport = require('passport'); const LocalStrategy = require('passport-local'); const User = require('./models/user'); const app = express(); // MongoDB connection mongoose.connect('mongodb://127.0.0.1:27017/authDemo')   .then(() => console.log('MongoDB connected'))   .catch(err => console.log(err)); app.set('view engine', 'ejs'); app.use(express.urlencoded({ extended: true })); // Session(using session is must) & flash config app.use(session({ secret: 'secretkey123', resave: false, saveUninitialized: false })); app.use(flash()); // Passport config app.use(passport.initialize()); app.use(passport.session()); passport.use(new LocalStrategy(User.authenticate())); passport.serializeUser(User.serializeUser()); passport.deserializeUser(User.deserializeUser()); // Global flash + user app.use((req, res, next) => {   res.locals.currentUser = req.user;   res.locals.success = req.flash('success');   res.locals.error = req.flash('error');   next(); }); // Routes app.get('/register', (req, res) => {   res.render('register'); }); app.post('/register', async (req, res) => {   try {     const { username, email, password } = req.body;     const user = new User({ username, email });     const registeredUser = await User.register(user, password); //pass password here     req.login(registeredUser, err => {       if (err) return next(err);       req.flash('success', 'Welcome!');       res.redirect('/');     });   } catch (err) {     req.flash('error', err.message);     res.redirect('/register');   } }); app.get('/login', (req, res) => {   res.render('login'); }); app.post('/login', passport.authenticate('local', { failureFlash: true, failureRedirect: '/login' }), (req, res) => {   req.flash('success', 'Welcome back!');   res.redirect('/'); }); app.get('/logout', (req, res, next) => {   req.logout(err => {     if (err) return next(err);     req.flash('success', 'Logged out successfully.');     res.redirect('/');   }); }); // Middleware to protect routes function isLoggedIn(req, res, next) {   if (req.isAuthenticated()) return next();   req.flash('error', 'You must be logged in');   res.redirect('/login'); } app.get('/secret', isLoggedIn, (req, res) => {   res.send('This is a secret page. Only for logged-in users!'); }); app.listen(3000, () => console.log('Server started on http://localhost:3000'));

Authentication

Authorization is the process of determining what a user is allowed to do after they have been authenticated.

Authentication = Who are you?, Authorization = What can you access?

EJS(Embedded JavaScript)

EJS (Embedded JavaScript) is a templating engine for Node.js that lets you generate HTML dynamically by embedding JavaScript directly in your templates. It's lightweight, works well with Express.js, and is great for rendering dynamic web pages with data from your server.

File Extension - .ejs

To Install - npm install ejs
We need to require express to use this because it automatically requires ejs.

Example setup: const express = require('express'); const app = express(); // Set EJS as the templating engine app.set('view engine', 'ejs'); app.set('views', './views'); // Setting the folder for EJS templates (commonly named 'views', but you can use any folder name) OR app.set('views', './templates'); // if your EJS files are in a 'templates' folder

to run ejs from other directory:

To run an EJS-based Express app from a parent directory, you need to ensure the Express server correctly locates the EJS templates and other resources, even when the server is started from a directory outside the project folder.

example: const express = require('express'); const path = require('path'); //require path first const app = express(); app.set('view engine', 'ejs'); // Set the views directory using an absolute path app.set('views', path.join(__dirname, 'views')); //set this always

res.render()

The res.render method in Express.js is used to render a view template (like an EJS file) and send the resulting HTML to the client.

Syntax:

res.render('templateName', dataObject);
'templateName' → the name of your .ejs file (without .ejs)
dataObject → an optional object containing data to use in the template.

example: const express = require("express"); const app = express(); const path = require("path"); app.set("view engine", "ejs"); app.set("views", path.join(__dirname, "views")); //this app.get("/", (req, res) => {   res.render("home", { name: "Inderjit", age: 20 }); }); //OR like this app.get("/support", (req, res) => {   res.render("support"); }); app.listen(3000, () => {   console.log("Server running on http://localhost:3000"); });

Interpolation Syntax

Interpolation syntax in EJS refers to the method of embedding dynamic values (such as variables or expressions or server-side values) directly into HTML templates using special EJS tags. It allows server-side data to be inserted into the rendered HTML output.

There are two main interpolation tags:

1. <%= %> — Escaped Output Tag

Prints the evaluated value of a variable or expression. HTML characters like <,>, &, etc., are escaped (converted to safe entities like &lt;). Safe to use with user data to prevent XSS (cross-site scripting).

example: <%= "<h1>Welcome</h1>" %> //output for html: &lt;h1&gt;Welcome&lt;/h1&gt; //It shows the string as plain text, not HTML. //in browser <h1>Welcome</h1>

2. <%- %> — Unescaped Output Tag

Prints the evaluated value as raw HTML. Useful when you want to render actual HTML from a variable. Not safe for user input unless sanitized — it can expose your page to XSS attacks.

example: <%- "<h1>Welcome</h1>" %> //output for html: <h1>Welcome</h1> //It shows in bold after h1 works. //in browser

Welcome

Control & Utility Tags

Used for Logic or Structure. These tags are used not to display data, but to control the flow of logic, structure your templates, or manage whitespace/comments. These are essential for adding loops, conditions, comments, and clean formatting in templates.

1. <% %> - Scriptlet Tag (Control-Flow, No Output)

Executes JavaScript for control flow (e.g., loops, conditionals, variable assignments) without directly outputting to the HTML.

example: <% if (user.isAdmin) { %>   <p> Welcome, Admin! </p> <% } %>

2. <%_ %> - Whitespace Slurping Scriptlet Tag

Removes all whitespace (spaces, newlines) before the tag, useful for clean HTML output.

example: <ul<   <%_ notes.forEach(note => { %>     <li><%= note.title %></li>   <%_ }) %> </ul> //output for html like: <ul>   <li>JavaScript Async/Await</li> </ul> //without it may like: <ul>   <li>JavaScript Async/Await</li> </ul>

3. <%# %> - Comment Tag

Adds comments in EJS that are not included in the rendered HTML and are not executed.

example: <%# This is a comment, not visible in the HTML %> <p>Visible content</p> //output for html like: <p>Visible content</p>

4. <%% %> - Outputs a Literal

Escapes the <% delimiter, rendering it as literal text in the output. If you want to print a literal <% in the HTML (for educational content or documentation), use <%%.

example: <%%= "example" %> //output: <%= "example" %>

5. %> - Plain Ending Tag

Closes any EJS tag (<%, <%_, <%=, <%-, etc.).

6. -%> - Trim-Mode (Newline Slurp) Tag

Removes the newline character after the tag, reducing extra line breaks in the output, helping keep HTML compact.

example: <ul<   <%_ notes.forEach(note => { %>     <li><%= note.title %></li>   <%_ }) %> </ul> //output for html like: <ul>   <li>JavaScript Async/Await</li> </ul> //without it may like: <ul>   <li>JavaScript Async/Await</li> </ul>

7. _%> - Whitespace Slurping Ending Tag

Like -%>, but removes all whitespace after the tag, not just the newline. Useful for super clean output.

example: <ul<   <%_ notes.forEach(note => { %>     <li><%= note.title %></li>   <%_ }) %> </ul> //output for html like: <ul>   <li>JavaScript</li><li>Python</li> </ul>

Passing data to ejs

In EJS, data is passed from the Express server to the template via the res.render method.

res.render()

The res.render method in Express.js is used to render a view template (like an EJS file) and send the resulting HTML to the client.

Syntax:

res.render('templateName', dataObject);
'templateName' → the name of your .ejs file (without .ejs)
dataObject → an optional object containing data to use in the template.

example: const express = require("express"); const app = express(); const path = require("path"); app.set("view engine", "ejs"); app.set("views", path.join(__dirname, "views")); app.get("/", (req, res) => {   res.render("home", { name: "Inderjit", age: 20 }); }); app.listen(3000, () => {   console.log("Server running on http://localhost:3000"); }); //views/home.ejs <p>Welcome, <%= name %>!</p> <p>Your age is <%= age %>.</p> //output:

Welcome, Inderjit!

Your age is 20.

In EJS, the variable name in the template and the key in the object passed to res.render() should match.

If the key and value names are the same in the object, you can use shorthand syntax and write only one.

Example: app.get("/", (req, res) => {   const random = Math.random();   res.render("home", { random }); // shorthand for { random: random } }); // views/home.ejs <p>Random number: <%= random %></p>

Include

In EJS , the <%- include %> syntax is used to include one EJS file inside another. This is helpful for reusing common layouts like headers, footers, or navigation menus.

Syntax:

<%- include('path/to/file') %>

example: //index.ejs: <%- include('partials/header') %> //include header ejs <p>Welcome to My Website</p> <%- include('partials/footer') %> //include footer ejs //header.ejs: <header>   <p>This is the header</p> </header> //footer.ejs: <footer>   <p>Footer content goes here</p> </footer>
//after rendering like: <header>   <p>This is the header</p> </header> <p>Welcome to My Website</p> <footer>   <p>Footer content goes here</p> </footer>
//in browser like:

This is the header

Welcome to My Website

Footer content goes here

Use different folder for these files like above partials folder is used or you can use "includes" also as folder name.

Passing Data to Includes

You can pass variables to included files by adding a second argument to include like:

Example: <%- include('header', { title: 'My Custom Title' }) %> //same like passing data to ejs In header.ejs, you can use the passed variable like: <header> <h1> <%= title %> </h1> </header>

ejs-mate package

ejs-mate is like an upgrade kit for EJS — it gives your EJS templates superpowers such as layouts, reusable page parts, and content placeholders so you don't have to repeat the same HTML structure on every page. By using ejs-mate, you can define a base layout and have individual views “extend” it.

<% layout('layoutFileName') -%>
Must be at the top of your .ejs file. layoutFileName is relative to your views folder (no .ejs extension needed). This tells ejs-mate which master template to use.

Installation & Setup Example: npm install ejs-mate //add this also: const engine = require('ejs-mate'); //or any name in place of engine app.engine('ejs', engine);
Example Layout Workflow: // views/layout.ejs (master layout): <html> <head> <title><%- title %></title> </head> <body> <%- body %> </body> </html> // views/pages/home.ejs (child template): <% layout('layout') -%> <h1>Hello from Home Page</h1> <p>This is simple EJS-Mate in action!</p> //If you render home.ejs, it will insert the h1 and p into the <%- body %> spot in layout.ejs.

REST

REST (Representational State Transfer) is a widely used architectural style for designing web APIs, networked applications, particularly web services. It relies on a stateless, client-server communication model, usually over HTTP.

Key principles include:

  • Resource-Based: Everything is treated as a resource, and each resource is identified by a URI (Uniform Resource Identifier), like /users/1.
  • Stateless: Each request from a client to a server must contain all the information needed to process it.
  • Client-Server Architecture: The client and server operate independently. The client handles the user interface, and the server manages data and logic.
  • Representation: Resources are represented in formats such as JSON, XML, or HTML. The client interacts with the resource by using these representations.
  • Stateless Communication: No session data is stored on the server. Every request is independent.
  • Uniform Interface: A consistent way to access resources using standard HTTP methods - (CRUD operations):-
    To Retrieve data - GET (e.g., GET /users/123)
    To Create new data - POST (e.g., POST /users)
    To Update data - PUT (e.g., PUT /users/123)
    To Remove data - DELETE (e.g., DELETE /users/123)
    To Partially update a resource - PATCH

How to Creaete Unique IDs

To create unique IDs in your Express app (e.g. for each post), you have a few good options depending on how unique, readable, or persistent you want them to be.

1. Use crypto.randomUUID() (Built-in Node.js)

Use Like: //1.require: const { randomUUID } = require('crypto'); //2.use const newPost = {   id: randomUUID(), //this   username: "alice",   content: "Hello world" }; //Produces something like: "3f50d2b7-9a7e-40ae-9f0c-5071d2e426f5" //these are Universally unique, No extra dependencies

2. Use the uuid package (Very common in real projects)

Use Like: //1.Install It: npm install uuid //2.require: const { v4: uuidv4 } = require('uuid'); //3.use const newPost = {   id: uuidv4(), //this   username: "bob",   content: "Hello world" }; Output is similar to randomUUID().

3. Use a Simple Counter (for testing or in-memory arrays)

If you don't care about global uniqueness and just want simple incremental IDs (for development/testing only).

Use Like: let idCounter = 1; const newPost = {   id: idCounter++,   username: "test",   content: "Simple ID test" }; //IDs will reset every time the server restarts — not recommended for real use.

There are many other methods also...

Use PUT, PATCH, DELETE in html

Since HTML forms only support GET and POST methods via method attribute, but Express.js supports all HTTP methods (GET, POST, PUT, PATCH, DELETE), here's how you can use PUT, PATCH, and DELETE in HTML forms using a common workaround.

Method Override in Express

Use a middleware called method-override.

Step by step guide: 1. Install method-override: npm install method-override 2. Set up in your Express app: const methodOverride = require('method-override'); //require it app.use(methodOverride('_method')); // looks for _method in form fields //also we can use other name than _method but this is commom 3. Write HTML Form with POST but include _method hidden field: <form action="/users/1?_method=PUT" method="POST"> //use ?_method=PUT   //form fields </form> Or using hidden input: <form action="/users/1" method="POST">   <input type="hidden" name="_method" value="PUT">   //other form fields </form> Similarly, use _method=PATCH or _method=DELETE for other methods. 4. Define routes in Express: app.put('/users/:id', (req, res) => { //use method you want to use   res.send('User updated'); });

There are other ways also...