⚙️ Unit 10 • Backend Engineering

Backend Development: Node.js & Express APIs

A comprehensive engineering blueprint for production server architectures: The Node.js event-driven runtime, Libuv asynchronous thread pooling, Express HTTP server setup, the middleware onion pipeline, RESTful API design standards, environment configuration (Twelve-Factor), defensive error handling, Bcrypt password hashing, and stateless JWT token authentication.

Track Full-Stack Web Dev
Code & Level WD-110 • Server Architecture
Core Topics 10 Modules + Capstone
Pedagogy Formula 50% Theory • 30% Code • 20% Flow
Curriculum Structure: 50% In-Depth Explanatory Theory 30% Verified Node.js & Express Code 20% Visual Architectural Flowcharts
01

Introduction to Node.js & The V8 / Libuv Runtime Architecture

Created by Ryan Dahl in 2009, Node.js is neither a programming language nor a web framework. It is an open-source, cross-platform JavaScript runtime environment built on Google Chrome's high-performance V8 JavaScript Engine that executes JavaScript code outside of a browser sandbox.

⚡ The Concurrency Paradox: Single-Threaded Yet Highly Scalable

Traditional backend web servers (like Apache on PHP or Tomcat on Java) operate on a Thread-per-Request model. If 1,000 clients connect simultaneously, the operating system allocates 1,000 distinct execution threads. Each thread consumes ~1MB to 2MB of stack RAM, and the CPU wastes extensive clock cycles on heavy kernel context switching.

Node.js uses a Single-Threaded Event Loop with Non-Blocking Asynchronous I/O:

  • V8 Engine: Parses, compiles, and executes your JavaScript code on a single main thread (Call Stack).
  • Libuv C++ Library: A multi-platform support library providing the Event Loop and a background Worker Thread Pool (default: 4 threads). When Node.js encounters expensive operations—such as disk file I/O (fs), cryptographic hashing (crypto), or DNS lookups—it delegates them to Libuv's thread pool or the operating system kernel's asynchronous epoll/kqueue subsystem.
  • Zero Thread Blocking: The main JavaScript thread never halts or sleeps waiting for database records or network sockets. It immediately accepts the next incoming HTTP request, enabling Node.js to comfortably service tens of thousands of concurrent connections on a single machine!
RUNTIME TOPOLOGY: JAVASCRIPT CALL STACK ➔ LIBUV EVENT LOOP ➔ THREAD POOL FLOW-01
1. V8 Engine (Call Stack)
Executes JS code synchronously. Offloads asynchronous tasks (fs, db, crypto) to Libuv.
➔ offload ➔
2. Libuv Event Loop & Thread Pool
Underlying C++ threads handle disk I/O & OS kernel events asynchronously in background.
➔ callback ➔
3. Event Queue & Callback
Finished tasks queue callbacks. Event loop pushes them back to V8 when Call Stack is empty!
💻 Native Low-Level Node.js HTTP Server (Zero Dependencies)
// Importing native Node.js core module (ESM format)
import http from 'node:http';

// Create bare-bones HTTP server instance
const server = http.createServer((req, res) => {
  // Incoming request metadata
  const { method, url } = req;
  console.log(`[${new Date().toISOString()}] ${method} ${url}`);

  // Manual routing and header dispatch
  if (url === '/api/health' && method === 'GET') {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ status: 'healthy', uptime: process.uptime() }));
  } else {
    res.writeHead(404, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ error: 'Endpoint Not Found' }));
  }
});

const PORT = 3000;
server.listen(PORT, () => {
  console.log(`🚀 Native Node HTTP Server listening on http://localhost:${PORT}`);
});
Architecture Assessment
While native node:http demonstrates underlying TCP socket handling, manual URL parsing, header writing, and stream chunking becomes unmanageable. This is why enterprise architectures build upon Express.js.
02

Setting Up an Enterprise Express.js Server

Express.js is a fast, unopinionated, minimalist web framework for Node.js. It acts as an abstraction layer above node:http, introducing structured routing, parameter parsing, content-negotiation, and the foundational Middleware Pipeline.

