Skip to content
Chapter 06 · Slide Breakdown

Server-Side: Node.js and Express

JavaScript moves to the server. This is the chapter where the course stops being about pages and starts being about applications — and where asynchronous code stops being optional. Everything in chapter 7 is built on top of the Express app you write here.

51 slidesSingle deckPure write-itBook ch. 13

What this chapter is really for

Slide 1 is a leftover chapter 5 task (make a table appear when a heading is clicked using DOM manipulation) — do it, then start here.

The deck moves through four stages, and each one exists because the previous one hit a wall:

  1. Node basics (3–14) — modules, fs, http. You build a server by hand and immediately see how tedious routing is.
  2. Async (15–28) — why Node is non-blocking, callback hell, promises, fetch. Because the raw callbacks in stage 1 do not scale.
  3. npm and Express (29–45) — packages, middleware, routing, three ways to receive parameters. Because hand-written routing does not scale either.
  4. EJS (46–50) — generating HTML from data on the server.

The idea that makes Node make sense

Node is non-blocking, asynchronous and single-threaded. One worker services every request in one event loop, delegating slow work to other agents. That single sentence explains the advantages, the disadvantages, and why every file operation takes a callback.

Where it connects

Chapter 1’s HTTP methods and ports become app.get and app.listen. Chapter 2’s form action and method finally point somewhere real. Chapter 4’s callbacks become promises. And chapter 7 plugs a database into the routes you write here.

All 51 slides, weighted

SlidesTopicWeightWhat to do with it
1Chapter 5 leftover taskWrite itDOM manipulation to show a table on click. Ten minutes, and it revises the previous chapter.
2–4Introducing Node; JavaScript everywhereMemorizeFive advantages: JavaScript everywhere, push architectures, non-blocking architectures, a rich tool ecosystem, broad adoption.
5–6Name conflicts; what a module isMemorizeThere is no function overloading in JavaScript — the second declaration simply replaces the first. Modules solve the resulting name conflicts.
7–8Running a Node app; import/exportWrite itnode app.js or just node app; Ctrl-C to stop. module.exports and require.
9Node core modulesMemorizeSix to know: http, url, querystring, path, fs, util.
10–14fs module; http module; simple and static serversWrite itType the simplest HTTP server from memory. It is nine lines and it is the most likely practical question in the first half.
15–18Blocking vs non-blocking; high volume; disadvantagesMemorizeThe restaurant analogy is the exam answer. Learn both disadvantages: relational databases were awkward, and computation-heavy work blocks the single thread.
19–21Asynchronous coding; the async fs example; callback hellMemorizeSlide 21 is the deliberate demonstration of callback hell — nested readFiles so the second can see the first result.
22–23PromisesWrite itThe single most important construct in the chapter. Know resolve/reject, then/catch, and how to wrap a callback API in one.
24–28fetch; what it returns; common mistakesWrite itfetch does not return the data — it returns a Promise. A web API is a web resource that returns data instead of HTML, CSS, JavaScript or images.
29npm, package.json, dependenciesMemorizenpm init -y then npm install express. Know what each does.
30CORSMemorizeBrowsers block cross-origin requests by default. Access-Control-Allow-Origin: *, or app.use(cors()) in Node.
31–33Semantic versioning; .gitignore; dev dependenciesMemorizeMAJOR.MINOR.PATCH, and what ~ and ^ each allow. Never commit node_modules.
34–36Express, static files, middlewareWrite itMiddleware is the chain of responsibility pattern. app.use() installs a function on that chain.
37–38Routing; chained middlewareWrite itThe four-handler pattern — static, two routes, and a catch-all 404 — is the shape of every Express app you will write.
39–40Environment variables; a simple APIWrite itdotenv, a .env file, and process.env.PORT. Used in every later example.
41–42Separating functionality into modulesWrite itOnce you have five or six routes a single file becomes too complex. This is the refactor pattern for the project.
43–45Three ways to receive parametersWrite itRoute params :word, query params ?first=, and form bodies. Know which object each lands in.
46–50View engines and EJS, including partialsWrite itLearn the two tag forms — <% %> runs code, <%= %> prints a value — and the <%- include %> partial.
51Supporting materialSkimInstallation links and a video walkthrough.

