ECE1724: Advanced Web Development

React Ecosystem and Modern Frameworks

Chen Ying

Assistant Professor, Teaching Stream

Department of Electrical and Computer Engineering

University of Toronto

Last Week’s Lecture

Fundamental Web Concepts

  • HTML defines structure
  • CSS defines appearance
  • JavaScript defines behavior and interactivity

How the Web Actually Runs

  • Browser parses HTML into DOM
  • JavaScript interacts with DOM, not raw HTML
  • JavaScript execution is single-threaded, event-driven, and asynchronous

Last Week’s Lecture

Asynchronous JavaScript

  • Callbacks → Promises → async/await

async/await: Core Idea

async / await lets you write Promise-based code that reads like synchronous code

async function myAsyncFunction() {
  await wait(1000);
  console.log("Step 1: Task 1 completed after 1 second");
}

An async function always returns a Promise

await pauses this function, and returns control to the event loop

  • Other events/callbacks can run, and this function resumes later

Important Clarification

async does not make code asynchronous

async function f() {
  return 42;
}
  • f() returns a Promise (Promise<number>), which fulfills with 42

Asynchronous behavior comes from timers, network requests, browser APIs, etc.

async keyword does only one thing: Wraps the return value in a Promise

Refactor with async/await

function wait(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function runTasks() {
  try {
    await wait(1000);
    console.log("Step 1: Task 1 completed after 1 second");

    await wait(2000);
    console.log("Step 2: Task 2 completed after 2 seconds");

    await wait(3000);
    console.log("Step 3: Task 3 completed after 3 seconds");
  } catch (error) {
    console.error("An error occurred:", error);
  }
}

runTasks();

async decides what the function returns

await decides when the function continues

Why function wait(ms)?

function wait(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

Shouldn’t it be

async function wait(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

function wait(ms) is Correct

function wait(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

This function explicitly returns a Promise

  • No async keyword is required
async function f() {
  return 42;
}
  • f() returns a Promise (Promise<number>)

Rule of Using await

await can only be used inside an async function

function wait(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

Legal JavaScript?

function wait(ms) {
  await new Promise(resolve => setTimeout(resolve, ms));
}

SyntaxError: await is only valid in async functions

Rule of Using await

await can only be used inside an async function

async function wait(ms) {
  await new Promise(resolve => setTimeout(resolve, ms));
}

await requires async

async guarantees a Promise

function wait(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

One Modern Exception

At the top level of modules, await is allowed without async

function wait(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

await wait(1000);
console.log("Done");

Refactor with async/await

function wait(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function runTasks() {
  try {
    await wait(1000);
    console.log("Step 1: Task 1 completed after 1 second");

    await wait(2000);
    console.log("Step 2: Task 2 completed after 2 seconds");

    await wait(3000);
    console.log("Step 3: Task 3 completed after 3 seconds");
  } catch (error) {
    console.error("An error occurred:", error);
  }
}

runTasks();

Cleaner than .then() .catch()

Important Clarification

async decides what the function returns

async function myAsyncFunction() {
  await wait(1000);
  console.log("Step 1: Task 1 completed after 1 second");
}

await decides when the function continues

  • await pauses this function, and returns control to the event loop

Remove await

function wait(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function runTasks() {
  try {
    wait(1000);
    console.log("Step 1");

    wait(2000);
    console.log("Step 2");

    wait(3000);
    console.log("Step 3");
  } catch (error) {
    console.error(error);
  }
}

runTasks();

async/await Does Not

  • It does not make JavaScript multi-threaded
  • It does not block the browser
  • It does not bypass the event loop

It is syntax sugar on top of Promises

  • Only changes how we write Promise-based code

Recap: Asynchrnous Programming in JavaScript

  • Callbacks lead to complex nested structures
  • Promises allow for cleaner, chained handling of asynchronous events
  • async/await allows functions to be written as if they were synchronous
    • This is the preferred style in modern web development

Today’s Lecture

Backend Development Fundamentals


Goal of today: Build the foundation needed to complete Assignment 1

Assignment 1

Build the backend foundation of the Paper Management System

  • Store and retrieve data using SQLite
  • Implement a RESTful API using Express
    • 5 endpoints to create/retrieve/update/delete paper(s)

Assignment 1: Demo

Create a new paper

curl -X POST http://localhost:3000/api/papers \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Example Paper Title",
    "authors": "John Doe, Jane Smith",
    "published_in": "ICSE 2025",
    "year": 2025
  }'
  • cURL (Client URL): A command-line tool and library used for transferring data with URLs

Assignment 1: Demo

Validate input

curl -X POST http://localhost:3000/api/papers \
  -H "Content-Type: application/json" \
  -d '{
    "authors": "John Doe, Jane Smith",
    "published_in": "ICSE 2025",
    "year": 2025
  }'

Assignment 1: Demo

Retrieve all papers

curl http://localhost:3000/api/papers

Retrieve a specific paper by its ID

curl http://localhost:3000/api/papers/1

Error handling

curl http://localhost:3000/api/papers/99

How Web Works

Step 1: Client sends HTTP request

How Web Works

Step 2: Server processes request (may involve database)

How Web Works

Step 3: Server returns HTTP response

Backend Development

Backend development is responsible for

  • Receiving and processing user requests
  • Data storage and retrieval
  • Ensuring security and authentication
  • Returning structured responses

Backend Development Basics

Hypertext Transfer Protocol (HTTP): A communication protocol for transmitting data between a client and server

HTTP Request-Response Cycle

The interaction between a client and a server to exchange data over HTTP

  1. Client makes an HTTP request
  2. Server processes the request
  3. Server sends an HTTP response
  4. Client processes the response

HTTP Request: Components

  • URL: Resource being requested

URL

A URL (Uniform Resource Locator) is the address used to locate a resource on the Internet

https://www.eecg.utoronto.ca/~cying/courses/ece1724-web/

HTTP Request: Components

URL: Resource being requested

HTTP Method: Action the client wants the server to perform

HTTP Method

Describe the action the client is asking the server to perform

Common HTTP Methods

  • GET: Retrieve data from the server
  • POST: Send data to the server to create a new resource
  • PUT: Update a resource on the server
  • PATCH: Partially update a resource on the server
  • DELETE: Remove a resource from the server

HTTP Request: Components

URL: Resource being requested

HTTP Method: Action the client wants the server to perform

Header: Metadata about the request (e.g. Content-Type, authentication tokens)

Body (optional): Data

curl -X POST http://localhost:3000/api/papers \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Example Paper Title",
    "authors": "John Doe, Jane Smith",
    "published_in": "ICSE 2025",
    "year": 2025
  }'

HTTP Response

Key conponents in a response:

  • Status Code: A three-digit number indicating the result of the client’s request

Common Status Codes

  • 200 OK: Success
  • 201 Created: Resource created
  • 400 Bad Request: Invalid input
  • 404 Not Found: Resource does not exist
  • 500 Internal Server Error: Server bug

HTTP Response

Key conponents in a response:

  • Status Code: 200 (success), 404 (not found), 500 (server error)
  • Header: Metadata about the response
  • Body: Data returned to the client (requested data, error message, or other information)

Recap: HTTP

A communication protocol for transmitting data between a client and server

HTTP Request-Response Cycle

  • HTTP Request: URL, HTTP method (e.g., GET, POST), header, body (optional)
  • HTTP Response: Status code (e.g., 200, 404), header, body

Create a Basic Web Server

Install Node.js

  • A JavaScript runtime allows JavaScript to run outside the browser

A Basic Web Server

Built with Node.js’ built-in http module

  • A web server listens for HTTP requests
  • For each request, Node.js calls a handler function
  • The handler decides status code & response body
server.js
const http = require("http");

function handleRequest() {
  // 1. Set HTTP status code
  // 2. Send response body
}

const server = http.createServer(handleRequest);

A Basic Web Server

server.js
const http = require("http");

function handleRequest(req, res) {
  res.statusCode = 200;
  res.end("<h1>My First Web Server</h1>");
}

const server = http.createServer(handleRequest);

// Start listening on port 3000
server.listen(3000);

Run

node server.js

Go to your browser and enter

localhost:3000

Support Different Paths

Different URLs → Different responses

server.js
const http = require("http");

function handleRequest(req, res) {
  if (req.url === "/") {
    res.statusCode = 200;
    res.end("<h1>My First Web Server</h1>");
  } else if (req.url === "/currenttime") {
    res.statusCode = 200;
    res.end(`<h1>${new Date().toISOString()}</h1>`);
  } else {
    res.statusCode = 404;
    res.end("<h1>Not Found</h1>");
  }
}

const server = http.createServer(handleRequest);

server.listen(3000);

Route

Define how a server responds to a specific request

In practice, a route is defined by

  • HTTP Method (e.g. GET, POST)
  • Path (e.g. /, /currenttime)
  • Response logic

In the example, we route by checking req.url

How Many Routes?

server.js
const http = require("http");

function handleRequest(req, res) {
  if (req.url === "/") {
    res.statusCode = 200;
    res.end("<h1>My First Web Server</h1>");
  } else if (req.url === "/currenttime") {
    res.statusCode = 200;
    res.end(`<h1>${new Date().toISOString()}</h1>`);
  } else {
    res.statusCode = 404;
    res.end("<h1>Not Found</h1>");
  }
}

const server = http.createServer(handleRequest);

server.listen(3000);
  • Three GET routes

Limitations of Raw HTTP

  • Hard to manage many routes
  • No easy way to handle methods other than GET
  • No built-in JSON handling
    • JSON (JavaScript Object Notation): A lightweight, easily readable format for data exchange
      • Easy to parse and work with in both client-side and server-side code
      • Most modern apps expect data in JSON format

We need a framework

Express

A lightweight and flexible Node.js framework for building web applications

  • Specifically designed for backend/server-side development
  • Simplifies the creation of web servers and the handling of requests and responses
  • Supports middleware
  • Widely used in industry

Install Express

npm init -y
npm install express

First Express Server

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

app.get("/", (req, res) => {
  res.send("<h1>My First Express Server</h1>");
});

app.listen(3000);
const http = require("http");

function handleRequest(req, res) {
  if (req.url === "/"){
    res.statusCode = 200;
    res.end("<h1>My First Web Server</h1>");
  }
}

const server = http.createServer(handleRequest);
server.listen(3000);

Add More Routes

express-app/server.js
const express = require("express");
const app = express();

app.get("/", (req, res) => {
  res.send("<h1>My First Express Server</h1>");
});

app.get("/currenttime", (req, res) => {
  res.send(`<h1>${new Date().toISOString()}</h1>`);
});

app.use((req, res) => {
  res.status(404).send("<h1>Not Found</h1>");
});

app.listen("3000");

app.get() defines route handler

app.use() acts as a fallback when no route matches

app.use()

app.use((req, res) => {
  res.status(404).send("<h1>Not Found</h1>");
});

app.use() registers a middleware function

  • Express treats function passed to app.use() as a middleware
(req, res) => {
  res.status(404).send("<h1>Not Found</h1>");
}

Middleware

A function that sits between the request and the response

  • Run code
  • Inspect or modify the request
  • Modify the response
  • Pass control forward
  • Or end the request immediately

Function Signature

Middleware functions have the signature

function (req, res, next) {
  // do something
  next(); // Or send a response
}
  • req: Request object, representing incoming HTTP request
  • res: Response object, used to send a response back
  • next: A function that passes control to the next middleware or route handler

Example: Multiple Middlewares in a Chain

Express executes middleware top to bottom

app.use((req, res, next) => {
  console.log("Middleware 1");
  next(); // Passes control to Middleware 2
});

app.use((req, res, next) => {
  console.log("Middleware 2");
  next(); // Passes control to the route handler
});

app.get("/", (req, res) => {
  res.send("<h1>Home Page</h1>");
});

Middleware 1 → Middleware 2 → Home Page response

Example: 404 Middleware

Respond with a 404 error when no route matches the request, ending the request-response cycle

app.use((req, res) => {
  res.status(404).send("<h1>Not Found</h1>");
});
  • No next() → Express stops here

Error-Handling Middleware

A special type of middleware for handling errors

app.use((err, req, res, next) => {
  console.error(err);
  res.status(500).send("<h1>Something went wrong!</h1>");
});
  • Four parameters (starts with err)
  • Only runs when an error is passed with next(err)
  • Typically placed after all routes

Why Middleware Exists

Middleware lets us separate concerns:

  • Logging

  • Parsing data

    app.use(express.json());
  • Authentication

  • Validation

  • Error handling

Instead of putting everything inside route handlers

Example: Logging Middleware

Add a logging middleware to print request details

app.use((req, res, next) => {
  console.log(`Received ${req.method} request for ${req.url}`);
  next();
});

nodemon

nodemon automatically restarts your server when files change

Install nodemon

npm install --save-dev nodemon
  • --save-dev marks this package as a so-called development dependency
    • Tells Node.js that it is not a package used for code, but a package only used during development

Use nodemon

Update package.json

  "scripts": {
    "start": "nodemon server.js"
  },

Run your server with the command

npm start

Recap: Middlewares

Functions that run between request and response

  • Have access to request (req), response (res), and next middleware (next)
  • Execute in order
  • Can modify request and response objects
  • Can pass control forward
  • Can end request-response cycle

API

Application Programming Interface

  • A contract that defines how two software systems communicate with each other

  • It specifies

    • What requests are allowed
    • How to make those requests
    • What data is returned

What an API Does

An API lets one program

  • Request data or actions from another program
  • Without knowing how the other program is implemented

Two programs are decoupled

Key Components of an API

  • Endpoints: URLs that identify what you want to access
  • Methods: HTTP methods that define what action to perform
  • Requests and Responses

API: Example

One API with two endpoints

app.get("/", function (req, res) {
  res.send("<h1>My First Web Server</h1>");
});

app.get("/currenttime", function (req, res) {
  res.send(`<h1>${new Date().toISOString()}</h1>`);
});
  • They expose endpoints
  • They accept HTTP requests
  • They return responses

They just return HTML, not structured data

RESTful API

Representational State Transfer (REST): An architectural style for designing APIs on top of HTTP

  • A set of design principles

Core Principles of REST

  • Stateless Communication: Server does not remember client state
    • Every request contains all required information
  • Resources Identified by URLs: Each endpoint represents a resource
  • Standard HTTP Methods: Methods describe actions on resources

Standard HTTP Methods (CRUD)

  • POST: Create data
  • GET: Read data
  • PUT: Update data
  • DELETE: Delete data

RESTful API Example

RESTful API for managing representative papers

  • POST /api/papers: Add a new paper
  • GET /api/papers: Retrieve all papers
  • GET /api/papers/123: Retrieve paper with ID 123
  • PUT /api/papers/123: Update paper with ID 123
  • DELETE /api/papers/123: Remove paper with ID 123

RESTful API Example

app.post("/api/papers", (req, res) => {
  // Create new paper
});

app.get("/api/papers", (req, res) => {
  // Get all papers
});

app.get("/api/papers/:id", (req, res) => {
  // Get specific paper
});

app.put("/api/papers/:id", (req, res) => {
  // Update specific paper
});

app.delete("/api/papers/:id", (req, res) => {
  // Delete specific paper
});
  • :id is a route parameter
    • Express extracts it from the URL for you

Better RESTful API Design

app.get("/currenttime", function (req, res) {
  res.send(`<h1>${new Date().toISOString()}</h1>`);
});

Change the response to JSON format

app.get("/currenttime", (req, res) => {
  res.json({ currentTime: new Date().toISOString() });
});
  • res.json(): Automatically converts JavaScript objects to JSON

RESTful API: Example

Build a tiny Users API with two endpoints

  • GET /api/users: Return the full list of users (JSON)
  • POST /api/users: Add a new user and return the created user (JSON)
    • Request body { "name": "Charlie" }

Live Coding

express-example/server.js
const express = require("express");
const app = express();

app.use(express.json());

let users = [
  { id: 1, name: "Alice" },
  { id: 2, name: "Bob" },
];

app.get("/api/users", (req, res) => {
  // Return all users as JSON
});

app.post("/api/users", (req, res) => {
  // 1. Read name from request body
  // 2. Minimal validation (name must exist)
  // 3. Create a new user
  // 4. Return the new user with status 201 (Created)
});

app.listen(3000, () => {
  console.log("Server running on http://localhost:3000");
});

RESTful API: Example

express-example/server.js
const express = require("express");
const app = express();

app.use(express.json());

const users = [
  { id: 1, name: "Alice" },
  { id: 2, name: "Bob" },
];

app.get("/api/users", (req, res) => {
  res.json(users);
});

app.post("/api/users", (req, res) => {
  const name = req.body.name;
  if (!name) {
    return res.status(400).json({ error: "name is required" });
  }

  const newUser = {
    id: users.length + 1,
    name,
  };

  users.push(newUser);

  res.status(201).json(newUser);
});

app.listen(3000, () => {
  console.log("Server running on http://localhost:3000");
});

Live Demo

curl localhost:3000/api/users


curl -X POST http://localhost:3000/api/users \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Charlie"
  }'

Quick Recap

  • HTTP: Communication protocol
  • Express: Web framework for handling requests & responses
  • Middleware: A function that sits between the request and the response
  • RESTful API
    • Resources identified by URLs
    • Standard HTTP methods (CRUD)
    • Stateless, predictable, scalable design

In-Memory Data

So far, our API stores data in memory

const users = [
  { id: 1, name: "Alice" },
  { id: 2, name: "Bob" },
];

What happens if the server restarts?

  • Data is lost

We need persistent storage

Solution: Use a Database

A database allows us to

  • Store data on disk
  • Keep data across server restarts
  • Query data efficiently

SQLite

A lightweight, file-based relational database

  • Database is stored as a single .db file on disk
  • Runs inside your application
    • No separate database server
  • Suitable for prototyping and small-scale applications

Assignment 1 uses SQLite

Replace In-Memory Array

Before:

const users = [
  { id: 1, name: "Alice" },
  { id: 2, name: "Bob" },
];

After using SQLite:

  • Data lives in a database file
  • Routes call database functions
  • Database returns rows

Install SQLite

npm install sqlite3

Create a Database

const express = require("express");
const sqlite3 = require("sqlite3").verbose();

const app = express();

app.use(express.json());

const db = new sqlite3.Database("demo_db.db", (err) => {
  if (err) {
    console.error("Error connecting to database:", err);
  } else {
    console.log("Connected to SQLite database");
  }
});

Key Methods

  • db.run(): Execute commands (e.g., CREATE, INSERT)
  • db.get(): Fetch one row
  • db.all(): Fetch all rows

Create a Table

db.run(
  `
    CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL
    )
`,
  (err) => {
    if (err) {
      console.error("Error creating table:", err);
    } else {
      console.log("users table ready");
    }
  }
);

SQL Queries

  • Insert: INSERT INTO table_name (column) VALUES (value)
  • Select all: SELECT * FROM table_name
  • Select one: SELECT * FROM table_name WHERE condition
  • Update: UPDATE table_name SET column1 = value1 WHERE condition
  • Delete: DELETE FROM table_name WHERE condition

POST /api/users

app.post("/api/users", (req, res) => {
  const name = req.body.name;
  db.run("INSERT INTO users (name) VALUES (?)", [name], function (err) {
    if (err) {
      res.status(400).json({ error: err.message });
      return;
    }
    res.status(201).json({
      id: this.lastID,
      name: name,
    });
  });
});

Think of this as a object SQLite hands to the callback

  • lastID: ID of the last inserted row

POST /api/users

app.post("/api/users", (req, res) => {
  const name = req.body.name;
  db.run("INSERT INTO users (name) VALUES (?)", [name], function (err) {
    if (err) {
      res.status(400).json({ error: err.message });
      return;
    }
    res.status(201).json({
      id: this.lastID,
      name: name,
    });
  });
});

Cannot use (err) => {} instead of function (err) {}

  • Regular functions receive this; Arrow functions don’t

Potential Problems

app.post("/api/users", (req, res) => {
  const name = req.body.name;
  db.run("INSERT INTO users (name) VALUES (?)", [name], function (err) {});
});
  • Database logic inside route handler
    • As the app grows, it will become hard to read, test, reuse
  • Database operations should be asynchronous
    • Even though in Node.js, SQL queries do not have to be asynchronous

DB Operations Should Be Asynchronous

Database calls

  • Perform disk I/O
  • Can take time
  • Must not block the event loop

Node.js handles many requests concurrently

db.run("INSERT INTO users (name) VALUES (?)", [name], function (err) {});

Blocking is bad

Use async / await

Wrap database logic in a Promise

const addUser = async (name) => {
  return await new Promise((resolve, reject) => {
    db.run("INSERT INTO users (name) VALUES (?)", [name], function (err) {
      if (err) reject(err);
      else resolve({ id: this.lastID, name });
    });
  });
};

Equivalent (semantically) to

async function addUser(name) {
  ...
}

Cleaner Route

app.post("/api/users", async (req, res) => {
  try {
    const user = await addUser(req.body.name);
    res.status(201).json(user);
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

Live Demo

server.js
const express = require("express");
const sqlite3 = require("sqlite3").verbose();

const app = express();

app.use(express.json());

const db = new sqlite3.Database("demo_db.db", (err) => {
  if (err) {
    console.error("Error connecting to database:", err);
  } else {
    console.log("Connected to SQLite database");
  }
});

db.run(
  `
    CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL
    )
`,
  (err) => {
    if (err) {
      console.error("Error creating table:", err);
    } else {
      console.log("users table ready");
    }
  }
);

const addUser = async (name) => {
  return await new Promise((resolve, reject) => {
    db.run("INSERT INTO users (name) VALUES (?)", [name], function (err) {
      if (err) reject(err);
      else resolve({ id: this.lastID, name });
    });
  });
};

app.post("/api/users", async (req, res) => {
  try {
    const user = await addUser(req.body.name);
    res.status(201).json(user);
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

app.listen(3000, () => {
  console.log("Server running on http://localhost:3000");
});
curl -X POST http://localhost:3000/api/users \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Charlie"
  }'

Everything Is in server.js

As the app grows

  • Routes
  • Database code
  • Validation
  • Middleware

We need separation of concerns

Modular Backend Structure

sqlite-example/
├── src/
│   ├── server.js        # Express application setup
│   ├── routes.js        # API routes
│   ├── database.js      # Database operations
│   └── middleware.js    # Custom middleware
└── package.json

Each file has one responsibility

server.js

Responsibilities:

  • Create Express app
  • Register middleware
  • Mount routes
  • Start server

server.js

sqlite-example/src/server.js
const express = require("express");
const routes = require("./routes");

const app = express();

app.use(express.json()); // Middleware to parse JSON

app.use("/api", routes); // Mount the routes

app.listen(3000, () => {
  console.log(`Server running on http://localhost:3000`);
});

routes.js

Responsibilities:

  • Define API endpoints
  • Validate input
  • Call database functions
  • Return responses

routes.js

sqlite-example/src/routes.js
const express = require("express");
const db = require("./database");

const router = express.Router();

// POST /api/users
router.post("/users", async (req, res) => {
  try {
    const { name } = req.body;
    const user = await db.addUser(name);
    res.status(201).json(user);
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

module.exports = router;

express.Router()

const express = require("express");
const router = express.Router();

Create a new router object

  • A mini Express application that can be used to define routes separately from the main application
  • Mounted in server.js

express.Router()

sqlite-example/src/server.js
const express = require("express");
const routes = require("./routes");

const app = express();

app.use(express.json()); // Middleware to parse JSON

app.use("/api", routes); // Mount the routes

app.listen(3000, () => {
  console.log(`Server running on http://localhost:3000`);
});

express.Router(): Benefits

  • Modularity: Group related routes into a single router and mount them under a specific path

    const routes = require("./routes");
    app.use("/api", routes);
  • Readability: The main application becomes cleaner, with just a few app.use() statements to mount routers

  • Reusability

routes.js

sqlite-example/src/routes.js
const express = require("express");
const db = require("./database");

const router = express.Router();

// POST /api/users
router.post("/users", async (req, res) => {
  try {
    const { name } = req.body;
    const user = await db.addUser(name);
    res.status(201).json(user);
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

module.exports = router;

express.Router(): Benefits

Modularity: Group related routes into a single router and mount them under a specific path

Readability: The main application becomes cleaner, with just a few app.use() statements to mount routers

Reusability

Middleware Usage: Can attach middleware

assignment-1/src/routes.js
// GET /api/papers/:id
router.get("/papers/:id", validateId, async (req, res, next) => {})

database.js

Responsibilities:

  • Connect to SQLite
  • Create tables
  • Run SQL queries
  • Return data as JavaScript objects

database.js

sqlite-example/src/database.js
const sqlite3 = require("sqlite3").verbose();

const db = new sqlite3.Database("./demo_db.db", (err) => {
  if (err) {
    console.error("Error connecting to database:", err);
  } else {
    console.log("Connected to SQLite database");
  }
});

db.run(
  `
    CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL
    )
`,
  (err) => {
    if (err) {
      console.error("Error creating table:", err);
    } else {
      console.log("Users table ready");
    }
  }
);

const dbOperations = {
  addUser: async (name) => {
    const user = await new Promise((resolve, reject) => {
      db.run("INSERT INTO users (name) VALUES (?)", [name], function (err) {
        if (err) reject(err);
        else resolve({ id: this.lastID, name });
      });
    });
    return user;
  },
};

module.exports = {
  db,
  ...dbOperations,
};

middleware.js

Responsibilities:

  • Logging
  • Validation
  • Error handling
sqlite-example/src/middleware.js
const requestLogger = (req, res, next) => {
  console.log(`${new Date().toISOString()} - ${req.method} ${req.path}`);
  next();
};

module.exports = { requestLogger };

Modular Backend Structure

sqlite-example/
├── src/
│   ├── server.js        # Express application setup
│   ├── routes.js        # API routes
│   ├── database.js      # Database operations
│   └── middleware.js    # Custom middleware
└── package.json

Live Demo

sqlite-example/src/server.js
const express = require("express");
const routes = require("./routes");
const middleware = require("./middleware");

const app = express();

// Middleware
app.use(express.json());
app.use(middleware.requestLogger);

// Routes
app.use("/api", routes);

app.listen(3000, () => {
  console.log(`Server running on http://localhost:3000`);
});

Assignment 1

assignment-1/
├── src/
│   ├── server.js        # Express application setup
│   ├── routes.js        # API routes
│   ├── database.js      # Database operations
│   └── middleware.js    # Custom middleware
└── package.json

Lecture Summary

Backend Development Fundamentals

  • HTTP: Communication protocol
  • Express: Web framework for handling requests & responses
  • Middleware: A function that sits between the request and the response
  • REST: An architectural style for designing APIs on top of HTTP
  • SQLite: A lightweight, file-based relational database