🛡️ Production Server Hygiene & Graceful Shutdowns

In cloud environments (Docker, Kubernetes, AWS ECS), servers receive SIGTERM or SIGINT operating system termination signals during rolling redeployments. An enterprise server must:

  • Stop accepting new incoming TCP network connections.
  • Allow active in-flight HTTP requests to conclude cleanly without abrupt connection resets.
  • Close open database connection pools and Redis cache sockets gracefully before exiting (process.exit(0)).
💻 Enterprise Express Server Bootstrap with Graceful Shutdown
import express from 'express';

const app = express();
const PORT = process.env.PORT || 5000;

// Core Built-in Middleware
app.use(express.json({ limit: '10kb' })); // Parse application/json bodies (guard against payload bombs)
app.use(express.urlencoded({ extended: true }));

// Diagnostic Health Check (Used by Kubernetes / AWS Load Balancers)
app.get('/healthz', (req, res) => {
  res.status(200).json({
    status: 'OK',
    timestamp: new Date().toISOString(),
    memoryUsageMB: Math.round(process.memoryUsage().heapUsed / 1024 / 1024)
  });
});

// Start Listening
const server = app.listen(PORT, () => {
  console.log(`✅ Enterprise Express Server running in ${process.env.NODE_ENV || 'development'} on port ${PORT}`);
});

// Graceful Shutdown Orchestrator
function shutdownGracefully(signal) {
  console.log(`\n🛑 Received ${signal}. Closing HTTP server...`);
  server.close(() => {
    console.log('🔒 Closed out remaining connections. Exiting process safely.');
    process.exit(0);
  });
}

process.on('SIGTERM', () => shutdownGracefully('SIGTERM'));
process.on('SIGINT', () => shutdownGracefully('SIGINT'));
Terminal Execution Output
✅ Enterprise Express Server running in development on port 5000 GET /healthz 200 4.120 ms - 74 bytes ^C 🛑 Received SIGINT. Closing HTTP server... 🔒 Closed out remaining connections. Exiting process safely.
03

Handling Requests & Responses: Parameters, Queries, Headers & Bodies

In Express, every route handler receives an incoming Request (req) object representing the HTTP request and an outgoing Response (res) object used to send data back to the client.

Property Source Location Example URL / Payload Express Access Syntax Primary Engineering Use Case
Route Params URL Path Segments /api/nodes/node-89 req.params.id Identifying a specific unique entity.
Query Params URL Query String /api/nodes?page=2&sort=desc req.query.page Filtering, pagination, searching, sorting.
Request Body HTTP POST/PUT Payload {"name": "Cluster-A"} req.body.name Submitting structured data for creation/mutation.
HTTP Headers Request Metadata Authorization: Bearer <jwt> req.headers['authorization'] Authentication tokens, client API keys, content types.
💻 Multi-Source Request Extraction & Structured Response Dispatch
// Route: GET /api/v1/clusters/:clusterId/nodes?status=active&limit=10
app.get('/api/v1/clusters/:clusterId/nodes', (req, res) => {
  // 1. Extract Route Parameters
  const { clusterId } = req.params;

  // 2. Extract Query String Parameters with sensible fallbacks
  const statusFilter = req.query.status || 'all';
  const limit = parseInt(req.query.limit, 10) || 20;

  // 3. Inspect Headers
  const clientVersion = req.get('X-Client-Version') || 'legacy';

  // 4. Dispatch Structured RESTful JSON Response
  return res.status(200).json({
    success: true,
    meta: {
      clusterId,
      statusFilter,
      limit,
      clientVersion,
      count: 2
    },
    data: [
      { id: 'node-1', name: 'edge-east', status: 'active' },
      { id: 'node-2', name: 'edge-west', status: 'active' }
    ]
  });
});
Architecture Validation
res.json() automatically sets the Content-Type: application/json; charset=utf-8 header, serializes the JavaScript object into JSON, and flushes the socket.
04

Modular Express Routing Architecture (express.Router)