Modules, files and a server by hand

Why modules exist

the problem — slide 5Slide 5
// JavaScript has NO FUNCTION OVERLOADING. The second declaration
// simply replaces the first — silently.
let product = (x, y)    => x * y;
let product = (x, y, z) => x * y * z;

console.log(product(2, 3));   // NaN  — z is undefined, and 6 * undefined = NaN

// With hundreds of literals across dozens of .js files, you need some
// way to prevent name conflicts. That way is MODULES: literals defined
// within a module are SCOPED TO THAT MODULE.
modules — CommonJSSlides 6–8
// ── mod-names.js ───────────────────────────────────────────────
const secret = 'SECRET PHRASE';      // local — NOT exported, stays private

const first_name = 'salah';
const last_name  = 'abid';
module.exports = { first_name, last_name };   // export an OBJECT

// ── mod-utils.js ───────────────────────────────────────────────
const saySelem = (name) => { console.log(`Selem Mr ${name}`); };
module.exports = saySelem;                    // export a FUNCTION (default style)

// ── app.js ─────────────────────────────────────────────────────
const names = require('./mod-names');    // ./ — a LOCAL file, not an npm package
const selem = require('./mod-utils');

selem(names.first_name);   // "Selem Mr salah"
selem(names.secret);       // "Selem Mr undefined"  ← not exported = not visible

// RUN IT:   node app.js     or just   node app
// STOP IT:  Ctrl-C

// On the CLIENT side you must tell the browser a file is a module:
//   <script src="art.js" type="module"></script>
// In Node every file is a module by default (CommonJS).

Core modules

ModuleWhat it gives you
httpClasses, methods and events to create a Node HTTP server.
urlMethods for URL resolution and parsing.
querystringMethods to deal with query strings.
pathMethods to deal with file paths.
fsClasses, methods and events for file I/O.
utilUtility functions.

The fs module — synchronous first

app.js — 02-filesSlide 10
const { readFileSync, writeFileSync } = require('fs');   // destructured import

console.log('start');

const first  = readFileSync('./content/first.txt',  'utf8');
const second = readFileSync('./content/second.txt', 'utf8');

writeFileSync(
    './content/result.txt',
    `Here is the result : ${first}, ${second}` + "\n",
    { flag: 'a' }        // 'a' for APPENDING (default would overwrite)
);

console.log('Task completed!');

// OUTPUT:  start
//          Task completed!
// …in that order, because Sync versions BLOCK until they finish.

A server by hand — the http module

simplest-server.jsSlide 12
const http = require('http');

const server = http.createServer((req, res) => {
    res.write("This is my response to your request!");
    res.end();                     // end() is REQUIRED — without it the browser hangs
    return;
});

server.listen(5000);
console.log("Listening on port 5000");

// > node simplest-server
// Then open http://127.0.0.1:5000 in the browser.
//
// NOTE: http://127.0.0.1:5000/anything/at/all gives the SAME response.
// There is no routing here at all — which is the point of the next slide.
simplest-server-2.js — routing by handSlide 13
const http = require('http');

const server = http.createServer((req, res) => {
    if (req.url === '/') {
        res.end("This is the homepage!");
    } else if (req.url === '/about') {
        res.end("This is the ABOUT page!");
    } else {
        res.end(`<h1>Page not Found!</h1>
                 <p><a href="/">Homepage</a></p>`);
    }
    return;
});

server.listen(5000);

// A fuller version writes headers explicitly:
//   res.writeHead(200, {"Content-Type": "text/plain"});
//
// Now imagine twenty routes in this if/else chain. That is why Express
// exists — and why slide 34 arrives when it does.

Non-blocking architecture, promises and fetch

Blocking vs non-blocking — the restaurant analogy

Blocking (Apache with JEE or PHP)Non-blocking (Node)
A blocking multiprocessing or multithreaded model.A single worker services all requests in a single event loop thread.
As though a single person had to handle every task for each table — so you need one person per table.The worker can only do one thing at a time, but delegates other tasks to other agents and carries on.
Scales by adding processes or threads.Node is non-blocking, asynchronous and single-threaded.

