Personal reference guide covering Node.js, Express, EJS, REST, SQL, MongoDB, Mongoose, Authentication, Authorization, and MVC architecture.
- Node.js
- Express.js
- EJS Templating
- GET & POST Requests
- JavaScript OOP
- REST & CRUD
- SQL Fundamentals
- SQL with Node.js (MySQL2)
- MongoDB
- Mongoose
- Error Handling & Validation
- Authentication with Passport.js
- Authorization
- MVC Architecture
Node.js is an open-source, cross-platform runtime that executes JavaScript outside the browser using the V8 engine. It's event-driven and non-blocking, making it ideal for scalable network applications.
Download from nodejs.org and verify:
node -v
npm -vQuick way to test JavaScript expressions interactively:
node> 2 + 3
5
> const msg = "Hello, Node!"
> msg
'Hello, Node!'Exit with Ctrl+C twice or .exit.
// hello.js
console.log("Hello, Node.js!");node hello.jsconsole.log(process.pid); // Process ID
console.log(process.version); // Node.js version
console.log(process.platform); // OS platform// node myScript.js arg1 arg2
console.log(process.argv);
// ['node', 'myScript.js', 'arg1', 'arg2']// math.js
module.exports = {
add: (a, b) => a + b,
subtract: (a, b) => a - b,
};// main.js
const math = require('./math');
console.log(math.add(5, 3)); // 8
console.log(math.subtract(10, 4)); // 6// utils/index.js
exports.math = require('./math');
exports.strings = require('./strings');// main.js
const utils = require('./utils');
console.log(utils.math.add(7, 2));Requires "type": "module" in package.json:
// math.js
export function sum(a, b) { return a + b; }
export function subtract(a, b) { return a - b; }// main.js
import { sum } from './math.js';
console.log(sum(5, 3)); // 8| Command | Description |
|---|---|
npm init -y |
Initialize a new project |
npm install <pkg> |
Install a local package |
npm install -g <pkg> |
Install globally |
npm uninstall <pkg> |
Remove a package |
package.json tracks your dependencies and scripts. Commit it — never commit node_modules/.
Express is a minimal, unopinionated web framework for Node.js.
npm init -y
npm install express// app.js
const express = require('express');
const app = express();
const PORT = 3000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));node app.js💡 Use nodemon for auto-restart during development:
npm install -g nodemon→nodemon app.js
app.get('/', (req, res) => res.send('Home Page'));
app.post('/submit', (req, res) => res.send('Form submitted'));
app.use('/about', (req, res) => res.send('About Page'));
// 404 catch-all
app.get('*', (req, res) => res.status(404).send('Page not found'));res.send('<h1>Hello!</h1>'); // HTML
res.json({ message: 'Hello JSON' }); // JSON
res.redirect('/other-route'); // Redirect
res.status(404).send('Not Found'); // With status code// Path param: /users/42
app.get('/users/:id', (req, res) => {
const { id } = req.params;
res.send(`User ID: ${id}`);
});
// Query string: /search?q=node
app.get('/search', (req, res) => {
const { q } = req.query;
res.send(`Search: ${q}`);
});// routes/users.js
const express = require('express');
const router = express.Router();
router.get('/', (req, res) => res.send('All users'));
router.get('/:id', (req, res) => res.send(`User ${req.params.id}`));
module.exports = router;// app.js
const userRoutes = require('./routes/users');
app.use('/users', userRoutes);Middleware functions run between the request and response. Use next() to pass control forward.
// Custom middleware
app.use((req, res, next) => {
console.log(`${req.method} ${req.path}`);
next();
});
// Built-in: parse JSON and form data
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Serve static files from /public
app.use(express.static('public'));HTML forms only support GET/POST. Use this package to fake PUT/PATCH/DELETE:
npm install method-overrideconst methodOverride = require('method-override');
app.use(methodOverride('_method'));<form method="POST" action="/posts/42?_method=DELETE">
<button>Delete</button>
</form>EJS (Embedded JavaScript) lets you generate dynamic HTML from your server.
npm install ejsconst path = require('path');
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));| Tag | Purpose |
|---|---|
<%= value %> |
Output (HTML-escaped) |
<%- value %> |
Output (unescaped HTML) |
<% code %> |
Execute JS (no output) |
<%# comment %> |
Comment (not rendered) |
<%- include('file') %> |
Include partial |
app.get('/profile', (req, res) => {
res.render('profile', { username: 'alice', posts: [...] });
});<h1>Welcome, <%= username %>!</h1>
<% if (posts.length > 0) { %>
<ul>
<% posts.forEach(post => { %>
<li><%= post.title %></li>
<% }); %>
</ul>
<% } else { %>
<p>No posts yet.</p>
<% } %><%- include('partials/navbar') %>
<h1>Main content</h1>
<%- include('partials/footer') %>| GET | POST | |
|---|---|---|
| Data location | URL query string | Request body |
| Use case | Fetch / read data | Submit / modify data |
| Security | Visible in URL | Not visible in URL |
| Payload size | Limited | Large payloads OK |
app.use(express.urlencoded({ extended: true }));
app.post('/register', (req, res) => {
const { username, password } = req.body;
res.send(`Welcome, ${username}!`);
});<form method="POST" action="/register">
<input name="username" placeholder="Username">
<input name="password" type="password" placeholder="Password">
<button>Submit</button>
</form>Every object has a prototype. Methods added to a prototype are shared across all instances.
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
Person.prototype.getFullName = function () {
return `${this.firstName} ${this.lastName}`;
};
const john = new Person('John', 'Doe');
console.log(john.getFullName()); // John Doefunction createPerson(firstName, lastName) {
return {
firstName,
lastName,
getFullName() { return `${this.firstName} ${this.lastName}`; },
};
}class Animal {
constructor(name) { this.name = name; }
speak() { console.log(`${this.name} makes a sound.`); }
}
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
speak() { console.log(`${this.name} (${this.breed}) barks.`); }
}
const dog = new Dog('Buddy', 'Golden Retriever');
dog.speak(); // Buddy (Golden Retriever) barks.💡
extendssets up inheritance.super()calls the parent constructor — always required before usingthisin a subclass.
REST (Representational State Transfer) is an architectural style for designing web APIs. RESTful APIs use HTTP methods to perform CRUD operations on resources.
| HTTP Method | CRUD Action | Example Route |
|---|---|---|
GET |
Read | GET /posts |
POST |
Create | POST /posts |
PUT / PATCH |
Update | PATCH /posts/:id |
DELETE |
Delete | DELETE /posts/:id |
- Stateless — Each request carries all required info; server holds no client state.
- Uniform Interface — Consistent URL structure and HTTP methods.
- Client-Server — Frontend and backend are decoupled.
- Resource-Based — Resources are identified by URIs.
const { v4: uuidv4 } = require('uuid');
let posts = [];
// Create
app.post('/posts', (req, res) => {
const { username, content } = req.body;
posts.push({ id: uuidv4(), username, content });
res.redirect('/posts');
});
// Read all
app.get('/posts', (req, res) => {
res.render('index', { posts });
});
// Read one
app.get('/posts/:id', (req, res) => {
const post = posts.find(p => p.id === req.params.id);
res.render('show', { post });
});
// Update
app.patch('/posts/:id', (req, res) => {
const post = posts.find(p => p.id === req.params.id);
post.content = req.body.content;
res.redirect('/posts');
});
// Delete
app.delete('/posts/:id', (req, res) => {
posts = posts.filter(p => p.id !== req.params.id);
res.redirect('/posts');
});| SQL | NoSQL | |
|---|---|---|
| Structure | Relational tables | Documents, key-value, graph |
| Schema | Fixed | Flexible |
| Best for | Complex queries, transactions | Fast iteration, flexible data |
-- Database
CREATE DATABASE IF NOT EXISTS mydb;
USE mydb;
DROP DATABASE mydb;
SHOW DATABASES;
-- Table
CREATE TABLE employees (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
email VARCHAR(100) UNIQUE,
salary DECIMAL(10,2) DEFAULT 0,
hired_at DATE
);
ALTER TABLE employees ADD COLUMN phone VARCHAR(15);
ALTER TABLE employees DROP COLUMN phone;
ALTER TABLE employees RENAME TO staff;
TRUNCATE TABLE employees; -- removes all rows, keeps table
DROP TABLE employees; -- removes table entirely-- Insert
INSERT INTO employees (name, email, salary)
VALUES ('Alice', 'alice@example.com', 60000);
-- Read
SELECT * FROM employees;
SELECT name, salary FROM employees WHERE salary > 50000;
-- Update
UPDATE employees SET salary = 65000 WHERE name = 'Alice';
-- Delete
DELETE FROM employees WHERE id = 1;-- WHERE with operators: =, <>, >, <, >=, <=, AND, OR, NOT
SELECT * FROM employees WHERE salary > 50000 AND department = 'Engineering';
-- Aggregates
SELECT AVG(salary), MAX(salary), COUNT(*) FROM employees;
-- Group and filter
SELECT department, AVG(salary) AS avg_sal
FROM employees
GROUP BY department
HAVING AVG(salary) > 50000
ORDER BY avg_sal DESC;-- PRIMARY KEY, FOREIGN KEY, UNIQUE, CHECK, DEFAULT
CREATE TABLE orders (
id INT PRIMARY KEY,
customer_id INT,
status VARCHAR(20) DEFAULT 'Pending',
amount DECIMAL(10,2) CHECK (amount >= 0),
FOREIGN KEY (customer_id) REFERENCES customers(id)
);-- INNER JOIN: only matching rows
SELECT e.name, d.department_name
FROM employees e
INNER JOIN departments d ON e.department_id = d.id;
-- LEFT JOIN: all from left table + matching from right
SELECT e.name, d.department_name
FROM employees e
LEFT JOIN departments d ON e.department_id = d.id;SELECT → FROM → JOIN → WHERE → GROUP BY → HAVING → ORDER BY → LIMITnpm install mysql2 express ejs method-override @faker-js/fakerconst mysql = require('mysql2');
const connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'yourpassword',
database: 'mydb',
});// Read all users
app.get('/users', (req, res) => {
connection.query('SELECT * FROM users', (err, users) => {
if (err) throw err;
res.render('users', { users });
});
});
// Update user (with password check)
app.patch('/users/:id', (req, res) => {
const { id } = req.params;
const { newUsername, password } = req.body;
connection.query('SELECT * FROM users WHERE id = ?', [id], (err, results) => {
if (err) throw err;
const user = results[0];
if (user.password !== password) return res.send('Wrong password');
connection.query('UPDATE users SET username = ? WHERE id = ?',
[newUsername, id], (err) => {
if (err) throw err;
res.redirect('/users');
});
});
});
// Delete user
app.delete('/users/:id', (req, res) => {
connection.query('DELETE FROM users WHERE id = ?', [req.params.id], (err) => {
if (err) throw err;
res.redirect('/users');
});
});
⚠️ Always use parameterized queries (?placeholders) to prevent SQL injection.
MongoDB is a NoSQL document database. It stores data as BSON (Binary JSON) in collections of documents.
mongoshshow dbs # list all databases
use mydb # switch / create database
show collections # list collections in current db
db.dropDatabase() # delete current database# Insert
db.users.insertOne({ name: "Alice", age: 25 })
db.users.insertMany([{ name: "Bob" }, { name: "Carol" }])
# Find
db.users.find()
db.users.find({ age: { $gte: 25 } })
db.users.findOne({ name: "Alice" })
# Update
db.users.updateOne({ name: "Alice" }, { $set: { age: 26 } })
db.users.updateMany({ age: { $lt: 18 } }, { $set: { status: "minor" } })
# Delete
db.users.deleteOne({ name: "Alice" })
db.users.deleteMany({ age: { $lt: 18 } })| Operator | Meaning |
|---|---|
$eq |
Equals |
$gt / $gte |
Greater than / or equal |
$lt / $lte |
Less than / or equal |
$in |
Value in array |
$and |
Logical AND |
$or |
Logical OR |
$not |
Logical NOT |
db.users.find({ $or: [{ age: { $lt: 18 } }, { name: "Admin" }] }){
name: "John",
address: {
street: "123 Main St",
city: "New York",
zip: "10001"
}
}Mongoose is an ODM (Object Data Modeling) library for MongoDB and Node.js. It provides schema-based modeling, validation, and middleware.
npm install mongooseconst mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/mydb')
.then(() => console.log('Connected to MongoDB'))
.catch(err => console.error(err));const { Schema, model } = require('mongoose');
const listingSchema = new Schema({
title: { type: String, required: true },
description: String,
price: { type: Number, min: 0 },
location: String,
country: String,
createdAt: { type: Date, default: Date.now },
});
module.exports = model('Listing', listingSchema);const Listing = require('./models/Listing');
// Create
const listing = new Listing({ title: 'Beach House', price: 200 });
await listing.save();
// or: await Listing.create({ title: 'Beach House', price: 200 });
// Read
const all = await Listing.find();
const one = await Listing.findById(id);
// Update
await Listing.findByIdAndUpdate(id, { price: 250 }, { new: true });
// Delete
await Listing.findByIdAndDelete(id);const userSchema = new Schema({
username: { type: String, required: true, minlength: 3 },
email: { type: String, required: true, unique: true },
age: { type: Number, min: 0, max: 120 },
});Run code before or after operations:
// Auto-delete reviews when a listing is deleted
listingSchema.post('findOneAndDelete', async (listing) => {
if (listing) {
await Review.deleteMany({ _id: { $in: listing.reviews } });
}
});const listingSchema = new Schema({
title: String,
owner: { type: Schema.Types.ObjectId, ref: 'User' },
reviews: [{ type: Schema.Types.ObjectId, ref: 'Review' }],
});
// Populate owner when querying
const listing = await Listing.findById(id).populate('owner').populate('reviews');npm install joiconst Joi = require('joi');
const listingSchema = Joi.object({
listing: Joi.object({
title: Joi.string().required(),
price: Joi.number().min(0).required(),
location: Joi.string().required(),
}).required(),
});
// Middleware
const validateListing = (req, res, next) => {
const { error } = listingSchema.validate(req.body);
if (error) throw new ExpressError(400, error.details[0].message);
next();
};// utils/ExpressError.js
class ExpressError extends Error {
constructor(statusCode, message) {
super();
this.statusCode = statusCode;
this.message = message;
}
}
module.exports = ExpressError;Avoids repetitive try/catch in every async route:
// utils/wrapAsync.js
module.exports = (fn) => (req, res, next) => {
fn(req, res, next).catch(next);
};// Usage in routes
router.get('/:id', wrapAsync(async (req, res) => {
const listing = await Listing.findById(req.params.id);
if (!listing) throw new ExpressError(404, 'Listing not found');
res.render('listings/show', { listing });
}));Put this last in app.js:
app.use((err, req, res, next) => {
const { statusCode = 500, message = 'Something went wrong' } = err;
res.status(statusCode).render('error', { message });
});Authentication = verifying who the user is.
npm install passport passport-local passport-local-mongoose express-session connect-flashconst session = require('express-session');
const flash = require('connect-flash');
app.use(session({
secret: 'your-secret-key',
resave: false,
saveUninitialized: false,
cookie: { maxAge: 7 * 24 * 60 * 60 * 1000 }, // 1 week
}));
app.use(flash());const passport = require('passport');
const LocalStrategy = require('passport-local');
app.use(passport.initialize());
app.use(passport.session());
passport.use(new LocalStrategy(User.authenticate()));
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());const mongoose = require('mongoose');
const passportLocalMongoose = require('passport-local-mongoose');
const userSchema = new mongoose.Schema({
email: { type: String, required: true, unique: true },
});
userSchema.plugin(passportLocalMongoose); // adds username, hash, salt + helpers
module.exports = mongoose.model('User', userSchema);const User = require('../models/User');
// Register
router.post('/register', wrapAsync(async (req, res) => {
const { username, email, password } = req.body;
const user = new User({ username, email });
const registered = await User.register(user, password);
req.login(registered, err => {
if (err) return next(err);
req.flash('success', 'Welcome!');
res.redirect('/listings');
});
}));
// Login
router.post('/login', passport.authenticate('local', {
failureRedirect: '/login',
failureFlash: true,
}), (req, res) => {
req.flash('success', `Welcome back, ${req.user.username}!`);
res.redirect('/listings');
});
// Logout
router.post('/logout', (req, res, next) => {
req.logout(err => {
if (err) return next(err);
req.flash('success', 'Logged out successfully');
res.redirect('/listings');
});
});module.exports.isLoggedIn = (req, res, next) => {
if (req.isAuthenticated()) return next();
req.session.redirectUrl = req.originalUrl;
req.flash('error', 'Please log in to continue');
res.redirect('/login');
};// Make flash & current user available in all templates
app.use((req, res, next) => {
res.locals.success = req.flash('success');
res.locals.error = req.flash('error');
res.locals.currUser = req.user;
next();
});<% if (success.length) { %>
<div class="alert alert-success"><%= success %></div>
<% } %>
<% if (error.length) { %>
<div class="alert alert-danger"><%= error %></div>
<% } %>Authorization = verifying what a logged-in user is allowed to do.
module.exports.isOwner = async (req, res, next) => {
const { id } = req.params;
const listing = await Listing.findById(id);
if (!listing.owner._id.equals(res.locals.currUser._id)) {
req.flash('error', 'You are not the owner of this listing');
return res.redirect(`/listings/${id}`);
}
next();
};// Protect edit/delete routes
router.delete('/:id', isLoggedIn, isOwner, wrapAsync(async (req, res) => {
await Listing.findByIdAndDelete(req.params.id);
req.flash('success', 'Listing deleted');
res.redirect('/listings');
}));Only show edit/delete buttons to the owner — but never rely on this alone:
<% if (currUser && listing.owner._id.equals(currUser._id)) { %>
<a href="/listings/<%= listing._id %>/edit">Edit</a>
<form method="POST" action="/listings/<%= listing._id %>?_method=DELETE">
<button>Delete</button>
</form>
<% } %>
⚠️ Client-side authorization is UX only. Server-side middleware is the real security layer.
MVC (Model-View-Controller) separates concerns into three layers, making apps easier to scale and maintain.
| Layer | Responsibility |
|---|---|
| Model | Data, schema, DB interactions |
| View | HTML templates (EJS), what the user sees |
| Controller | Business logic, connects Model ↔ View |
project/
├── controllers/
│ ├── listings.js
│ └── users.js
├── models/
│ ├── listing.js
│ ├── review.js
│ └── user.js
├── routes/
│ ├── listing.js
│ └── user.js
├── views/
│ ├── listings/
│ └── layouts/
├── utils/
│ ├── ExpressError.js
│ └── wrapAsync.js
├── middleware.js
└── app.js
const mongoose = require('mongoose');
const Review = require('./review');
const listingSchema = new mongoose.Schema({
title: { type: String, required: true },
price: Number,
owner: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
reviews: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Review' }],
});
// Cascade delete reviews when listing is deleted
listingSchema.post('findOneAndDelete', async (listing) => {
if (listing) await Review.deleteMany({ _id: { $in: listing.reviews } });
});
module.exports = mongoose.model('Listing', listingSchema);const Listing = require('../models/listing');
module.exports.index = async (req, res) => {
const allListings = await Listing.find({});
res.render('listings/index', { allListings });
};
module.exports.create = async (req, res) => {
const listing = new Listing(req.body.listing);
listing.owner = req.user._id;
await listing.save();
req.flash('success', 'New listing created!');
res.redirect('/listings');
};
module.exports.destroy = async (req, res) => {
await Listing.findByIdAndDelete(req.params.id);
req.flash('success', 'Listing deleted');
res.redirect('/listings');
};router.route() groups multiple HTTP methods under the same path for cleaner code:
const express = require('express');
const router = express.Router();
const { isLoggedIn, isOwner, validateListing } = require('../middleware');
const listingController = require('../controllers/listings');
const wrapAsync = require('../utils/wrapAsync');
const multer = require('multer');
const { storage } = require('../cloudConfig');
const upload = multer({ storage });
router.route('/')
.get(wrapAsync(listingController.index))
.post(isLoggedIn, upload.single('listing[image]'), validateListing, wrapAsync(listingController.create));
router.route('/:id')
.get(wrapAsync(listingController.show))
.patch(isLoggedIn, isOwner, upload.single('listing[image]'), validateListing, wrapAsync(listingController.update))
.delete(isLoggedIn, isOwner, wrapAsync(listingController.destroy));
module.exports = router;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 methodOverride = require('method-override');
const path = require('path');
const User = require('./models/user');
const listingRoutes = require('./routes/listing');
const userRoutes = require('./routes/user');
const app = express();
mongoose.connect('mongodb://localhost:27017/mydb');
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));
app.use(express.urlencoded({ extended: true }));
app.use(methodOverride('_method'));
app.use(express.static(path.join(__dirname, 'public')));
app.use(session({ secret: 'mysecret', resave: false, saveUninitialized: false }));
app.use(flash());
app.use(passport.initialize());
app.use(passport.session());
passport.use(new LocalStrategy(User.authenticate()));
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());
// Make flash + user available in all templates
app.use((req, res, next) => {
res.locals.success = req.flash('success');
res.locals.error = req.flash('error');
res.locals.currUser = req.user;
next();
});
app.use('/listings', listingRoutes);
app.use('/', userRoutes);
// Global error handler
app.use((err, req, res, next) => {
const { statusCode = 500, message = 'Something went wrong' } = err;
res.status(statusCode).render('error', { message });
});
app.listen(3000, () => console.log('Server running on port 3000'));Notes by me — built while learning the MERN/Node stack. Update as needed. Source: https://github.com/MaanasSehgal/Backend-Notes