In large-scale codebases, registering hundreds of route handlers directly on the root app object produces unmaintainable spaghetti code. Express solves this via express.Router(), enabling developers to decompose routing tables into isolated, modular domain controllers.

DOMAIN-DRIVEN ROUTER TOPOLOGY: APP ROOT ➔ SUB-ROUTERS ➔ CONTROLLERS FLOW-02
Root app.use('/api/v1')
Mounts global API router. Directs paths to modular sub-routers based on resource prefix.
➔ routes to ➔
authRouter (/auth)
POST /register, POST /login, POST /refresh-token.
➔ or ➔
nodesRouter (/nodes)
GET /, POST /, GET /:id, PATCH /:id, DELETE /:id.
💻 Modular Resource Router (routes/nodesRouter.js)
import { Router } from 'express';

const router = Router();

// Route Chaining: Group HTTP verbs targeting the same URI path
router.route('/')
  .get((req, res) => {
    res.status(200).json({ nodes: [] });
  })
  .post((req, res) => {
    res.status(201).json({ message: 'Node provisioned successfully' });
  });

router.route('/:id')
  .get((req, res) => {
    res.status(200).json({ id: req.params.id, status: 'online' });
  })
  .delete((req, res) => {
    res.status(204).send(); // 204 No Content for successful deletion
  });

export default router;

// In main app.js:
// app.use('/api/v1/nodes', nodesRouter);
Architecture Note
By mounting nodesRouter at '/api/v1/nodes', route paths inside the sub-router stay clean: '/' maps to '/api/v1/nodes' and '/:id' maps to '/api/v1/nodes/:id'.
05

Middleware: The Core Pipeline Engine of Express

Express applications are fundamentally a stack of middleware functions. A middleware function is any function that has access to the Request object (req), the Response object (res), and the next callback function in the application’s request-response cycle.

🧅 The Middleware Onion Architecture

Middleware functions execute sequentially in the exact order they are registered via app.use(). A middleware can:

  • Execute arbitrary code (e.g., logging request timestamp and IP address).
  • Mutate the req and res objects (e.g., parsing token and appending req.user = decodedPayload).
  • Terminate the request-response cycle by sending a response (e.g., returning 401 Unauthorized).
  • Call next() to pass control to the next middleware: CRITICAL TRAP: If a middleware neither sends a response nor calls next(), the request will hang indefinitely until the client times out!
THE MIDDLEWARE PIPELINE: INCOMING REQUEST ➔ FILTERS ➔ ROUTE HANDLER FLOW-03
1. Logger & Timer
Captures req start time. Calls next().
➔ next() ➔
2. JSON Body Parser
Decodes buffer to req.body. Calls next().
➔ next() ➔
3. Auth Guard
Verifies JWT. Attaches req.user. Calls next().
➔ next() ➔
4. Controller Handler
Executes business logic. Dispatches res.json().
💻 Custom Latency Profiler & API Key Verification Middleware
// 1. Latency Profiler Middleware: calculates total execution duration
export const requestTimer = (req, res, next) => {
  const startHighResTime = process.hrtime();

  // Listen to response 'finish' event emitted when headers/body are flushed
  res.on('finish', () => {
    const diff = process.hrtime(startHighResTime);
    const durationMs = (diff[0] * 1e3 + diff[1] * 1e-6).toFixed(2);
    console.log(`⚡ [HTTP] ${req.method} ${req.originalUrl} - Status: ${res.statusCode} (${durationMs}ms)`);
  });

  next(); // Pass execution to next middleware in stack
};

// 2. API Key Guard Middleware: validates custom client header
export const requireApiKey = (req, res, next) => {
  const clientKey = req.headers['x-api-key'];
  
  if (!clientKey || clientKey !== process.env.INTERNAL_API_KEY) {
    // Terminate cycle early with 403 Forbidden
    return res.status(403).json({
      error: 'Forbidden',
      message: 'Invalid or missing X-API-Key header.'
    });
  }

  next(); // Key is valid; proceed!
};
Terminal Profile Output
⚡ [HTTP] GET /api/v1/telemetry/nodes - Status: 200 (1.45ms) ⚡ [HTTP] POST /api/v1/telemetry/nodes - Status: 403 (0.28ms)
06