Where Node shines: data-intensive real-time applications talking to distributed computers, with NoSQL data sources. The slide’s example is a Like button handling a massive number of concurrent writes — a memory-based message queue records the changes and they are persisted eventually.

Where Node does not: sites whose data lives in a traditional relational database such as MySQL, where access was a complex programming task; and computationally heavy work such as video processing or scientific computing, which stalls the single thread.

Callback hell — the problem, demonstrated

app.js — 04-files-asyncSlide 20
const { readFile, writeFile } = require('fs');   // the ASYNC versions

console.log('Starting task A...');

readFile('./content/first.txt', 'utf8', (err, result) => {
    if (err) { console.log(err); return; }      // error-first callback convention

    writeFile('./content/result-async.txt',
        `Here is the Async result : ${result}`,
        (err, result) => {
            if (err) { console.log(err); return; }
            console.log('Task A completed!');
        }
    );
});

console.log('starting next task...');

// OUTPUT ORDER — the whole point:
//   Starting task A...
//   starting next task...     ← this line runs BEFORE the file is read
//   Task A completed!         ← the callback fires last
the same thing with TWO files — slide 21Slide 21
// To let the second read see the first result, the second readFile has
// to be called INSIDE the first callback. Add a third file and a fourth
// and you get a staircase drifting off the right of the screen.
readFile('./content/first.txt', 'utf8', (err, result) => {
    if (err) { console.log(err); return; }
    const first = result;

    readFile('./content/second.txt', 'utf8', (err, result) => {
        if (err) { console.log(err); return; }
        const second = result;

        writeFile('./content/result-async.txt',
            `Here is the result : ${first}, ${second}`,
            (err, result) => {
                if (err) { console.log(err); return; }
                console.log('done with this task');
            }
        );
    });
});

//                    ← ← ←   CALLBACK HELL   → → →

Promises — the solution

A Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value. Probably the promise will complete and you receive the data; or it will not, and you receive an error instead.

promises.jsSlides 22–23
// ── DECLARATION AND INSTANTIATION ──────────────────────────────
// The handler passed to the constructor takes TWO parameters. 
const promiseObj = new Promise((resolve, reject) => {
    doWork();
    if (someCondition)
        resolve(someValue);      // works like a return
    else
        reject(someMessage);     // works like a throw
});

// ── CONSUMING IT ───────────────────────────────────────────────
promiseObj
    .then(someValue => {
        // success — the promise was achieved
    })
    .catch(someMessage => {
        // the promise was not satisfied
    });

// ── WRAPPING A CALLBACK API IN A PROMISE (slide 23) ────────────
const { readFile } = require('fs');

const read = (path) => {
    return new Promise((resolve, reject) => {
        readFile(path, 'utf8', (err, data) => {
            if (err) reject(err);
            else     resolve(data);
        });
    });
};

read('./content/first.txt')
    .then(data => console.log(data))
    .catch(err  => console.log(err));

// ── CHAINING beats nesting — the whole payoff ──────────────────
read('./content/first.txt')
    .then(first  => read('./content/second.txt'))
    .then(second => console.log(second))
    .catch(err   => console.log(err));      // ONE catch for the whole chain

fetch — asynchronous data requests

A web API is simply a web resource that returns data instead of HTML, CSS, JavaScript or images. fetch is done from the client side to get that data asynchronously, and it replaces the older AJAX technology.

fetch.jsSlides 25–28
let cities = fetch('/api/cities?country=italy');

// What does `cities` contain? NOT the JSON data. It takes time for the
// service to execute and respond, so fetch returns a PROMISE object.

fetch('/api/cities?country=italy')
    .then(response => {
        console.warn('response received!!!');
        return response.json();      // ← ALSO returns a promise
    })
    .then(data => {
        console.log(data);              // finally the actual data
    })
    .catch(err => console.log(err));

