Share
Explore

MADS 4012 Full Stack Web Application Development: Session 4,5: February 1, 2

Session 5 - February 2 Building the full stack application: Connecting Express.js with the MONGO Database
Today’s developments of Mongo DB front-ended by Express.js : here are a number of build it up development versions of the code.
This will introduce the ways in which your Express Code must integrate with your MONGO Code. (Database name, schema name)
Here is the finished Gold Version of the Code:

Warm up Drill

Below is a simple warm-up Express.js program in Node.js that presents a calculator application.

The code incorporates ECMAScript 6 best practices, including the use of async/await for making Mongoose calls, async/await statements wrapped in try-catch, and arrow function syntax.

```javascript // Required Dependencies const express = require('express'); const bodyParser = require('body-parser'); const mongoose = require('mongoose');
// Initialize Express App const app = express();
// Middleware app.use(bodyParser.urlencoded({ extended: true })); app.set('view engine', 'ejs');
// MongoDB Connection mongoose.connect('mongodb://localhost:27017/calculatorDB', { useNewUrlParser: true, useUnifiedTopology: true }); const calculatorSchema = new mongoose.Schema({ num1: Number, num2: Number, result: Number }); const Calculator = mongoose.model('Calculator', calculatorSchema);
// Routes app.get('/', (req, res) => { res.render('index'); });
app.post('/calculate', async (req, res) => { const { num1, num2, operation } = req.body; let result;
try { switch (operation) { case '+': result = num1 + num2; break; case '-': result = num1 - num2; break; case '*': result = num1 * num2; break; case '/': result = num1 / num2; break; default: throw new Error('Invalid operation'); }
const newCalculation = new Calculator({ num1: num1, num2: num2, result: result });
await newCalculation.save(); res.render('index', { result: result }); } catch (error) { res.status(500).send('An error occurred during calculation'); } });
// Start Server app.listen(3000, () => { console.log('Server is running on port 3000'); }); ```
In this example, the code uses async/await for making Mongoose calls to the MongoDB database. The await statements are wrapped in a try-catch block to handle errors effectively. Additionally, arrow function syntax is used throughout the code for concise and clear function definitions. This program sets up an Express server with a basic calculator functionality, allowing users to perform addition, subtraction, multiplication, and division operations on input numbers.

Understanding EJS (Embedded JavaScript)

Introduction

Welcome, students! Today, we will delve into the world of EJS, also known as Embedded JavaScript. EJS is a popular template engine for Node.js and Express.js, used to generate HTML markup with plain JavaScript.

Part 1: What is EJS?

1.1 Definition

EJS is a simple templating language that lets you generate HTML markup with JavaScript. It allows you to embed JavaScript code directly within your HTML files.

1.2 Features

EJS supports both server-side and client-side template rendering.
It provides a straightforward syntax for embedding dynamic content, including variables, control flow, and template inclusion.
EJS enforces a clear separation between logic and presentation, facilitating code organization and reusability.

1.3 Example:

html<h1>Hello, <%= user.name %>!</h1>
In this example, <%= user.name %> is an EJS tag that injects the value of user.name into the HTML output.

Part 2: Integration with Node.js/Express.js

2.1 Setup

EJS can be easily integrated with Node.js and Express.js using the ejs package, which is commonly used for server-side rendering of templates.

2.2 Example Usage:

const express = require('express'); const app = express();
// Set 'ejs' as the view engine app.set('view engine', 'ejs');
// Render an EJS template
app.get('/', (req, res) => { const data = { user: { name: 'John' } };
res.render('index', { data }); });
In this example, we set EJS as the view engine for Express. When rendering the 'index' template, we pass a data object containing dynamic content to be injected into the template.

Part 3: Use Cases

3.1 Dynamic Content

EJS is ideal for rendering dynamic content, such as user-specific data, database records, or real-time information.

3.2 Reusable Components

It enables the creation of reusable template components, promoting modular design and code reusability.


In conclusion, EJS (Embedded JavaScript) serves as a powerful tool for generating HTML templates with dynamic content using JavaScript. Its seamless integration with Node.js and Express.js makes it a popular choice for server-side rendering in web applications.

