Share
Explore

10-exercise lab workbook designed to reinforce key concepts in Node.js, JavaScript, and web development.

Here's a comprehensive 10-exercise lab workbook designed to reinforce key concepts in Node.js, JavaScript, and web development. Each exercise includes learning outcomes, detailed workflows, and fully specified code.

Lab Workbook: Advanced Web Development with Node.js and JavaScript

Learning Outcomes

By completing this lab workbook, students will be able to:
Set up and configure a Node.js project using npm
Create and manage Express.js servers with routes and middleware
Implement GET and POST requests in both client-side and server-side JavaScript
Manipulate the DOM using JavaScript
Work with asynchronous JavaScript using Promises and async/await
Implement form handling and data validation
Use the Fetch API for making HTTP requests
Apply CSS selectors and styling to web pages
Create and use JavaScript modules
Implement basic error handling in Node.js applications

Exercise 1: Setting Up a Node.js Project

Learning Outcomes

Initialize a Node.js project using npm
Understand the purpose of package.json
Install and use external dependencies

Workflow

Create a new directory for your project
Initialize a new Node.js project
Install Express.js as a dependency
Create a basic server file

Code

mkdir my-node-project
cd my-node-project
npm init -y
npm install express
Create a file named server.js:
const express = require('express');
const app = express();
const port = 3000;

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

app.get('/help', (req, res) => {
res.send('Welcome to the help desk!');
});


app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});
Run the server:
bash
Copy
node server.js


Exercise 2 demonstrates HTML forms that interact with the server endpoints.

Exercise 2: Creating Routes in Express.js with HTML Forms

Learning Outcomes

Define routes in Express.js
Handle different HTTP methods (GET, POST)
Use route parameters
Create HTML forms to interact with server endpoints
Serve static HTML files from Express.js

Workflow

Extend the previous server file
Add routes for different HTTP methods
Implement route parameters
Create HTML forms to interact with the endpoints
Serve the HTML file as a static resource

Code

Modify server.js:
const express = require('express');
const path = require('path');
const app = express();
const port = 3000;

app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.static(path.join(__dirname, 'public')));

let users = [
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' }
];

app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});

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

app.post('/api/users', (req, res) => {
const newUser = {
id: users.length + 1,
name: req.body.name
};
users.push(newUser);
res.status(201).json(newUser);
});

app.get('/api/users/:id', (req, res) => {
const id = parseInt(req.params.id);
const user = users.find(u => u.id === id);
if (user) {
res.json(user);
} else {
res.status(404).json({ message: 'User not found' });
}
});

app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});
Create a new directory called public and add a file named index.html inside it:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Express.js Routes Example</title>
</head>
<body>
<h1>Express.js Routes Example</h1>

<h2>Get All Users</h2>
<button id="getAllUsers">Get All Users</button>
<pre id="allUsersResult"></pre>

<h2>Get User by ID</h2>
<form id="getUserForm">
<input type="number" id="userId" required>
<button type="submit">Get User</button>
</form>
<pre id="userResult"></pre>

<h2>Add New User</h2>
<form id="addUserForm">
<input type="text" id="userName" required>
<button type="submit">Add User</button>
</form>
<pre id="newUserResult"></pre>

<script>
document.getElementById('getAllUsers').addEventListener('click', async () => {
const response = await fetch('/api/users');
const users = await response.json();
document.getElementById('allUsersResult').textContent = JSON.stringify(users, null, 2);
});

document.getElementById('getUserForm').addEventListener('submit', async (e) => {
e.preventDefault();
const id = document.getElementById('userId').value;
const response = await fetch(`/api/users/${id}`);
const user = await response.json();
document.getElementById('userResult').textContent = JSON.stringify(user, null, 2);
});

document.getElementById('addUserForm').addEventListener('submit', async (e) => {
e.preventDefault();
const name = document.getElementById('userName').value;
const response = await fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name }),
});
const newUser = await response.json();
document.getElementById('newUserResult').textContent = JSON.stringify(newUser, null, 2);
});
</script>
</body>
</html>

Explanation

We useexpress.urlencoded({ extended: true }) middleware to parse form data.
We serve static files from the public directory using express.static.
The root route (/) serves the index.html file.
The /api/users POST route to create a new user with an auto-incremented ID.
The /api/users/:id GET route searches for a user in the users array and returns a 404 if not found.
We've created an index.html file with forms and buttons to interact with our API endpoints:
A button to get all users
A form to get a user by ID
A form to add a new user
The HTML file includes JavaScript to handle form submissions and button clicks, making fetch requests to our API endpoints and displaying the results.
This exercise provides a full-circle example of how to create Express.js routes and interact with them using HTML forms and JavaScript. Students can run the server and open http://localhost:3000 in their browser to see the forms and interact with the API endpoints directly.

Exercise 3: Implementing Middleware with File Logging

Example : Making Custom Middleware

Learning Outcomes

Understand the concept of middleware in Express.js
Implement custom middleware functions
Use built-in and third-party middleware
Implement file logging for server activities

Workflow

Create a custom logging middleware that writes to a file
Use the built-in express.static middleware
Implement error-handling middleware with file logging
Log all server activities to a text file