// COMMON MISTAKES (slide 28):
//   1. expecting fetch to return the data directly
//   2. forgetting that response.json() is ALSO asynchronous
//   3. nesting multiple fetches inside each other — callback hell again.
//      CHAIN them with .then, or use async/await:

async function load() {
    try {
        const response = await fetch('/api/cities?country=italy');
        const data     = await response.json();
        console.log(data);
    } catch (err) {
        console.log(err);
    }
}
The single sentence to remember about fetch: it returns a Promise, not data — and response.json() returns another one. Two awaits, or two .then()s. Nearly every broken fetch in a lab is one of those two missing.

CORS

corsSlide 30
// Modern browsers PREVENT cross-origin requests by default, which makes
// legitimate sharing between two domains harder.
//
// An ORIGIN is a protocol + domain + port. All three must match.
//   https://a.com      vs  http://a.com       ← different (protocol)
//   https://a.com      vs  https://b.com      ← different (domain)
//   http://a.com:3000  vs  http://a.com:5000  ← different (port)

// An API that wants to allow ANY domain adds this header to responses:
//   Access-Control-Allow-Origin: *

// In Node:
// > npm install cors
const cors = require('cors');
app.use(cors());   // allows the API to be accessed through JavaScript

Packages, routing, middleware and parameters

npm and package.json

terminal + package.jsonSlides 29, 32–33
# npm = Node Package Manager
npm init -y                # creates a basic package.json (-y skips the questionnaire)
npm install express        # installs into node_modules AND adds it to dependencies
npm install nodemon -D     # a DEV dependency — needed at development time only
npm install -g nodemon     # or globally
npm install                # restores node_modules from package.json
npm run start              # runs the "start" script
npm run dev                # runs the "dev" script — nodemon restarts on save

// package.json
{
  "name": "05-npm-demo",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "start": "node app.js",
    "dev":   "nodemon app.js"
  },
  "dependencies":    { "express": "^4.18.2" },
  "devDependencies": { "nodemon": "^2.0.20" }
}

# .gitignore — node_modules is NEVER shared. Teammates recreate it
# from package.json with `npm install`.
/node-modules

Semantic versioning — MAJOR.MINOR.PATCH

PartIncrements when…Breaks your code?
MAJORYou make incompatible API changes.Yes — will not work with earlier versions.
MINORYou add functionality in a backwards-compatible way.No.
PATCHYou make backwards-compatible bug fixes.No.
~1.3.8Allows automatic update to the latest PATCH.No.
^1.3.8Allows automatic update to the latest MINOR.No.

Express: static files, routing and the 404

app.js — 08-express-serverSlides 34, 37
const express = require('express');
const app = express();

// A — the public folder becomes visible to HTTP requests, so
//     http://localhost:3000/css/styles.css works if css/ is inside public/
app.use( express.static('public') );

// B — a route is a URL: a series of folders, files, or parameter data
app.get('/', (request, response) => {
    response.sendFile('./pages/index.html');
});

// C
app.get('/about', (request, response) => {
    response.sendFile('./pages/about.html');
});

// D — ANY other request falls through to here. Order matters:
//     this must come LAST or it swallows everything.
app.use( (request, response) => {
    response.status(404).sendFile('./pages/404.html');
});

app.listen(3000, () => { console.log('listening on 3000'); });

// A request tries A, then B, then C, then D — the FIRST match wins.

Middleware — the chain of responsibility

Middleware is software that is a bridge between an application and the data, and Express arranges it as a chain of responsibility. The slides give the reason to use it: it splits server operations into smaller units, such as performing validation on the data, which leads to better app structure and reuse — and you can block the execution of the current chain and pass control to functions that handle errors.

middleware.jsSlides 35–36, 38
// app.use() INSTALLS a middleware function on the chain.

// At the ROOT of the route — runs for EVERY request
app.use( (req, res, next) => {
    console.log(`${req.method} ${req.path} - ${req.ip}`);
    next();          // ← WITHOUT next() the request HANGS FOREVER
});