By understanding EJS, students gain valuable insights into the world of server-side template rendering and dynamic content generation. This knowledge equips them with a versatile tool for building interactive and data-driven web applications using Node.js and Express.js.An example of what the content of the "index.ejs" file could look like. This file should be placed in a directory called "views" in the root directory of your Express application:

In our express.js program, the views directory is being referenced in the following line of code:

javascriptapp.set('view engine', 'ejs');

In this line, the app.set method is used to set the 'view engine' to 'ejs'. When Express is configured in this way, it automatically knows that the templates are stored in a directory named "views". This is a convention in Express.js where the views directory is assumed to be named "views" unless otherwise specified.

Therefore, when you provide the view engine as 'ejs', Express expects to find the EJS template files in the "views" directory. This is how Express knows where to look for the EJS templates when rendering them for a route.
Additionally, when rendering the EJS templates using res.render() method, Express will look for the specified template file within the "views" directory by default unless a specific path is provided.
So, the line app.set('view engine', 'ejs'); is where the views directory is referenced and configured as the default location for EJS templates in the Express application.
In this "index.ejs" file, we have a simple HTML form for entering two numbers and choosing an arithmetic operation. When the form is submitted, it makes a POST request to the "/calculate" route in the Express app. If there is a result available, it will be displayed below the form.
Make sure to save this code in a file named "index.ejs" and place it in a directory called "views" in the root directory of your Express application.

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Calculator</title> </head> <body> <h1>Simple Calculator</h1> <form action="/calculate" method="post"> <input type="number" name="num1" placeholder="Enter first number" required> <select name="operation"> <option value="+" selected>Addition (+)</option> <option value="-">Subtraction (-)</option> <option value="*">Multiplication (*)</option> <option value="/">Division (/)</option>

</select> <input type="number" name="num2" placeholder="Enter second number" required> <button type="submit">Calculate</button> </form>
<% if (typeof result !== 'undefined') { %> <h2>Result: <%= result %></h2> <% } %> </body> </html>

Here's a line-by-line explanation of what the provided Express.js program does:

```javascript // Required Dependencies const express = require('express'); const bodyParser = require('body-parser'); const mongoose = require('mongoose');
// Initialize Express App const app = express();
// Middleware app.use(bodyParser.urlencoded({ extended: true })); app.set('view engine','ejs');
// MongoDB Connection mongoose.connect('mongodb://localhost:27017/calculatorDB', { useNewUrlParser: true, useUnifiedTopology: }); ``` 1. Required Dependencies: The program begins by requiring the necessary modules: `express` for creating the server, `body-parser` for handling form data, and `mongoose` for interacting with the MongoDB database. 2. Initialize Express App: It initializes an instance of the Express application. 3. Middleware: The program uses `bodyParser` middleware to parse URL-encoded form data and sets the view engine to 'ejs' for rendering dynamic content using EJS templates. 4. MongoDB Connection: It establishes a connection to the MongoDB database named 'calculatorDB' using `mongoose`, specifying connection options.
```javascript // Define MongoDB Schema and Model const calculatorSchema = new mongoose.Schema({ num1: Number, num2: Number, result: Number }); const Calculator = mongoose.model('Calculator', calculatorSchema); ``` 1. Define MongoDB Schema and Model: It defines a Mongoose schema named `calculatorSchema` with fields for `num1`, `num2`, and `result`, representing the numbers to be operated on and the result of the operation. Then, it creates a Mongoose model named `Calculator` based on the schema.
```javascript // Routes app.get('/', (req, res) => { res.render('index'); }); ``` 1. Index Route: It handles the GET request to the root URL ('/'). When accessed, it renders the 'index' template, which will likely contain the form for calculator input and display of the result.
```javascript app.post('/calculate', async (req, res) => { const { num1, num2, operation } = req.body; let result;
try { // Perform Calculation based on the operation switch (operation) { case '+': result = num1 + num2; break; case '-': result = num1 - num2; break; case '*': result = num1 * num2; break; case '/': result = num1 / num2; break; default: throw new Error('Invalid operation'); }
// Save the calculation to the MongoDB database const newCalculation = new Calculator({ num1: num1, num2: num2, result: result });
await newCalculation.save(); // Render the result on the index page res.render('index', { result: result }); } catch (error) { // Handle any errors that occur during the calculation or database operation res.status(500).send('An error occurred during calculation'); } }); ``` 1. Calculate Route: It handles the POST request to the '/calculate' URL. It retrieves the input `num1`, `num2`, and `operation` from the request body and performs the corresponding calculation based on the chosen operation (`+`, `-`, `*`, or `/`). 2. Database Interaction: It creates a new instance of the `Calculator` model with the input numbers and the result, and saves it to the MongoDB database. The `save` method is awaited to ensure the operation completes before rendering the result. 3. Response Handling: Any errors that occur during the calculation or database operation are caught and a 500 Internal Server Error response is sent with an appropriate message.
```javascript // Start Server app.listen(3000, () => { console.log('Server is running on port 3000'); }); ``` 1. Start Server: It starts the Express server to listen on port 3000 and logs a message indicating that the server is running.
This program sets up an Express server to handle rendering the calculator interface, performing calculations, and storing the results in a MongoDB database, all following ECMAScript 6 best practices.

