Node.js Tutorial

Node.js is a JavaScript runtime built on Chrome's V8 engine that lets you run JavaScript on the server side. It's fast, non-blocking, and perfect for building APIs, real-time apps, and microservices.

What is Node.js?

  • Created by Ryan Dahl in 2009. Uses Google's V8 JavaScript engine.
  • Event-driven, non-blocking I/O - handles many connections concurrently.
  • Great for: REST APIs, real-time chat (WebSockets), file servers, CLI tools, microservices.
  • npm (Node Package Manager) - largest software registry in the world.
Terminal
# Check installed version
node --version    # v20.x.x
npm  --version    # 10.x.x

# Run a script
node app.js

# Interactive REPL
node
► Try It Yourself

Modules

JavaScript (Node)
// CommonJS (default in Node.js)
// math.js
const PI = 3.14159;
function add(a, b) { return a + b; }
module.exports = { PI, add };

// app.js
const { PI, add } = require("./math");
const path = require("path");  // built-in modules use strings, no ./

// ES Modules (add "type": "module" to package.json)
// math.mjs
export const PI = 3.14159;
export function add(a, b) { return a + b; }
export default function main() {}

// app.mjs
import main, { PI, add } from "./math.mjs";
import { readFile } from "fs/promises";     // named import from built-in
► Try It Yourself

npm & package.json

Terminal / JSON
# Initialize project
npm init -y

# Install package
npm install express
npm install -D nodemon  # dev dependency
npm install -g typescript  # global

# Other commands
npm uninstall lodash
npm update
npm outdated
npm audit fix
npm run build

// package.json
{
  "name": "my-api",
  "version": "1.0.0",
  "scripts": {
    "start": "node src/index.js",
    "dev":   "nodemon src/index.js",
    "test":  "jest"
  },
  "dependencies": {
    "express": "^4.18.0"
  },
  "devDependencies": {
    "nodemon": "^3.0.0"
  }
}
► Try It Yourself

File System (fs)

JavaScript (Node)
const fs = require("fs");
const { readFile, writeFile, appendFile, mkdir, readdir } = require("fs/promises");
const path = require("path");

// Async (Promises - recommended)
async function main() {
  // Read file
  const content = await readFile("./README.md", "utf-8");
  console.log(content);

  // Write file (creates/overwrites)
  await writeFile("./output.txt", "Hello Node!\n", "utf-8");

  // Append
  await appendFile("./log.txt", `[${new Date().toISOString()}] Event\n`);

  // Create directory
  await mkdir("./uploads", { recursive: true });

  // List directory
  const files = await readdir("./");
  console.log(files);

  // Check if file exists
  try { await fs.promises.access("./file.txt"); console.log("exists"); }
  catch { console.log("not found"); }
}

// Path utilities
path.join(__dirname, "data", "file.json");   // OS-safe path
path.resolve("./src/index.js");              // absolute
path.basename("/path/to/file.txt");          // "file.txt"
path.extname("file.txt");                    // ".txt"
path.dirname("/a/b/c.js");                   // "/a/b"
► Try It Yourself

Express.js

JavaScript (Node)
const express = require("express");
const app     = express();

// Built-in middleware
app.use(express.json());                         // parse JSON body
app.use(express.urlencoded({ extended: true })); // parse form data
app.use(express.static("public"));               // serve static files

// Routes
app.get("/", (req, res) => {
  res.send("Hello World!");
});

app.get("/about", (req, res) => {
  res.sendFile(__dirname + "/public/about.html");
});

// Query params: GET /search?q=laptop&page=2
app.get("/search", (req, res) => {
  const { q, page = 1 } = req.query;
  res.json({ query: q, page: Number(page) });
});

// Route params: GET /users/42
app.get("/users/:id", (req, res) => {
  const { id } = req.params;
  res.json({ userId: id });
});

// POST with JSON body
app.post("/users", (req, res) => {
  const { name, email } = req.body;
  // validate, save to DB...
  res.status(201).json({ id: 1, name, email });
});

// Start server
app.listen(3000, () => console.log("Server running on http://localhost:3000"));
► Try It Yourself

Building a REST API

JavaScript (Node)
// routes/products.js - Router module
const router = require("express").Router();

