Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...

Full Transcript

Backend Application Development v2.2 Lecture VI How Does Web Work “Introduction Routes” Prepared by Namgay Dema OVERVIEW & PURPOSE This section provides a found...

Backend Application Development v2.2 Lecture VI How Does Web Work “Introduction Routes” Prepared by Namgay Dema OVERVIEW & PURPOSE This section provides a foundational understanding of how Node.js handles web requests, emphasizing its architecture's event-driven, non-blocking nature. It covers essential concepts such as request handling, routing, parsing user input, and the difference between blocking and non-blocking code, equipping readers with the knowledge needed to build efficient, scalable web applications. Contents OVERVIEW & PURPOSE 0 Pre-requisites 1 Software and Hardware Prerequisite 1 How Web Works 1 NodeJS Program LifeCycle 7 Understanding the Request Object 8 Routing Requests 11 Redirecting Requests 15 Parse the User's Input and Write It to the File 18 Summary 21 Understanding Event-Driven Code Execution 21 Key Takeaways 23 Blocking vs Non-Blocking Code 24 Try out some challenges 27 Change Log 28 Pre-requisites 1. General knowledge of how the web works is recommended but not a must-have 2. Basic JavaScript knowledge is strongly recommended but could be picked up while going through the course 3. NO NodeJS knowledge is required! Software and Hardware Prerequisite 1. Personal Computer with at least 8 (16 Preferred) GB, 10 GB HDD Space, Intel at least 10th Gen equivalence 2. Windows 10/11 or macOS >= 10.15 or Linux 64-bit with kernel >= 1.4. 3. Docker 4. Visual Studio Code How Web Works 1 The web operates by having a user interact with a browser, entering a URL which the browser translates through domain name servers to an IP address corresponding to a server. The browser sends a request to this server's IP address, and here Node.js (or other server-side technologies like PHP, ASP.NET, or Ruby on Rails) processes the request. Node.js code handles user input validation, communicates with a database if needed, and formulates a response, which could be HTML, JSON, XML, or a file. This exchange follows protocols like HTTP or HTTPS, with HTTPS providing encrypted data transfer for security which is understood by Browser and Server. Node.js powers the server that handles these requests and responses, enabling dynamic web interactions. Create a Node Server Let's dive into creating a basic web server using Node.js. a. Create a file app.js 2 This file will serve as the entry point for your Node.js application. This is where you’ll write the code to create a web server. b. Importing Core Modules Node.js provides several built-in modules, such as http, fs, and path, which can be used without installing any third-party packages. To create a server, you imported the http module using the require function: c. Creating a Server 3 http.createServer: The http module provides the createServer method, which is used to create a new HTTP server. This server listens for incoming requests and processes them with a request listener function: const server = http.createServer((req, res) => { console.log(req); }); The createServer method requires a callback function (also known as a request listener) that handles incoming requests. This function takes two parameters: ○ req: Represents the incoming request. ○ res: Represents the response that will be sent back to the client. d. Starting the Server server.listen: To make the server listen for incoming requests, you use the listen method. This method takes the port number as an argument, and optionally, a hostname: 4 The server will continue to run and listen on the specified port (3000 in this example). You can access the server by navigating to http://localhost:3000 in your browser. e. Running the Server Running the App: To run your Node.js application, open a terminal in your project folder and execute: node app.js Once the server is running, it will log details of any incoming requests to the console. Open a web browser and go to http://localhost:3000. While nothing may appear in the browser (sin 5 However, a request object has been executed in your terminal. Following these steps, you've set up a basic web server with Node.js that can listen to and log incoming requests. This forms the foundation for building more complex applications that can handle routing, serve static files, and more. 6 NodeJS Program LifeCycle When we run the file with node app.js, it will start the script, and Node.js parses and executes the code, keeping the program running due to the event loop. This event loop, a core concept in Node.js, continues running as long as there are event listeners, such as the incoming request listener that we set up with http.createServer. Node.js uses this event-driven approach because it executes single-threaded JavaScript, enabling it to handle multiple requests efficiently through fast event handling and behind-the-scenes multi-threading via the operating system. 7 The event loop keeps the server available, executing code when specific events occur. If we use process. exit, we can manually stop the event loop and the server, but typically we avoid this to keep the server running and responsive. Therefore, understanding this event loop is crucial as we move forward in handling request and response objects in our server code. Understanding the Request Object The req object contains details about the incoming request, such as the request method, headers, and URL. Analyzing this object helps us understand what the client is requesting. 8 The line console.log(req.url, req. method, req. headers); in the code is used to log specific details about the incoming HTTP request to the console. Let's break down what each part of this line logs and why it is useful for understanding the requests: req.url: ○ req.url logs the URL path of the incoming request. ○ Example: If the request is made to http://localhost:3000/about, req. url would log /about. req. Method: ○ req. method logs the HTTP method of the incoming request. ○ Example: Common methods include GET, POST, PUT, and DELETE. req.headers: ○ req. headers logs an object containing the headers of the incoming request. ○ Example: Headers might include Content-Type, User-Agent, Authorization, etc. 9 Sending Responses To handle and send a response back to the client, you need to utilize the res object in the callback function provided to http.createServer. The res object represents the HTTP response that will be sent back to the client. a. Setting Content-Type Header: // Set the response header res.setHeader('Content-Type', 'text/html'); This header informs the browser that the response contains HTML content. The browser uses this information to correctly render the content. b. Writing the Response Body // Write a response res.write(''); res.write('My First Node.js Server'); res.write('Hello from my Node.js Server!'); res.write(''); The res.write() method is used to send chunks of data as part of the response body. In this example, you're sending HTML content that will be rendered in the browser. c. Ending the Response After you’ve finished writing the response body, you call res.end() to signal to Node.js that the response is complete and can be sent back to the client. Attempting to write to the response after calling end() will result in an error. // End the response 10 res.end(); d. Testing the Response Running the Server: After saving your changes, restart the server and navigate to http://localhost:3000 in your browser. You should see the message "Hello from my Node.js server!" displayed on the page. Routes In a web application, a route is a specific endpoint that the application can respond to. It represents a specific URL path and the HTTP method (GET, POST, PUT, DELETE, etc). They are the entry point of our application and determine how the application responds to a client request to a particular endpoint. Think of routes as the menu at the restaurant. Each menu item represents a different route that the customer can choose, just like each route in the web application represents a different endpoint point that a user can request. 11 Now that we know how to read data from the request (req) and send a response (res), the next logical step is to connect these two. For example, you could respond differently based on the request URL or method, enabling more dynamic and interactive server behavior. Routing Requests Let's look into how to create a basic Node.js web server that serves different content depending on the route the user accesses. To implement routing in a Node.js server, we need to examine the req.url properties to determine how to handle each incoming request based on the URL path and HTTP method. Step 1: Handle Requests Capture the URL: Use the request.url property to capture the part of the URL after the hostname. const http = require('http'); const server = http.createServer((req, res) => { const url = req.url; // Process the request based on the URL }); server.listen(3000); Check the URL Path: 12 Use an if statement to check if the URL path is exactly '/'. This will be our home route. if (url === '/') { // Handle the root route } Step 2: Serve HTML Form on the Root Route Send an HTML Form: If the user visits the root route (/), send back an HTML form that allows them to input a message. if (url === '/') { res.setHeader('Content-Type', 'text/html'); res.write(''); res.write('Enter Message'); res.write(''); res.write(''); res.write('Send'); res.write(''); res.write(''); res.write(''); return res.end(); } The HTML form has a single input field () and a submit button. The form’s action attribute is set to /message, meaning that the form data will be sent to the /message route when the user clicks "Send". The method is set to POST, indicating that this will be a POST request, rather than the default GET. 13 To prevent further code execution, return from the function immediately after calling res.end(). This ensures that the server doesn't try to execute any further code after sending the response, which could cause errors. Step 3: Test the Web Server Run the Server: Start the server by running node app.js in your terminal. Access the Form: Visit http://localhost:3000/ in your browser. You should see a simple form with an input field and a "Send" button. Submit the Form: Enter a message and click "Send." The browser will navigate to /message, but the server won't yet handle this route (this will be covered in the next steps). If the URL path is not /, the server responds with a default HTML page. The Content-Type header is set to text/html to indicate that the response content is HTML. res.write is used to write the HTML content. res.end() is called to end the response and send it back to the client. const http = require('http'); const server = http.createServer((req, res) => { const url = req.url; if (url === '/') { res.write(''); res.write('Enter Message'); 14 res.write('Send'); res.write(''); return res.end(); } res.setHeader('Content-Type', 'text/html'); res.write(''); res.write('My First Page'); res.write('Hello from my Node.js Server!'); res.write(''); res.end(); }); server.listen(3000); Redirecting Requests Step 1: Handle the POST Request to /message Check for POST Requests: We need to ensure that we handle only POST requests to the /message route. Use an if statement to check both the url and method. 15 if (url === '/message' && req.method === 'POST') { // Code to handle the POST request goes here } Redirect the User: After handling the request, we want to redirect the user back to the home page (/). Set the status code to 302 (which indicates a redirect) and the Location header to /. if (url === '/message' && req.method === 'POST') { // Redirect the user res.statusCode = 302; res.setHeader('Location', '/'); return res.end(); } Step 2: Write Data to a File Import the File System Module: To write the user's message to a file, we need to use Node.js’s fs (file system) module. const fs = require('fs'); Write a New File: Use the fs.writeFileSync method to create a new file (or overwrite an existing one) and write the message into it. For now, let’s start by writing some dummy text into the file. 16 Step 3: Test the Redirection and File Writing Restart the Server: After making changes to the server code, you need to stop the server (if it’s running) by pressing Ctrl + C and then restart it using node app.js. Submit a Message: Visit http://localhost:3000/, enter some text in the input field, and click "Send." The page should reload, and a file named message.txt should be created in the same directory as your app.js file, containing the text "Dummy text." 17 Parse the User's Input and Write It to the File Streams in Node.js: Node.js handles incoming data (like HTTP requests) as a stream, meaning it processes the data in small chunks rather than waiting for the entire payload. This approach is particularly beneficial for large payloads (e.g., file uploads), allowing Node.js to begin processing data immediately, even before receiving the entire payload. Buffers: A buffer acts as a temporary holding area for these chunks of data. It collects chunks until you’re ready to process the entire set. In your example, the Buffer.concat() method combines all the chunks into a single buffer that can then be processed as a whole. Handling Incoming Data with Streams and Buffers 1. Request Handling: When Node.js receives a request, the data isn't immediately available as a complete object. Instead, it's received in chunks, which Node.js processes sequentially. 2. Listening to Data Events: By attaching an event listener to the data event, you can capture these chunks as they arrive. These chunks are then stored in an array (or buffer) for later processing. 3. End Event: After all the data chunks have been received, an end event is triggered. At this point, you can concatenate the chunks and convert them into a string or another format that suits your needs. Implementation Steps 18 1. Listen for Data Chunks: We use the on('data') event listener to listen for chunks of data as they arrive. Each chunk of data is added to an array (body) as it arrives. const body = []; req.on('data', (chunk) => { body.push(chunk); }); 2. Collect Chunk data req.on('data', (chunk) => {... }): An event listener is set up to handle the 'data' event, which is triggered when a new chunk of data is received. chunk: The data chunk received from the client. console.log(chunk);: Logs the received chunk to the console for debugging purposes. 3. Listen for the End of the Data Stream: After all chunks have been received, the on('end') event is triggered. At this point, we can concatenate the chunks stored in the body array into a single buffer and convert it into a string. req.on('end', () => { const parsedBody = Buffer.concat(body).toString(); console.log(parsedBody); // Outputs the entire request body as a string 19 }); req.on('end', () => {... }): An event listener is set up to handle the 'end' event, which is triggered when all the data chunks have been received. Buffer.concat(body).toString();: The body array, which contains all the data chunks, is concatenated into a single Buffer and then converted to a string. parsedBody.split('=');: The resulting string is split at the = character, extracting the value of the form input named message. 4. Parsing and Using the Data The request body will contain the data in the format of key=value pairs (e.g., message=hello). To extract the message, we split the string by the = character. const parsedBody = Buffer.concat(body).toString(); const message = parsedBody.split('='); 5. Writing the Message to a File: After extracting the message, we can use the fs.writeFileSync method to write the message to a file (message.txt). We move this file-writing code inside the end event listener to ensure it's executed after the data has been fully received. 20 req.on('end', () => { const parsedBody = Buffer.concat(body).toString(); const message = parsedBody.split('='); fs.writeFileSync('message.txt', message); // Redirect the user back to the home page res.statusCode = 302; res.setHeader('Location', '/'); return res.end(); }); Testing the Implementation Restart the Server: ○ After implementing these changes, restart your Node.js server. Submit a Message: ○ Visit your form in the browser, enter a message, and submit it. ○ The message should now be written to the message.txt file. Check the File: ○ Open message.txt and verify that the submitted message is correctly stored. 21 This approach ensures the server can handle incoming form submissions, parse the data correctly, and store the information as needed. Summary We used Node.js's event-driven model to handle incoming data streams from POST requests. We utilized buffers to accumulate data chunks and processed them once the full data was received. Finally, we wrote the parsed data to a file and redirected the user back to the home page. Understanding Event-Driven Code Execution Asynchronous Code Execution Node.js operates asynchronously, meaning that it doesn’t block the execution of your code while waiting for operations to complete. Instead, it moves on to the next task, and once the operation is complete, it triggers the corresponding callback function. Event Listeners are Registered, Not Executed Immediately When you write code like req.on('data',...), you’re registering an event listener for the data event. This listener will be triggered when a chunk of data is received, but it won’t execute immediately. Instead, Node.js registers this function internally and moves on to the next line of code. Asynchronous Execution and Callbacks 22 Similarly, the req.on('end',...) listener is registered to run when the entire request has been received. This will execute sometime in the future when Node.js has finished reading all the data. Node.js does not pause or block the code execution while waiting for this event. It continues executing any code that comes after these listeners. Potential Pitfall: Executing Code Too Early If you have code that depends on the data being fully received, you must ensure it’s placed inside the event listeners. Otherwise, it might execute before the data is fully available. For instance, if you send the response (res.end()) outside the end event listener, it might execute before the file is written, causing errors like Cannot set headers after they are sent to the client. Solution: Return Early to Avoid Premature Execution To prevent code from executing prematurely, you can return early in your functions when registering event listeners. This ensures that no additional code is executed until the necessary asynchronous operations have completed. 23 Key Takeaways Asynchronous Patterns: Node.js uses asynchronous patterns extensively. This means that callbacks and event listeners are often executed later, not at the point in the code where they are written. Non-Blocking Execution: Node.js doesn’t wait for asynchronous operations to complete. It continues executing the next lines of code, which can lead to situations where certain operations happen out of the expected order if not handled properly. Event Loop: Node.js relies on an event loop that constantly checks for events (like incoming data) and triggers the appropriate callback functions when those events occur. 24 Blocking vs Non-Blocking Code Blocking Code: Operations that block the execution of subsequent code until they are complete. Example: fs.writeFileSync is a synchronous operation that blocks the event loop until the file is written. fs. writeFileSync('Message.txt' , message){ res.statusCode = 302; res.setHeader('Location', '/'); return res.end(); }; Non-Blocking Code: Operations that allow the execution of subsequent code while the current operation is still in progress. Example: fs.writeFile is an asynchronous operation that does not block the event loop fs. writeFile('Message.txt' , message, err =>{ res.statusCode = 302; res.setHeader('Location', '/'); return res.end(); }); The writeFile method provides an asynchronous alternative. It accepts a callback function that is executed once the file has been written, allowing Node.js to continue processing other code in the meantime. 25 Node Modules System Step 1: Create a file called routes.js and “CTRL + V” the code from app.js in route.js Make sure you copy the if-block code into your routes.js Step 2: Open your route.js Create a function called requestHanlder with the following 26 const fs = require('fs'); const requestHanlder = (req, res) =>{ const url = req.url; const method = req.method; if (url === '/') { res.write(''); res.write('Enter Message'); res.write('Send'); res.write(''); return res.end(); } if (url === '/message' && method === 'POST'){ const body = [] req.on('data', (chunk) =>{ console.log(chunk); body.push(chunk); }); return req.on('end', () =>{ const parsedBody = Buffer.concat(body).toString(); const message = parsedBody.split('='); fs. writeFile('Message.txt' , message, err =>{ res.statusCode = 302; res.setHeader('Location', '/'); return res.end(); }); }); } res.setHeader('Content-Type', 'text/html'); res.write(''); res.write('My First Page'); res.write('Hello from my Node.js Server!'); res.write(''); res.end(); 27 } module.exports = requestHanlder; This function should export to be used by another file by using the module.exports = functionName(requestHanlder) Step 3: Update your app.js To use a function that resides in another file, you have to import the file by using the require keyword const http = require('http'); const routes = require('./routes'); const server = http.createServer(routes); server.listen(3000); Step 4: Test your code 28 Congrats !!! You can get a response and write it on your file. Try out some challenges Follow this instruction to set up a Node.js server, define routes, and handle form submissions to ensure you understand how to implement these functionalities effectively. 1. Spin up a Node. js-driven server (on port 3000) 2. Handle two routes i.e “/‘ and “/users” a. Return some greeting text on “/” b. Return a list of dummy users (e.eg User 1 3. Add a form with a username to the “/” page and submit a POST request to “/create-user” upon a button click 4. Add the “/create-user” route, parse the incoming data (i.e., the username), and simply log it to the console. ~ END OF INSTRUCTION ~ Change Log Version Change Date 29 30 31

Use Quizgecko on...
Browser
Browser