Learning Outcomes:
Let’s make an interesting full stack Node.js application to exercise the power of the Node.js Application Development Framework:
Explainer Video:

Lecture: Understanding req and res in Express and Post/Get Encoding with Forms

Introduction

In Express.js, handling HTTP requests and responses is fundamental to building web applications. Additionally, understanding how data is exchanged between the client and the server using form submissions is crucial. This lecture will cover the concepts of req and res in Express, as well as the Post/Get encoding with forms.

Part 1: Understanding req and res in Express

1.1 Request (req) Object

When a client makes an HTTP request to the server, Express handles this request with the req object.
The req object contains information about the HTTP request made by the client, such as parameters, query strings, headers, and body content.

1.2 Response (res) Object

Once the server processes the request, it uses the res object to send a response back to the client.
The res object allows us to set response headers, status codes, and send the response data back to the client.

1.3 Example:

javascriptapp.get('/example', (req, res) => { // Accessing query parameters from the request URL const name = req.query.name; // Sending a JSON response back to the client res.status(200).json({ message: `Hello, ${name}!` }); });

Part 2: Post/Get Encoding with Forms

2.1 The HTML Form

HTML forms provide a way to send data from the client to the server. Forms typically use the HTTP methods POST and GET to send the form data to the server.

2.2 GET Method:

When a form is submitted using the GET method, the form data is appended to the URL in the form of query parameters.
This method is suitable for simple requests with limited data because the data is visible in the URL.

2.3 POST Method:

When a form is submitted using the POST method, the form data is sent in the body of the HTTP request, rather than in the URL.
This method is suitable for sending large amounts of data or sensitive information, as it is not visible in the URL.

2.4 Form Encoding Types:

Forms can be encoded using different types.
application/x-www-form-urlencoded: This is the default encoding type for HTML forms. It URL-encodes the form data before sending it to the server.
multipart/form-data: This encoding type is used when forms contain file uploads.
text/plain: This encoding type sends the form data in a single line of text, with newline characters representing form fields.

2.5 Example:

html<form action="/submit-form" method="post"> <label for="username">Username:</label> <input type="text" id="username" name="username" required> <label for="password">Password:</label> <input type="password" id="password" name="password" required> <button type="submit">Submit</button> </form>

Conclusion

In conclusion, understanding the req and res objects in Express is crucial for handling incoming requests and sending appropriate responses to the client. Additionally, grasping the differences between the POST and GET methods, as well as the form encoding types, is essential for effectively handling form submissions in web applications.
Thank you for attending this lecture on Understanding req and res in Express and Post/Get Encoding with Forms.
This lecture provides a comprehensive overview of the concepts related to handling requests and responses in Express.js, as well as the intricacies of form submissions using the POST and GET methods with different encoding types. This knowledge is crucial for anyone working with web applications and serves as a solid foundation for further exploration into web development using Express.

Bookstore Application

Let's consider a MongoDB CRUD case study for a bookstore application, where MongoDB is utilized to manage the books and customers data.

In this case study will cover various operations related to managing the bookstore's inventory and customer information in a MongoDB database:

Create (C):
Scenario: Adding a new book to the inventory or registering a new customer in the database.
Solution: The Express application can handle requests to add a new book or customer by creating and saving a new document in the respective MongoDB collection. For example, when adding a new book, the application can populate fields such as title, author, genre, price, and quantity available in the inventory collection.
Read (R):
Scenario: Displaying the list of available books, retrieving specific book details, or fetching customer information from the database.
Solution: The application can define API endpoints to serve requests for reading book details or customer information. It then uses Mongoose to query the corresponding MongoDB collections and return the requested data in a structured format.
Update (U):
Scenario: Modifying book details (e.g., updating the price, adding new copies) or updating customer information (e.g., changing contact details).
Solution: Upon receiving an update request, the Express application uses Mongoose to find and update the respective document in the MongoDB collection. For instance, updating book details could involve modifying fields like price, quantity, or other metadata.
Delete (D):
Scenario: Removing a book from the inventory or deleting customer records from the database.
Solution: The application can handle delete requests by using Mongoose to find and remove the specified book or customer document from the respective MongoDB collection.
This case study demonstrates the utilization of MongoDB for managing book inventory and customer information in a bookstore application, emphasizing how CRUD operations can be effectively performed using Node.js, Express, and MongoDB. It provides a tangible example for students to visualize the interaction between the application and the database in a real-world context.

Let's set up a basic Node.js app with Express and MongoDB.

In this example, we'll utilize async/await for making Mongoose calls and ensure that await statements are wrapped in a try-catch block. We'll also use arrow function syntax where applicable.

Setup the Project:
Ensure you have Node.js and npm installed.
Create a new directory for the project and initialize it with a package.json file by running npm init -y.
Install required packages: npm install express mongoose.
Create the Express App:
Create a file named app.js and set up a basic Express app with routes and MongoDB connection:
// app.js
// Import required modules const express = require('express'); const mongoose = require('mongoose');
// Initialize Express app const app = express(); const PORT = 3000;
// Connect to MongoDB using async/await (async () => { try { await mongoose.connect('mongodb://localhost:27017/myapp', { useNewUrlParser: true, useUnifiedTopology: true, }); console.log('Connected to MongoDB'); } catch (error) { console.error('Error connecting to MongoDB: ', error); } })();
// Define a sample route app.get('/', (req, res) => { res.send('Hello, World!'); });
// Start the server app.listen(PORT, () => { console.log(`Server running on port ${PORT}`); });
Run the Application:
Start the application by running the command node app.js.
Access http://localhost:3000 in a web browser to see the "Hello, World!" message.
This example follows ECMAScript 6 best practices by using async/await for making Mongoose calls and wrapping await statements in a try-catch block. Additionally, arrow function syntax is used for concise code. Students can use this as a starting point to understand the setup of a Node.js app with Express and MongoDB, while adhering to best practices.

Let's break down the code line by line:

```javascript // Import required modules const express = require('express'); const mongoose = require('mongoose'); ``` - Here, we import the required Node.js modules, `express` for creating the web application and `mongoose` for interacting with MongoDB.
```javascript // Initialize Express app const app = express(); const PORT = 3000; ``` - We initialize the Express application and set the port number to 3000.
```javascript // Connect to MongoDB using async/await (async () => { try { await mongoose.connect('mongodb://localhost:27017/myapp', { useNewUrlParser: true, useUnifiedTopology: true, }); console.log('Connected to MongoDB'); } catch (error) { console.error('Error connecting to MongoDB: ', error); } })(); ``` ​This section uses an immediately invoked async function to establish a connection to the MongoDB database.
Within the function, we use async/await to connect to the MongoDB server at `mongodb://localhost:27017/myapp` with the specified options.
If the connection is successful, a message "Connected to MongoDB" is logged. Otherwise, any connection error is caught and logged.
```javascript // Define a sample route app.get('/', (req, res) => { res.send('Hello, World!'); }); ``` - We define a basic route using Express framework. When a GET request is made to the root URL (`/`), the server responds with "Hello, World!".
```javascript // Start the server app.listen(PORT, () => { console.log(`Server running on port ${PORT}`); }); ``` - Finally, we start the Express server to listen on port 3000. Once the server is running, a message "Server running on port 3000" is logged.
This line-by-line narrative explains the functionality of each line in the provided code, enabling students to understand the purpose and usage of ECMAScript 6 best practices in a basic Node.js app with Express and MongoDB.

Let's delve deeper into the purpose and functionality of defining a route in the Express.js application architecture:

In the context of an Express.js application, a route determines how the server should respond to client requests to a specific endpoint (URL) and HTTP method (e.g., GET, POST, PUT, DELETE).
Defining routes is integral to building the API and handling various client requests effectively.
Here's a detailed breakdown of the route definition in the provided Express.js application:
```javascript // Define a sample route app.get('/', (req, res) => { res.send('Hello, World!'); }); ```
1. Defining the Route: - `app.get('/')`: This line sets up a GET request handler for the root URL `/`. It indicates that when a client sends a GET request to the root URL of the server, the following function should be executed to handle the request. Express provides corresponding methods such as `app.post()`, `app.put()`, and `app.delete()` for handling other HTTP methods.
2. **Request and Response Parameters**: - `(req, res) => { ... }`: The callback function takes two parameters - `req` (request) and `res` (response). These parameters provide access to the incoming HTTP request and allow the server to construct and send an HTTP response back to the client.
3. **Handling the Request**: - `res.send('Hello, World!')`: Within the callback function, `res.send()` is used to send a response back to the client. In this case, when a request is made to the root URL, the server responds with the string "Hello, World!". This could be a simple text response, or it could be a more complex JSON object, an HTML page, or even a file download, depending on the requirements of the application.
4. **Route Modularity and Organization**: - As the application grows, routes can be modularized and organized into separate files using Express Routers. This helps in maintaining a clean and organized codebase by separating different parts of the API into distinct route files.
Defining routes in an Express.js application plays a pivotal role in determining how the server handles incoming requests and serves appropriate responses. It allows for handling different HTTP methods, modularizing the code, and structuring the API in a logical and organized manner. Understanding routes is fundamental to building robust and scalable web applications using Express.js.

Understanding how form data from the client-side reaches the backend server via Express.js involves delving into the mechanics of the HTTP protocol and how Express.js handles incoming requests. Here's a detailed explanation of how the form's action attribute and the Express.js server work together to handle form data:

1. **HTML Form Submission**: - When a user submits a form on the client-side (e.g., a web browser), the browser takes the form data and constructs an HTTP request based on the form's method (e.g., GET or POST) and action (e.g., URL to send the form data to) attributes.
2. **Action Attribute of the Form**: - The action attribute of the HTML form specifies the URL (i.e., the endpoint) to which the form data should be submitted. For example, `<form action="/submit-form" method="POST">` tells the browser to send the form data to the `/submit-form` endpoint on the server using the POST method.
3. **Express.js Server Setup**: - On the server-side, an Express.js application is set up to listen for incoming HTTP requests. This is achieved by configuring route handlers for specific endpoints, including the endpoint specified in the form's action attribute.
4. **Endpoint Handling in Express.js**: - When an HTTP request is made to the endpoint specified in the form's action attribute, Express.js middleware and route handlers are triggered based on the HTTP method and the URL endpoint. For example:
```javascript // Handling form submission endpoint app.post('/submit-form', (req, res) => { // Handle form data here }); ```
5. **Receiving Form Data on the Server**: - In the route handler (`app.post('/submit-form')`), the `req` object represents the incoming HTTP request and contains the form data sent by the client. Express.js parses the form data and makes it accessible within the `req.body` object, allowing the server-side logic to access and process the form input data.
6. **Processing Form Data and Interaction with MongoDB**: - Once the form data is received by the server, it can be processed, validated, and eventually interacted with MongoDB for storage or retrieval. Within the route handler, the server can use Mongoose models and operations to store the form data in MongoDB or perform any necessary database interactions.
In essence, the form's action attribute, coupled with the Express.js server's route handling, enables the seamless transfer of form data from the client-side to the backend server, where it can be further processed and potentially stored in a MongoDB database. This knowledge provides students with a foundational understanding of how the client-server interaction occurs in the context of form submissions and backend data handling.

The form submission and Express.js listening are not directly based on the JavaScript network socket at the application level. Instead, these actions operate within the context of the HTTP protocol and the underlying networking infrastructure. Here's a deeper elucidation of the relevant networking details:
1. **Form Submission and HTTP Protocol**: - When a user submits a form in a web browser, the browser constructs an HTTP request based on the form's method and action attributes. This HTTP request includes the form data as its payload and is sent to the server specified in the action attribute. - The form data is sent as part of the HTTP request body when using the POST method, or as query parameters in the URL when using the GET method.
2. **Express.js Server and Networking**: - Express.js, being a web framework for Node.js, operates at a higher level of abstraction and does not directly interact with network sockets at the JavaScript application level. Instead, Express.js relies on lower-level networking capabilities provided by Node.js, including the core HTTP module for handling incoming requests and sending responses.
3. **HTTP Module and Networking in Node.js**: - Node.js's HTTP module provides the necessary functionality for creating an HTTP server, handling incoming requests, and making outgoing requests. When an Express.js application is running, it internally leverages the HTTP module to listen for incoming requests on a specified port and route those requests to the appropriate handler functions.
4. **TCP/IP Networking Layer**: - At a lower level, form submission, Express.js server listening, and all other network communication in web applications are underpinned by the TCP/IP networking protocol suite. When a form is submitted, the browser uses TCP/IP to establish a connection with the server and send an HTTP request, and the server uses TCP/IP to listen for and accept incoming connections on a specified port.
While form submission and the Express.js server operation are not directly based on raw JavaScript network sockets, they operate within the broader context of the HTTP protocol, the networking capabilities provided by Node.js, and the underlying TCP/IP networking layer. Understanding these networking details helps students comprehend how data flows between a client-side form and a backend server, and how the underlying networking infrastructure facilitates this exchange.

Backstory narrative of how the HTTP protocol works within tcp, and how it allows protocols like json and html to ride on it between network end points

HTTP and JSON are not protocols that ride directly on a TCP pipe, but they are instead data formats and communication protocols that operate at a higher level of the networking stack. Here's a clearer explanation:
1. **TCP as Transport Protocol**: - TCP (Transmission Control Protocol) operates at the transport layer of the TCP/IP networking stack. It provides reliable, ordered, and error-checked delivery of a stream of bytes between applications running on hosts connected via a network.
2. **HTTP Protocol**: - HTTP (Hypertext Transfer Protocol) operates at the application layer of the TCP/IP stack. It is used for transmitting hypermedia documents, such as HTML. When a browser requests a web page from a server over the Internet, it uses HTTP, which operates over the TCP connection, to do so.
3. **JSON as a Data Format**: - JSON (JavaScript Object Notation) is a lightweight data interchange format. It is not a protocol, but rather a way to structure data that can be easily transmitted between a server and a client. When used in the context of web applications, JSON data is often transmitted as part of an HTTP response or request.
In summary, while TCP provides the underlying transportation mechanism for reliable data delivery across networks, HTTP operates at a higher level to define the rules for structuring and transmitting requests and responses between clients and servers. JSON, on the other hand, is a data format often used within the context of HTTP to exchange structured data.

Coding Drill: Building the Bookstore

// Let’s setup a bookstore. You can make a data model on another topic

// of interest for this Lab.
const mongoose = require('mongoose');

// Define MongoDB connection URI and options
const mongoURI = 'mongodb://localhost:27017/bookstore';
const mongoOptions = {
useNewUrlParser: true,
useUnifiedTopology: true,
};

// Book schema
const bookSchema = new mongoose.Schema({
title: String,
author: String,
genre: String,
publishedYear: Number
});

// Customer schema
const customerSchema = new mongoose.Schema({
name: String,
email: String,
membership: String
});

// Book model
const Book = mongoose.model('Book', bookSchema);

// Customer model
const Customer = mongoose.model('Customer', customerSchema);

// Function to seed the database
const seedDatabase = async () => {
try {
await mongoose.connect(mongoURI, mongoOptions);
console.log('Connected to MongoDB');

// Seed the database with 10 book records
const books = [
{ title: 'The Hobbit', author: 'J.R.R. Tolkien', genre: 'Fantasy', publishedYear: 1937 },
// Add more book records here
];
await Book.insertMany(books);
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.