// In-memory store (replace with DB in production)
let products = [
  { id: 1, name: "Laptop", price: 80000 },
  { id: 2, name: "Keyboard", price: 2500 }
];
let nextId = 3;

// GET /api/products
router.get("/", (req, res) => res.json(products));

// GET /api/products/:id
router.get("/:id", (req, res) => {
  const p = products.find(p => p.id === Number(req.params.id));
  if (!p) return res.status(404).json({ error: "Not found" });
  res.json(p);
});

// POST /api/products
router.post("/", (req, res) => {
  const { name, price } = req.body;
  if (!name || !price) return res.status(400).json({ error: "name and price required" });
  const product = { id: nextId++, name, price: Number(price) };
  products.push(product);
  res.status(201).json(product);
});

// PUT /api/products/:id
router.put("/:id", (req, res) => {
  const idx = products.findIndex(p => p.id === Number(req.params.id));
  if (idx === -1) return res.status(404).json({ error: "Not found" });
  products[idx] = { ...products[idx], ...req.body, id: products[idx].id };
  res.json(products[idx]);
});

// DELETE /api/products/:id
router.delete("/:id", (req, res) => {
  products = products.filter(p => p.id !== Number(req.params.id));
  res.status(204).end();
});

module.exports = router;

// app.js - mount router
app.use("/api/products", require("./routes/products"));
► Try It Yourself

JWT Authentication

JavaScript (Node)
// npm install jsonwebtoken bcryptjs

const jwt    = require("jsonwebtoken");
const bcrypt = require("bcryptjs");
const SECRET = process.env.JWT_SECRET; // store in .env!

// Register - hash password
app.post("/auth/register", async (req, res) => {
  const { email, password } = req.body;
  const hashed = await bcrypt.hash(password, 12);
  const user = await User.create({ email, password: hashed });
  res.status(201).json({ id: user.id, email });
});

// Login - issue token
app.post("/auth/login", async (req, res) => {
  const { email, password } = req.body;
  const user = await User.findOne({ email });
  if (!user || !(await bcrypt.compare(password, user.password)))
    return res.status(401).json({ error: "Invalid credentials" });

  const token = jwt.sign({ userId: user.id, role: user.role }, SECRET, { expiresIn: "7d" });
  res.json({ token });
});

// Auth middleware
function requireAuth(req, res, next) {
  const auth = req.headers.authorization;
  if (!auth?.startsWith("Bearer ")) return res.status(401).json({ error: "No token" });
  try {
    req.user = jwt.verify(auth.slice(7), SECRET);
    next();
  } catch { res.status(401).json({ error: "Invalid token" }); }
}

// Protected route
app.get("/api/profile", requireAuth, (req, res) => {
  res.json({ userId: req.user.userId });
});
► Try It Yourself

Environment Variables

bash / JavaScript
# .env file (NEVER commit to git)
PORT=3000
DB_URL=mongodb://localhost:27017/myapp
JWT_SECRET=super_secret_key_change_this
NODE_ENV=development

# .gitignore
.env
node_modules/

// Load with dotenv (npm install dotenv)
require("dotenv").config();   // CommonJS (at top of entry file)
// or
import "dotenv/config";       // ESM

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Server on port ${PORT}`));
► Try It Yourself

Async Patterns

Node.js apps spend a lot of time waiting for disk, network, and database operations. Learn to structure async code clearly.

Node.js
import fs from 'node:fs/promises';

async function loadConfig() {
  try {
    const text = await fs.readFile('./config.json', 'utf8');
    return JSON.parse(text);
  } catch (error) {
    console.error('Could not load config:', error.message);
    throw error;
  }
}

Validation & Security

  • Validate request bodies before business logic runs.
  • Never trust IDs, emails, or roles coming from the client.
  • Store secrets in environment variables, not source files.
  • Use rate limiting, secure headers, and input validation in public APIs.
  • Hash passwords with bcrypt or argon2, never plain text.

Testing & Logging

Production backends need tests and logs. Tests protect behavior; logs help you understand failures after deployment.

Node.js
app.get('/health', (req, res) => {
  console.info('Health check requested');
  res.json({ ok: true, uptime: process.uptime() });
});