RESTful API Design Standards & HTTP Status Taxonomy

REST (Representational State Transfer) is an architectural style for distributed hypermedia systems. Production REST APIs adhere to strict resource-oriented conventions and leverage the full expressive power of standard HTTP status codes.

📐 REST URI Design Conventions & Idempotency
  • Use Plural Nouns for Resources: Correct: /api/v1/servers. Incorrect: /api/v1/getServer or /api/v1/create_server. The HTTP verb already expresses the action!
  • Hierarchical Relationships: Represent child entities under their parents: /api/v1/clusters/:clusterId/nodes/:nodeId.
  • Idempotency: An HTTP method is idempotent if executing it multiple times produces the exact same server state as executing it once:
    • GET, HEAD, OPTIONS: Safe and idempotent (never mutate server state).
    • PUT: Idempotent (replaces entire target resource with payload; running 10 times results in identical state).
    • DELETE: Idempotent (deleting resource #5 leaves it deleted whether run once or 50 times).
    • POST: Non-idempotent (submitting 5 times creates 5 separate database rows!).
    • PATCH: Non-idempotent (partial mutations like {"balance": balance + 10} compound).
Status Code Category Meaning Standard Production Use Case
200 OK 2xx Success Request succeeded. Standard response for successful GET, PATCH, or PUT.
201 Created 2xx Success Resource generated. Successful POST (returns newly created entity + ID).
204 No Content 2xx Success Action succeeded with no body. Successful DELETE operation.
400 Bad Request 4xx Client Error Malformed request syntax. JSON validation failure, missing required body fields.
401 Unauthorized 4xx Client Error Lacks authentication. Missing or expired JWT / API key (Identity unverified).
403 Forbidden 4xx Client Error Authenticated but unauthorized. Valid user lacks permission to access resource (e.g. non-admin).
404 Not Found 4xx Client Error Resource does not exist. Invalid entity ID or unmapped route.
409 Conflict 4xx Client Error State collision. Attempting to register an email address that already exists.
500 Internal Error 5xx Server Error Unhandled exception. Uncaught code error, crashed database connection.
07

Configuration Management & Environment Variables (The 12-Factor App)

According to the industry-standard Twelve-Factor App Methodology (Factor III: Config), application code and configuration must be strictly separated. Database credentials, JWT signing secrets, API keys, and port numbers must never be committed into a Git repository.

🔒 The .env Architecture & Startup Validation (Fail-Fast)
  • .env File: Holds local environment secrets during development (e.g., JWT_SECRET=supersecret123). Must be listed in .gitignore!
  • .env.example File: Safe template committed to Git showing required configuration keys without sensitive values: JWT_SECRET=your_jwt_secret_here.
  • Fail-Fast Startup Validation: Never allow an API server to boot up with missing configuration. If DATABASE_URL is undefined, crash the server immediately on boot rather than failing hours later when a customer attempts to query the database.
💻 Fail-Fast Environment Configuration Loader (config/env.js)
import dotenv from 'dotenv';

// Load .env file into process.env
dotenv.config();

// Array of mandatory configuration keys
const REQUIRED_ENV_VARS = [
  'PORT',
  'NODE_ENV',
  'JWT_SECRET',
  'DATABASE_URL'
];

// Validate presence
const missingVars = REQUIRED_ENV_VARS.filter(key => !process.env[key]);

if (missingVars.length > 0) {
  console.error(`❌ FATAL: Missing mandatory environment variables: ${missingVars.join(', ')}`);
  console.error('Check your .env file against .env.example.');
  process.exit(1); // Exit immediately with error code
}

export const config = {
  port: parseInt(process.env.PORT, 10) || 5000,
  env: process.env.NODE_ENV,
  jwtSecret: process.env.JWT_SECRET,
  databaseUrl: process.env.DATABASE_URL
};
Startup Diagnostics
If JWT_SECRET is omitted: ❌ FATAL: Missing mandatory environment variables: JWT_SECRET Check your .env file against .env.example. Process terminated with code 1. Prevents insecure startup!
08

Defensive Error Handling & Centralized Error Middleware

In Node.js, an unhandled exception or unhandled promise rejection crashes the entire process, killing all active user sessions on the server. Production Express apps implement a Centralized Error Handling Architecture.

⚠️ The 4-Argument Error Middleware Signature

Express identifies error-handling middleware exclusively by the presence of exactly four parameters:

(err, req, res, next) => { ... }

If you define only three arguments (err, req, res), Express treats it as standard middleware and will fail to route errors to it! Whenever any middleware or controller calls next(err), Express skips all remaining regular middleware in the stack and jumps straight to the 4-argument error handler.

ERROR PROPAGATION: ASYNC REJECTION ➔ NEXT(ERR) ➔ CENTRALIZED HANDLER FLOW-04
1. Controller Error
Database query rejects or throw new AppError('Node not found', 404).
➔ next(err) ➔
2. Express Error Interceptor
Bypasses remaining middleware. Routes directly to (err, req, res, next).
➔ format ➔
3. Sanitized JSON Output
Hides internal C++ stack trace in production. Returns clean error JSON to client.
💻 Operational AppError Class & Centralized Error Handler
// 1. Custom Operational Error Class
export class AppError extends Error {
  constructor(message, statusCode) {
    super(message);
    this.statusCode = statusCode;
    this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
    this.isOperational = true; // Distinguishes predictable business errors from server bugs
    Error.captureStackTrace(this, this.constructor);
  }
}

// 2. Async Controller Wrapper (Eliminates repetitive try/catch blocks)
export const asyncHandler = (fn) => (req, res, next) => {
  Promise.resolve(fn(req, res, next)).catch(next);
};

// 3. Centralized 4-Argument Error Middleware
export const globalErrorHandler = (err, req, res, next) => {
  err.statusCode = err.statusCode || 500;
  err.status = err.status || 'error';

  if (process.env.NODE_ENV === 'development') {
    // Developer Mode: Full debug stack trace
    return res.status(err.statusCode).json({
      status: err.status,
      message: err.message,
      stack: err.stack,
      error: err
    });
  }

  // Production Mode: Sanitize and protect internal server architecture
  if (err.isOperational) {
    return res.status(err.statusCode).json({
      status: err.status,
      message: err.message
    });
  }

  // Unknown programmer bug: Don't leak details
  console.error('💥 UNHANDLED SYSTEM ERROR:', err);
  return res.status(500).json({
    status: 'error',
    message: 'Something went wrong on the server.'
  });
};
Security Architecture Note
Hiding err.stack in production prevents attackers from learning internal database schemas, absolute filesystem directories, and vulnerable package versions.
09

Authentication Foundations & Cryptographic Password Hashing (Bcrypt)

Authentication is the process of verifying who a user is (e.g., verifying email and password). Authorization is determining what resources an authenticated user is permitted to access (e.g., verifying if the user has an admin role).

⚠️ Why Fast Hashes (MD5, SHA-256) are Unsafe for Passwords

General-purpose cryptographic hash functions like SHA-256 or MD5 were designed for file checksums and digital signatures—meaning they are engineered to be extremely fast. Modern GPUs can calculate over 100 billion SHA-256 hashes per second. If an attacker steals your database, they can crack complex passwords within minutes using precomputed Rainbow Tables.

Passwords must be hashed using an intentionally slow, key-derivation function: Bcrypt.

  • Cryptographic Salt: Bcrypt generates a random 128-bit string of salt prepended to each password. This guarantees that two users with the identical password "password123" produce completely distinct hashes, neutralizing Rainbow Tables!
  • Configurable Work Factor (Cost): Bcrypt uses an exponential cost parameter (default: 10 to 12). A work factor of 12 takes ~250ms to calculate. While imperceptible to a logging-in user, it renders GPU brute-force attacks computationally impossible.
💻 Password Hashing & Verification with Bcrypt
import bcrypt from 'bcrypt';

const SALT_ROUNDS = 12;

// 1. Registration: Hash plain-text password before saving to database
export async function hashPassword(plainTextPassword) {
  // Automatically generates random salt and calculates hash
  const hashedPassword = await bcrypt.hash(plainTextPassword, SALT_ROUNDS);
  return hashedPassword;
}

// 2. Login: Compare incoming password attempt against stored database hash
export async function verifyPassword(incomingPassword, storedHash) {
  // Uses constant-time comparison to prevent timing attacks
  const isMatch = await bcrypt.compare(incomingPassword, storedHash);
  return isMatch;
}
Sample Bcrypt Hash Anatomy
$2b$12$e8qZ5d3Jp7Kq9Lw1X2Y3Z.4N5O6P7Q8R9S0T1U2V3W4X5Y6Z7A8B9 ├── 2b : Bcrypt specification identifier ├── 12 : Cost parameter (2^12 = 4,096 computation iterations) ├── e8qZ5d3Jp7Kq9Lw1X2Y3Z. : 22-character base64 random salt └── 4N5O6P7Q8R9S0T1U2V3W4X5Y6Z7A8B9 : 31-character cipher hash
10

Stateless Authentication with JSON Web Tokens (JWT)

In modern distributed architectures, servers do not store user sessions in server RAM. Instead, they issue an encoded, cryptographically signed JSON Web Token (JWT).

🎫 The Anatomy of a JSON Web Token

A JWT is a compact, URL-safe string composed of three distinct Base64URL-encoded segments delimited by periods (.):

header.payload.signature

  • 1. Header: Identifies the signing algorithm (e.g., {"alg": "HS256", "typ": "JWT"}).
  • 2. Payload (Claims): Contains identity assertions (e.g., {"sub": "usr_104", "role": "admin", "exp": 1729000000}). Note: The payload is Base64 encoded, not encrypted! Never store sensitive passwords or credit card numbers in a JWT payload.
  • 3. Signature: Cryptographic seal calculated by hashing the encoded header + encoded payload with a secret key: HMACSHA256(base64Url(header) + "." + base64Url(payload), secret). If an attacker tampers with the payload (e.g., changing role from user to admin), the signature mathematically invalidates!
STATELESS AUTHENTICATION FLOW: LOGIN ➔ TOKEN ISSUANCE ➔ BEARER VERIFICATION FLOW-05
1. Client POST /login
Client submits email & password. Server verifies against Bcrypt database hash.
➔ credentials OK ➔
2. Server Issues JWT
jwt.sign({ userId, role }, SECRET, { expiresIn: '1h' }). Sent back to client.
➔ Authorization: Bearer ➔
3. Stateless Verification
Server verifies signature locally using SECRET. Zero database lookups required!
💻 JWT Token Generation & Route Guard Middleware (authMiddleware.js)
import jwt from 'jsonwebtoken';

const JWT_SECRET = process.env.JWT_SECRET || 'super-secret-key-change-in-prod';

// 1. Generate Signed JWT Token
export function generateAuthToken(user) {
  return jwt.sign(
    { 
      userId: user.id, 
      email: user.email, 
      role: user.role 
    },
    JWT_SECRET,
    { expiresIn: '2h' } // Token automatically expires in 2 hours
  );
}

// 2. Route Protection Middleware
export function authenticateToken(req, res, next) {
  // Extract from 'Authorization: Bearer <token>' header
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.startsWith('Bearer ') 
    ? authHeader.split(' ')[1] 
    : null;

  if (!token) {
    return res.status(401).json({
      error: 'Unauthorized',
      message: 'Access token is missing or malformed.'
    });
  }

  // Verify signature and expiration
  jwt.verify(token, JWT_SECRET, (err, decodedPayload) => {
    if (err) {
      return res.status(403).json({
        error: 'Forbidden',
        message: err.name === 'TokenExpiredError' 
          ? 'Token expired. Re-authenticate.' 
          : 'Invalid token signature.'
      });
    }

    // Attach decoded user identity to request object
    req.user = decodedPayload;
    next(); // Proceed to protected route controller
  });
}
Execution Trace
When client sends valid token: req.user is populated with { userId: 'usr_104', role: 'admin' }. Downstream controllers immediately know the caller's identity without querying the database!
11

Unit 10 Practical Capstone: Secure Telemetry API Engine

This capstone builds a complete, production-ready RESTful backend API in a single verified file. It encapsulates password hashing, JWT authentication, protected CRUD routes, error handling, and latency profiling:

🚀 Verified Production Backend API Server (server.js)
import express from 'express';
import jwt from 'jsonwebtoken';
import bcrypt from 'bcrypt';

const app = express();
app.use(express.json());

const JWT_SECRET = 'devascent-telemetry-secret-key-2026';

// In-Memory Mock Databases
const usersDb = [];
const telemetryNodes = [
  { id: 'node-1', name: 'us-east-core', throughput: 14200 },
  { id: 'node-2', name: 'eu-west-edge', throughput: 8900 }
];

// --- Authentication Routes ---

// 1. User Registration
app.post('/api/v1/auth/register', async (req, res, next) => {
  try {
    const { email, password } = req.body;
    if (!email || !password || password.length < 6) {
      return res.status(400).json({ error: 'Email and 6+ character password required.' });
    }

    const existingUser = usersDb.find(u => u.email === email);
    if (existingUser) {
      return res.status(409).json({ error: 'User already registered.' });
    }

    const hashedPassword = await bcrypt.hash(password, 10);
    const newUser = { id: `usr_${Date.now()}`, email, password: hashedPassword, role: 'engineer' };
    usersDb.push(newUser);

    return res.status(201).json({ message: 'Account created successfully.', userId: newUser.id });
  } catch (err) {
    next(err);
  }
});

// 2. User Login
app.post('/api/v1/auth/login', async (req, res, next) => {
  try {
    const { email, password } = req.body;
    const user = usersDb.find(u => u.email === email);
    
    if (!user || !(await bcrypt.compare(password, user.password))) {
      return res.status(401).json({ error: 'Invalid email or password credentials.' });
    }

    const token = jwt.sign({ userId: user.id, role: user.role }, JWT_SECRET, { expiresIn: '1h' });
    return res.status(200).json({ token, expiresInSeconds: 3600 });
  } catch (err) {
    next(err);
  }
});

// --- Protected Telemetry CRUD Resource ---

// Auth Guard Middleware
const authGuard = (req, res, next) => {
  const authHeader = req.headers['authorization'];
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Missing or malformed Authorization Bearer token.' });
  }
  try {
    req.user = jwt.verify(authHeader.split(' ')[1], JWT_SECRET);
    next();
  } catch (err) {
    return res.status(403).json({ error: 'Invalid or expired token.' });
  }
};