// Mounted at a SPECIFIC route:  app.METHOD(path, middleware, callback)
app.get('/now',
    function (req, res, next) {
        req.time = new Date().toString();    // attach data to the request
        next();                                // hand on to the next function
    },
    function (req, res) {
        res.json({ time: req.time });          // the last one SENDS the response
    }
);

// Built-in and third-party middleware you have already met:
app.use(express.static('public'));                        // serve static files
app.use(cors());                                          // allow cross-origin
app.use(bodyParser.urlencoded({ extended: false }));     // parse form bodies
The bug that will cost you an hour: a middleware function that forgets next(). There is no error, no crash, no log — the browser just spins until it times out. If a request hangs, look for a missing next() before you look anywhere else.

Environment variables

.env + app.js — 09-env-variablesSlide 39
# .env — any number of key=value pairs
PORT=8080
BUILD=development

// > npm install dotenv
require('dotenv').config();          // load .env into process.env

console.log(process.env);            // see everything available
console.log("build type=" + process.env.BUILD);

app.listen(process.env.PORT);        // never hard-code the port again

// .env belongs in .gitignore too — it holds secrets.

A simple API, and splitting it into modules

app.js + data-module.js — 10-simple-apiSlides 40–42
// ── data-module.js — slide 42 ──────────────────────────────────
const fs   = require('fs');
const path = require('path');

const jsonPath = path.join(__dirname, 'data', 'SE2022.json');
//   __dirname = the folder THIS file is in. path.join builds a path
//   that works on Windows and Linux alike.

let curriculum;
fs.readFile(jsonPath, (err, data) => {
    if (err) console.log('Unable to read json data file');
    else     curriculum = JSON.parse(data);
});

const getData = () => { return curriculum; };
module.exports = getData;

// ── app.js ─────────────────────────────────────────────────────
const express = require('express');
require('dotenv').config();
const getData = require('./data-module');

const app = express();

app.get('/', (req, resp) => { resp.json(getData()); });   // res.json sends JSON

app.listen(process.env.PORT, () => {
    console.log("Listening at port… " + process.env.PORT);
});

// WHY: with five or six routes a single Node file becomes too complex.
// Separate the routing, and separate the handler logic, into modules.

Three ways to receive parameters — know which object each lands in

WayURL / sourceRead it fromNeeds
Route params/echo/helloreq.params.wordThe :param syntax in the route.
Query params/employee?first=skander&last=turkireq.query.firstNothing — no library needed.
Form bodyA POSTed <form>req.body.firstbody-parser middleware.
parameters.jsSlides 43–45
// ── 1. ROUTE PARAMS — the :param syntax (slide 43) ─────────────
app.get('/echo/:word', (req, res) => {
    let param = req.params.word;
    res.json({ echo: param });
});
// GET https://ip:port/echo/hello  →  'hello' lands in req.params.word

// ── 2. QUERY PARAMS — URL-encoded queries (slide 44) ───────────
app.get('/employee', (req, res) => {
    res.json({ employee: `${req.query.first} ${req.query.last}` });
});
app.post('/employee', (req, res) => {
    res.json({ employee: `${req.query.first} ${req.query.last}` });
});
// http://ip:port/employee?first=skander&last=turki

// ── 3. FORM BODY (slide 45) ────────────────────────────────────
//   <form action="/name" method="post">
//     <label>First Name :</label><input type="text" name="first"><br>
//     <label>Last Name :</label><input type="text" name="last"><br>
//     <input type="submit" value="Submit">
//   </form>

// A payload (POST, DELETE…) needs middleware to be parsed:
const bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({ extended: false }));

app.post('/name', (req, res) => {
    let p_first = req.body.first;      // ← req.BODY, not query, not params
    res.send(`Hello ${p_first}`);
});

// Modern Express has this built in: app.use(express.urlencoded({extended:false}))

View engines: generating HTML on the server

A view engine lets Node generate HTML from data. Install the package, tell Express which engine to use and which folder holds the views, and render instead of sending files.

setting up EJSSlides 46–47
// > npm install ejs

// register the view engine
app.set('view engine', 'ejs');

// templates go in /views by DEFAULT — configure only if you want another
app.set('views', 'otherThanViewsFolder');

