Skip to main content

Prelab 5: Node.js

due February 26th at 10:00am ET

Setup

  • Accept this Github Classroom assignment and clone this repo that contains stencil code for this prelab.
  • Introduction

    In this prelab, you will learn how to write a basic Node.js server and how to manage your dependencies using npm. Make sure you have Node installed (node --version should give you the version number of node that is installed on your machine. See Assignment 0 for installation instructions if you do not see a version in your output).

    Accessibility/Ethics Readings (Optional)

    For the upcoming lab, please take a look at the article Privacy Policies: How Websites Track You, Explained.

    Overview

    Taken from Wikipedia:
    Node.js is an open-source, cross-platform, JavaScript runtime environment that executes JavaScript code outside of a browser. Node.js lets developers use JavaScript to write command line tools and for server-side scripting —- running scripts server-side to produce dynamic web page content before the page is sent to the user's web browser . Consequently, Node.js represents a "JavaScript everywhere" paradigm, unifying web-application development around a single programming language, rather than different languages for server-side and client-side scripts.

    What concerns us in this quote is that Node.js can evaluate Javascript code outside a browser, just like a Python interpreter. This enables us to write all kinds of applications using Javascript Syntax, including what most relevant to this course -- the server for a web application.

    In assignment2 you have written Javascript code that is interpreted by the browser and sends requests. If you have experience with other programming languages, you might have heard or used server-side web-application frameworks like Django and Flask for Python, Ruby on Rails for Ruby, Spring Framework for Java...

    In this prelab, we will cover:

    Node.js

    Writing Your First Node.js Application

    Create a new directory for this prelab and a new file inside this directory. Name the file anything you want, for example, server.js .

    Add the following line to the file:

                                    
    console.log('Hello, world!');
                                

    In the terminal, you can run this application with node server.js and you should see the phrase Hello, world! printed to the command line!

    If you type node in your terminal and click enter, you can access the Node REPL.

    Note the string 'Hello, world!' is printed to the terminal instead of your browser's debugger. In fact, console is a global Console class instance configured to write to process.stdout and process.stderr . Node.js actually provides the console module which provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

    Modules

    Just like any other programming languages, Node becomes much more powerful when you start using modules. In the Node.js module system, each file is treated as a separate module and Node.js has a set of built-in modules.

    For example, let us use the http module to create a HelloWorld server. Copy the following code from Node.js About page to your file:

                            
    const http = require('http');
    
    const hostname = '127.0.0.1';
    const port = 3000;
    
    const server = http.createServer((req, res) => {
      res.statusCode = 200;
      res.setHeader('Content-Type', 'text/plain');
      res.end('Hello World');
    });
    
    server.listen(port, hostname, () => {
      console.log(`Server running at http://${hostname}:${port}/`);
    });
                        

    Use node server.js to run your server, access the printed address in your browser, you should see a plain Hello World page.

    Use CTRL+C to stop your server.

    We will cover more about web server in the Express section, now let us focus on the first line
    const http = require('http'); .

    Importing with require

    Node.js has a built-in function require that are used to include modules in your current module. These imported modules can be built-in modules like http , external modules downloaded by package managers in the node_modules directory, or local modules you wrote.

    Therefore, the first line will perform a search, find the built-in http module, and assigns the exports (explained soon) object to the variable http .

    The variable name could be anything you want.
    Moreover, since the variable is bound to the returned exports object, you can use Destructuring assignment to directly bind exported fields to variable names like:

                            
                                const { createServer } = require('http');
    
                                const server = createServer((req, res) => {
                                res.statusCode = 200;
                                res.setHeader('Content-Type', 'text/plain');
                                res.end('Hello World');
                                });
                        

    Exporting with module.exports

    Before a module's code is executed, Node.js will wrap it with a function wrapper that looks like the following:

                                
                                (function(exports, require, module, __filename, __dirname) {
                                // Module code actually lives in here
                                });
                            

    By doing this, Node.js achieves a few things:

    • It keeps top-level variables (defined with var , const or let ) scoped to the module rather than the global object.
    • It helps to provide some global-looking variables that are actually specific to the module, such as:

      • The module and exports objects that the implementor can use to export values from the module.
      • The convenience variables __filename and __dirname , containing the module's absolute filename and directory path.

    module.exports is what require function actually returns when a module is imported. Since module.exports is initially aliased to exports (sharing a reference), we can reate another module (file) called, for example, config.js and exposes the hostname , port variables using either

    • exports
                                          
      exports.hostname = '127.0.0.1';
      exports.port = 3000;
                                      
    • module.exports
                                          
      module.exports = {
        hostname: '127.0.0.1',
        port: 3000
      };
                                      

    Then in the server.js , you can use const { hostname, port } = require('./config.js'); to import both variables. Note relative filepath is used rather than only the filename when importing local modules. This signifies that you are importing a local module instead of a built-in module or downloaded module.

    We do not mandate one way over the other. Stylistically speaking, using module.exports might lead to better structured code since all exported variables are gathered together. You can read more on the difference between module.exports and exports from this StackOverflow post .

    NPM

    If you have prior knowledge with package managers like pip for Python, Homebrew for MacOS, it is not hard to imagine how downloading external packages can be tremendously helpful. NPM is a package manager for Node.js modules which gets installed together with Node.

    You can use npm to install modules from npm's online registry. For example, instead of using those VSCode extensions, you can directly install the linters as packages: ESLint , StyleLint , HTMLHint .

    To track downloaded npm packages, it is often a good idea to creating a package.json file . You might have noted we have a package.json file in most of our Github repo. This allows you to use npm install to download all the dependencies of the stencil without actually store the dependencies concretely in the repo.

    Type npm init in your terminal at the directory created for this prelab. Feel free to click Enter to accept the defaults. After the interactive prompt is finished, you will find a package.json file in your directory.
    You can use npm init -y to create a default package.json directly.

    Now let us try install some modules:

    npm install will not only install the correct module into the node_modules directory, but also store the metadata like package name and version in the package.json file. For this reason, including the package.json file in Git repo or handin is extremely important because it allows other developers to replicate your project by installing all required dependencies themselves rathern than transferring all the dependencies over the network.

    You should never include the node_modules directory in your handin. The package.json and package-lock.json files are suffcient for us to install all dependencies in your handins.

    Express

    Basic Routing

    The server you created with http is very basic, it neither scales well nor provides commonly required functionalities like cookies, params, template renderings... To build a full-fledged web application server, we will introduce Express .

    Replace your server.js with the following code taken from Hello world example in Express.

                            
    const express = require('express')
    const app = express()
    const port = 3000
    
    app.get('/', (req, res) => res.send('Hello World!'))
    
    app.listen(port, () => console.log(`Example app listening on port ${port}!`))
                        

    Run the server with node server.js , go to the address at http://localhost:3000 and you should see Hello World being printed out.

    • At line 2, we are creating an express app. Think this as an instance of the express server.
    • At line 7, we tell the express app to listens on the port 3000 for connections. Since we are running the server locally and, in computer networking, localhost is a hostname that means this computer, we can access the express app at http://localhost:3000 .
    • Try a little experimentation, what if you access a specific endpoint (route) like http://localhost:3000/random , what will your express server respond?

      At line 5, we register the root route / in the express app. More specifically, we are telling the express app that if the user visits the root route via GET method, execute the callback which is the second argument.
      Remember visiting the URL automatically triggers a GET request and visiting http://localhost:3000 is the same as visiting http://localhost:3000/ .

      Since only the root route is registered, the express app does not know how to handle other routes like /random . By default, it will respond with a 404 Not Found. If you open the browser inspector Network tab and refresh the page, you can see the RESPONSE status is 404 Not Found .

      app.get means the express application will respond to the client request at the specified endpoint (the first argument which is often called route ) using the specified callback (the second argument which is often called handler ) when GET request method is used.
      Similarly, you can use register handlers for other HTTP request method like POST .

                                         
      app.post('/', function (req, res) {
          res.send('Got a POST request');
      });
                                      

      Restart your server and you can simulate a POST request from terminal using curl:
      curl --request POST --url http://localhost:3000/

    Route Paths

    Besides specifying a string for the route registration, you can also use string patterns and regular expressions to specify a set of routes.
    For example, we can use the wildcard * to match all routes:

                           
    app.get('*', (req, res) => res.sendStatus(404));
                        

    * means matching all. Therefore, the registered callback will be executed for every route if it is not already handled.

    Add this line after your registration for GET, POST methods on the root routes. The order is significant, try putting this line before, restart the server and observe what happens when you visit the root route.
    If we put this catchall route earlier than specific routes, every route is first caught by the catchall handler which terminates processing and the callbacks at specific routes will never be triggered.
    By registering the catchall handler last, we are matching the remaining routes.

    res.sendStatus is a method on the response object (the second argument on the callback) that set the response status code and send its string representation as the response body.

    Tasks:

    Serving static files in Express

    Web applications usually have static files such as images, CSS files, and JavaScript files. You do not need to manually create a route for each file, rather, you can host an entire static directory with express.static .

    1. Create a public directory
    2. Within the public directory, create a images directory
    3. Put your favorite image inside the images directory. For example, I used a ratatouille photo .
    4. Add the following code to serve images, CSS files, and JavaScript files in a directory named public: app.use(express.static('public'))
    5. Restart the server and go to the corresponding route, for example, I put a ratatouille.jpg in public/images and I can access it at http://localhost:3000/images/ratatouille.jpg .

    You might have noticed we used app.use rather than a specific HTTP method name like app.get . app means the effect is application-level: we are specifying something global here and use means a middleware is used as the callback function.

    We only covered the basics of Express, please read guide on Express for more information and use API Reference to look for specific functions.

    This is the end of the prelab! We have only covered the basics for Node.js so if you're confused about anything, feel free to look at official documentations or come to TA Hours.

    Handin Instructions

  • To hand in your code for prelab 5, commit and push your changes to your cloned GitHub Classroom repository's main branch.
  • Submit it to Gradescope via GitHub upload. You must submit your prelab to Gradescope or we'll not be able to grade your submission.