// GET All Nodes (Protected)
app.get('/api/v1/telemetry/nodes', authGuard, (req, res) => {
  res.status(200).json({ totalNodes: telemetryNodes.length, nodes: telemetryNodes });
});

// POST Create Node (Protected)
app.post('/api/v1/telemetry/nodes', authGuard, (req, res) => {
  const { name, throughput } = req.body;
  if (!name) return res.status(400).json({ error: 'Node name is required.' });

  const newNode = { id: `node-${telemetryNodes.length + 1}`, name, throughput: throughput || 0 };
  telemetryNodes.push(newNode);
  res.status(201).json({ message: 'Node created', node: newNode });
});

// Centralized Error Handler Middleware
app.use((err, req, res, next) => {
  console.error('Server Error:', err);
  res.status(500).json({ error: 'Internal Server Error' });
});

const PORT = 5000;
app.listen(PORT, () => console.log(`🚀 Telemetry API Engine listening on port ${PORT}`));
Capstone Verification Flow
1. POST /api/v1/auth/register -> 201 Created (Stores password hashed with Bcrypt). 2. POST /api/v1/auth/login -> 200 OK (Returns signed JWT). 3. GET /api/v1/telemetry/nodes without header -> 401 Unauthorized. 4. GET /api/v1/telemetry/nodes with "Authorization: Bearer <jwt>" -> 200 OK with cluster list!
12