// response.render() sends the template — 'index' means views/index.ejs
app.get('/', (request, response) => {
    response.render('index');
});
app.js — injecting dataSlide 48
const express = require('express');
const app = express();

app.set('view engine', 'ejs');
app.use(express.static('public'));

app.get('/', (req, res) => {
    const blogs = [
        { title: 'First post',  snippet: '…' },
        { title: 'Second post', snippet: '…' },
    ];
    // SECOND ARGUMENT = the data sent to the template
    res.render('index', { title: 'Home', blogs: blogs });
});

app.get('/about',  (req, res) => res.render('about',  { title: 'About' }));
app.get('/create', (req, res) => res.render('create', { title: 'Create a new blog' }));

// catch-all 404, rendered rather than sent as a file
app.use((req, res) => {
    res.status(404).render('404', { title: '404' });
});

app.listen(3000);

The two EJS tag forms — and partials

views/index.ejsSlides 49–50
<html lang="en">
<%- include("./partials/head.ejs") %>

<body>
  <%- include("./partials/nav.ejs") %>

  <div class="blogs content">
    <h2><%= title %>: All Blogs</h2>

    <% if (blogs.length > 0) { %>
      <% blogs.forEach(blog => { %>
        <h3 class="title"><%= blog.title %></h3>
        <p class="snippet"><%= blog.snippet %></p>
      <% }) %>
    <% } else { %>
      <p>There are no blogs to display...</p>
    <% } %>
  </div>

  <%- include("./partials/footer.ejs") %>
</body>
</html>

<!-- THE THREE TAGS:
     <%  %>   runs JavaScript, outputs NOTHING       (if, forEach, loops)
     <%= %>   outputs the VALUE of an expression      (escaped — safe)
     <%- %>   outputs UNESCAPED — used for include()

     PARTIALS let one .ejs file be included in another so shared parts
     are reused. Organise them under views/partials/ :
        /views/partials/head.ejs
        /views/partials/header.ejs
        /views/partials/footer.ejs                                 -->
The EJS tag you will get wrong once: <% %> versus <%= %>. Anything that controls flow uses the plain form; anything that appears on the page needs the equals sign. Writing <% title %> renders nothing at all, silently.

The eight things people get wrong

Middleware with no next()

The request hangs. No error, no stack trace, no log line — the browser spins until it times out. The chain of responsibility simply stopped.

Fix: Every middleware function either calls next() or sends a response. Exactly one of the two, never neither.

The 404 handler placed too early

app.use((req,res) => res.status(404)...) written before your routes catches every request, and your real routes never run.

Fix: Order is everything in Express: static, then specific routes, then the catch-all last. The first match wins.

Expecting fetch to return data

let cities = fetch(url) gives you a Promise object, not the cities. Logging it shows Promise { <pending> } and people conclude the API is broken.

Fix: Chain .then(r => r.json()).then(data => ...), or await twice. Slide 26 asks this exact question.

Reading a form body from req.query

A POSTed form puts its data in the request body, not the URL. req.query.first is undefined and so is req.body.first until the parser is installed.

Fix: Three sources, three objects: req.params for :route params, req.query for ?query= strings, req.body for form payloads — and the last one needs body-parser or express.urlencoded.

Committing node_modules

Thousands of files in the repository, huge diffs, and merge conflicts in code nobody wrote. Slide 32 is explicit that the folder does not have to be shared.

Fix: Add /node_modules to .gitignore. Teammates run npm install and get the same tree from package.json.

Hard-coding the port

app.listen(3000) works on your laptop and fails wherever the platform assigns a port. It also means every teammate edits the same line.

Fix: require('dotenv').config() and app.listen(process.env.PORT). Put .env in .gitignore as well — it holds secrets.

Blaming the API for a CORS error

The request works in Postman and in the address bar but fails from your page. That is the browser enforcing the same-origin policy, not the API refusing you.

Fix: The server must send Access-Control-Allow-Origin. In your own Node app: npm install cors, then app.use(cors()). Remember an origin is protocol + domain + port — a different port is a different origin.

<% %> where <%= %> was needed