Code

Modify server.js:
const express = require('express');
const path = require('path');
const fs = require('fs');
const app = express();
const port = 3000;

// Create a write stream (in append mode)
const accessLogStream = fs.createWriteStream(path.join(__dirname, 'access.log'), { flags: 'a' });

// Custom logging middleware
app.use((req, res, next) => {
const logMessage = `${new Date().toISOString()} - ${req.method} ${req.url}\n`;
accessLogStream.write(logMessage);
console.log(logMessage.trim());
next();
});

// Built-in middleware for parsing JSON and serving static files
app.use(express.json());
app.use(express.static(path.join(__dirname, 'public')));

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

// Error-handling middleware
app.use((err, req, res, next) => {
const errorMessage = `${new Date().toISOString()} - Error: ${err.stack}\n`;
accessLogStream.write(errorMessage);
console.error(errorMessage.trim());
res.status(500).send('Something broke!');
});

// Log server start
app.listen(port, () => {
const startMessage = `${new Date().toISOString()} - Server started and running at http://localhost:${port}\n`;
accessLogStream.write(startMessage);
console.log(startMessage.trim());
});
Create a public directory and add an index.html file:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Static File Example</title>
</head>
<body>
<h1>Welcome to my static page!</h1>
</body>
</html>

Explanation of Changes

We import the fs (File System) module to handle file operations.
We create a write stream for our log file:
javascript
Copy
const accessLogStream = fs.createWriteStream(path.join(__dirname, 'access.log'), { flags: 'a' });
This creates (or opens if it already exists) a file named access.log in append mode.
Our custom logging middleware now writes to both the console and the log file:
javascript
Copy
app.use((req, res, next) => {
const logMessage = `${new Date().toISOString()} - ${req.method} ${req.url}\n`;
accessLogStream.write(logMessage);
console.log(logMessage.trim());
next();
});
The error-handling middleware also logs errors to both the console and the file:
javascript
Copy
app.use((err, req, res, next) => {
const errorMessage = `${new Date().toISOString()} - Error: ${err.stack}\n`;
accessLogStream.write(errorMessage);
console.error(errorMessage.trim());
res.status(500).send('Something broke!');
});
We log the server start event:
javascript
Copy
app.listen(port, () => {
const startMessage = `${new Date().toISOString()} - Server started and running at http://localhost:${port}\n`;
accessLogStream.write(startMessage);
console.log(startMessage.trim());
});
This modification ensures that all server activities, including requests, errors, and the server start event, are logged to both the console and a file named access.log. This approach provides a persistent record of server activities, which is useful for debugging and monitoring purposes.

Exercise 4: DOM Manipulation with JavaScript

Learning Outcomes

Select and modify DOM elements
Create and append new DOM elements
Handle events in the browser

Workflow

Create an HTML file with some initial elements
Write JavaScript to manipulate the DOM
Add event listeners to elements

Code

Create a file named index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DOM Manipulation</title>
</head>
<body>
<h1 id="title">Welcome to DOM Manipulation</h1>
<ul id="item-list">
<li>Item 1</li>
<li>Item 2</li>
</ul>
<button id="add-item">Add Item</button>

<script src="dom-script.js"></script>
</body>
</html>
Create a file named dom-script.js:

document.addEventListener('DOMContentLoaded', () => {
const title = document.getElementById('title');
title.style.color = 'blue';

const itemList = document.getElementById('item-list');
const addItemButton = document.getElementById('add-item');

let itemCount = 2;

addItemButton.addEventListener('click', () => {
itemCount++;
const newItem = document.createElement('li');
newItem.textContent = `Item ${itemCount}`;
itemList.appendChild(newItem);
});
});

Exercise 5: Asynchronous JavaScript with Promises and Async/Await

Learning Outcomes

Understand asynchronous JavaScript
Work with Promises
Use async/await syntax

Workflow

Create a function that returns a Promise
Use the Promise with .then() and .catch()
Refactor the code to use async/await

Code

Create a file named async-example.js:
javascript
Copy
function simulateAsyncOperation(success = true) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (success) {
resolve('Operation completed successfully');
} else {
reject('Operation failed');
}
}, 2000);
});
}

// Using Promises
simulateAsyncOperation()
.then(result => console.log(result))
.catch(error => console.error(error));

simulateAsyncOperation(false)
.then(result => console.log(result))
.catch(error => console.error(error));

// Using async/await
async function runAsyncOperations() {
try {
const result1 = await simulateAsyncOperation();
console.log(result1);

const result2 = await simulateAsyncOperation(false);
console.log(result2);
} catch (error) {
console.error(error);
}
}

runAsyncOperations();
Run the script:
bash
Copy
node async-example.js

Exercise 6: Form Handling and Data Validation

Learning Outcomes

Create and handle HTML forms
Implement client-side form validation
Process form data on the server

Workflow

Create an HTML form
Implement client-side validation with JavaScript
Handle form submission on the server

Code

Modify index.html:
html
Copy
<!DOCTYPE html>
<html lang="en">
<head>
Want to print your doc?
This is not the way.
Try clicking the ⋯ next to your doc name or using a keyboard shortcut (
CtrlP
) instead.