React Ecosystem and Modern Frameworks
Chen Ying
Assistant Professor, Teaching Stream
Department of Electrical and Computer Engineering
University of Toronto
Fundamental Web Concepts
How the Web Actually Runs
Asynchronous JavaScript
async/awaitasync/await: Core Ideaasync / await lets you write Promise-based code that reads like synchronous code
An async function always returns a Promise
await pauses this function, and returns control to the event loop
async does not make code asynchronous
Asynchronous behavior comes from timers, network requests, browser APIs, etc.
async keyword does only one thing: Wraps the return value in a Promise
async/awaitfunction 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
function wait(ms)?function wait(ms) is CorrectThis function explicitly returns a Promise
async keyword is requiredawaitawait can only be used inside an async function
SyntaxError: await is only valid in async functions
awaitawait can only be used inside an async function
await requires async
async guarantees a Promise
At the top level of modules, await is allowed without async
async/awaitfunction 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()
async decides what the function returns
await decides when the function continues
await pauses this function, and returns control to the event loopawaitasync/await Does NotIt is syntax sugar on top of Promises
async/await allows functions to be written as if they were synchronous
Backend Development Fundamentals
Goal of today: Build the foundation needed to complete Assignment 1
Build the backend foundation of the Paper Management System
Create a new paper
Validate input
Retrieve all papers
Step 1: Client sends HTTP request
Step 2: Server processes request (may involve database)
Step 3: Server returns HTTP response
Backend development is responsible for
Hypertext Transfer Protocol (HTTP): A communication protocol for transmitting data between a client and server
The interaction between a client and a server to exchange data over HTTP
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/
URL: Resource being requested
HTTP Method: Action the client wants the server to perform
Describe the action the client is asking the server to perform
Common HTTP Methods
GET: Retrieve data from the serverPOST: Send data to the server to create a new resourcePUT: Update a resource on the serverPATCH: Partially update a resource on the serverDELETE: Remove a resource from the serverURL: 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
Key conponents in a response:
Key conponents in a response:
200 (success), 404 (not found), 500 (server error)A communication protocol for transmitting data between a client and server
HTTP Request-Response Cycle
GET, POST), header, body (optional)200, 404), header, bodyInstall Node.js
Built with Node.js’ built-in http module
server.js
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);Define how a server responds to a specific request
In practice, a route is defined by
GET, POST)/, /currenttime)In the example, we route by checking req.url
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);GET routesGETWe need a framework
A lightweight and flexible Node.js framework for building web applications
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() registers a middleware function
app.use() as a middlewareA function that sits between the request and the response
Middleware functions have the signature
req: Request object, representing incoming HTTP requestres: Response object, used to send a response backnext: A function that passes control to the next middleware or route handlerExpress executes middleware top to bottom
Middleware 1 → Middleware 2 → Home Page response
Respond with a 404 error when no route matches the request, ending the request-response cycle
next() → Express stops hereA special type of middleware for handling errors
Middleware lets us separate concerns:
Logging
Parsing data
Authentication
Validation
Error handling
Instead of putting everything inside route handlers
Add a logging middleware to print request details
nodemonnodemon automatically restarts your server when files change
nodemonUpdate package.json
Functions that run between request and response
req), response (res), and next middleware (next)Application Programming Interface
A contract that defines how two software systems communicate with each other
It specifies
An API lets one program
Two programs are decoupled
One API with two endpoints
They just return HTML, not structured data
Representational State Transfer (REST): An architectural style for designing APIs on top of HTTP
POST: Create dataGET: Read dataPUT: Update dataDELETE: Delete dataRESTful API for managing representative papers
POST /api/papers: Add a new paperGET /api/papers: Retrieve all papersGET /api/papers/123: Retrieve paper with ID 123PUT /api/papers/123: Update paper with ID 123DELETE /api/papers/123: Remove paper with ID 123app.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
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)
{ "name": "Charlie" }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");
});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");
});So far, our API stores data in memory
What happens if the server restarts?
We need persistent storage
A database allows us to
A lightweight, file-based relational database
.db file on diskAssignment 1 uses SQLite
Before:
After using SQLite:
SQLiteconst 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(): Execute commands (e.g., CREATE, INSERT)db.get(): Fetch one rowdb.all(): Fetch all rowsINSERT INTO table_name (column) VALUES (value)SELECT * FROM table_nameSELECT * FROM table_name WHERE conditionUPDATE table_name SET column1 = value1 WHERE conditionDELETE FROM table_name WHERE conditionPOST /api/usersThink of this as a object SQLite hands to the callback
lastID: ID of the last inserted rowPOST /api/usersCannot use (err) => {} instead of function (err) {}
this; Arrow functions don’tDatabase calls
Node.js handles many requests concurrently
Blocking is bad
async / awaitWrap database logic in a Promise
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");
});server.jsAs the app grows
We need separation of concerns
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.jsResponsibilities:
server.jssqlite-example/src/server.js
routes.jsResponsibilities:
routes.jssqlite-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()Create a new router object
server.jsexpress.Router()sqlite-example/src/server.js
express.Router(): BenefitsModularity: 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
routes.jssqlite-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(): BenefitsModularity: 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
database.jsResponsibilities:
database.jssqlite-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,
};Responsibilities:
sqlite-example/
├── src/
│ ├── server.js # Express application setup
│ ├── routes.js # API routes
│ ├── database.js # Database operations
│ └── middleware.js # Custom middleware
└── package.json
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/
├── src/
│ ├── server.js # Express application setup
│ ├── routes.js # API routes
│ ├── database.js # Database operations
│ └── middleware.js # Custom middleware
└── package.json
Backend Development Fundamentals