<h2><% title %></h2> renders an empty heading. The tag evaluated the expression and threw the result away.

Fix: <% %> runs code. <%= %> prints a value. <%- %> prints unescaped, which is what include() needs.

Chapter 6 on a single screen

Modules

  • module.exports = { a, b }
  • module.exports = fn
  • const x = require('./file')
  • Not exported = not visible
  • Client side: <script type="module">

Core modules

  • http — create a server
  • url — parse URLs
  • querystring — query strings
  • path — file paths
  • fs — file I/O
  • util — utilities

Terminal

  • node app · Ctrl-C
  • npm init -y
  • npm install express
  • npm install nodemon -D
  • npm install — restore modules
  • npm run dev

Async

  • Node: non-blocking, async, single-threaded
  • Callback: (err, result) => {}
  • new Promise((resolve, reject) => {})
  • .then() · .catch()
  • await inside async function
  • fetch returns a Promise, not data

Express

  • app.use(express.static('public'))
  • app.get(path, (req, res) => {})
  • app.post app.put app.delete
  • res.send res.json res.sendFile res.render
  • res.status(404)
  • app.listen(process.env.PORT)

Middleware

  • Chain of responsibility
  • app.use((req,res,next) => { next(); })
  • Always next() or send a response
  • Order matters — 404 handler goes LAST
  • cors() · bodyParser.urlencoded()

Parameters

  • /echo/:wordreq.params.word
  • ?first=xreq.query.first
  • POSTed form → req.body.first
  • Bodies need a parser middleware

EJS

  • app.set('view engine', 'ejs')
  • Templates live in /views
  • res.render('index', { title })
  • <% %> run · <%= %> print
  • <%- include("./partials/head.ejs") %>

SemVer

  • MAJOR.MINOR.PATCH
  • MAJOR — breaking changes
  • MINOR — new features, compatible
  • PATCH — bug fixes, compatible
  • ~1.3.8 latest patch · ^1.3.8 latest minor

Build one app, one slide at a time

This chapter cannot be revised from paper — every idea in it is something a server either does or fails to do. Work through the list as one growing project rather than fifteen throwaway files, and by the end you will have the skeleton chapter 7 needs.

  1. Do the slide 1 leftover task first: show a table when a heading is clicked, using DOM manipulation methods.
  2. Write two modules and an app that imports both. Deliberately leave one constant unexported and confirm it reads as undefined.
  3. Read two files with readFileSync and append the result to a third with { flag: 'a' }.
  4. Write the simplest HTTP server from memory in nine lines, then visit a nonsense URL and confirm it gives the same response.
  5. Add hand-written routing with req.url, including a 404 branch. Then imagine twenty routes and decide you want Express.
  6. Rewrite the same two-file read asynchronously and log the output order — predict it before running.
  7. Nest a second readFile inside the first callback so it can see the first result. That is callback hell; look at the indentation.
  8. Wrap readFile in a Promise and chain two reads with .then. Compare the shape to the previous drill.
  9. Fetch from a public API, log the raw fetch return value, then fix it with two .thens, then rewrite with async/await.
  10. Run npm init -y, install express and nodemon (one as a dev dependency), and read the resulting package.json line by line.
  11. Add start and dev scripts, run npm run dev, and edit a file while it is running.
  12. Build the four-handler Express app: static, two routes, catch-all 404. Then move the 404 to the top and watch everything break.
  13. Add a logging middleware that prints method, path and IP. Then delete its next() and watch the request hang.
  14. Move the port into a .env file and read it with dotenv.
  15. Write three endpoints that take a parameter three different ways — route, query, and a POSTed form — and note which req object each uses.
  16. Split the routes and the data loading into separate modules, as slides 41–42 describe.
  17. Convert the pages to EJS templates, pass real data into one, and factor the header and footer into partials.

There is no chapter-6 example folder in your study material, but two solved labs cover exactly this ground: lab 09 — Express and EJS and lab 08. Slide 51 and chapter 3 slide 85 both point to the full code repository at github.com/skanderturki/se371.

Can you answer these without scrolling up?

Question 1