University Exam & Technical Interview Viva Preparation Guide

Q1. How does Node.js handle thousands of concurrent requests if its JavaScript engine is single-threaded?
Node.js utilizes an event-driven, non-blocking I/O model coordinated by the Libuv library. While JavaScript code executes on a single main thread (the V8 Call Stack), any slow I/O operations (file system reading, network database queries, cryptographic encryption) are offloaded to operating system kernel mechanisms (epoll/kqueue) or Libuv’s multi-threaded worker pool. The main thread never blocks; it continues accepting incoming connections and processes completion callbacks asynchronously as they land on the Event Loop queue.
Q2. What is the role of the next() function in Express middleware, and what happens if you forget to invoke it?
next() is the callback that signals Express to pass execution control to the subsequent middleware function in the application pipeline. If a middleware function does not send a response back to the client (e.g., via res.json() or res.send()) and fails to invoke next(), the request-response cycle becomes trapped and the client connection will hang indefinitely until an HTTP gateway timeout occurs.
Q3. Compare Session-based Authentication vs. Stateless JWT Token-based Authentication.
In Session-based Authentication, the server stores the session state in memory or a database (like Redis) and gives the client a cookie containing a session ID. Every subsequent request requires the server to query the session store to identify the user. In Stateless JWT Authentication, the user identity and claims are digitally signed and stored client-side in the token itself. The server verifies the token signature using a secret key locally with zero database roundtrips, making JWTs vastly superior for microservices and horizontally scaled multi-server clusters.
Q4. What are the three segments of a JSON Web Token (JWT)? Can a client decode the payload?
The three segments are: (1) Header (algorithm and token type), (2) Payload (user claims like userId and role), and (3) Signature (HMACSHA256 hash of the header, payload, and server secret). Yes, the client can read the payload because it is only Base64URL-encoded, not encrypted. Anyone can decode it. However, the client cannot alter the payload, because any alteration invalidates the cryptographic signature, causing the server to reject the token. Sensitive data (passwords) must never be stored in a JWT payload.
Q5. Why is Bcrypt preferred over MD5 or SHA-256 for hashing user passwords?
MD5 and SHA-256 are general-purpose hash algorithms designed to be computed very quickly. With modern GPUs capable of testing billions of SHA-256 hashes per second, stolen database dumps can be decrypted rapidly using Rainbow Tables. Bcrypt is an intentionally slow key-derivation function based on the Blowfish cipher. It includes an automatic random salt to defeat precomputed Rainbow Tables and a configurable work factor (cost) that exponentially increases calculation time, making brute-force cracking mathematically infeasible.
Q6. What is the fundamental difference between an HTTP 401 Unauthorized and an HTTP 403 Forbidden status code?
401 Unauthorized means unauthenticated: the client has not provided valid credentials (e.g., token is missing, expired, or invalid). The server does not know who you are. 403 Forbidden means authenticated but unauthorized: the server verified your identity successfully, but your role or permission level does not grant access to the requested resource (e.g., a standard employee attempting to delete an organization database).
Q7. How does Express distinguish an Error-Handling middleware from a standard middleware function?
Express detects error-handling middleware exclusively by checking fn.length === 4, meaning the function definition must explicitly declare four parameters: (err, req, res, next). Standard middleware declares two or three parameters (req, res, next). Whenever any code calls next(err) with an error argument, Express immediately skips all remaining standard middleware and jumps straight to the 4-argument error handler.
Q8. What makes an HTTP method "idempotent"? Which methods are idempotent and why?
An HTTP method is idempotent if making multiple identical requests has the exact same effect on the server state as making a single request. GET, PUT, and DELETE are idempotent: updating a record to a specific value with PUT or deleting an entity with DELETE produces the exact same server state whether executed once or ten times. POST is non-idempotent because executing 10 identical POST requests creates 10 separate duplicate entities in the database.