1 — Not a browser
Welcome and Tech Check
We’ll get set up and ensure everyone has the required software installed.
Process, Arguments, Input and Output
In Node.js, process is a global that provides information about your running Node program. Think of it as the server-side equivalent to a web browser’s
Many programming languages have a single “entry” point function (often called
EXERCISE: Magic 8 Ball
Write a Node.js program that prompts the user for a question, and responds with a random answer. If the user provides something that doesn’t seem like a question, your program should use
process.stderr to tell the user their input is invalid, and allow them to try again.
Async Control Flow
While promises and
async/await have become the preferred way to manage asynchronous jobs, nearly all of the asynchronous functions in Node.js core libraries require a callback to be passed as an argument. We’ll compare different ways of managing concurrency, and hone in on today’s best practices.
Modern versions of Node.js include a
promisify utility function, which converts a node-callback-style function into a promise-emitting function. However, as a workshop instructor, I need to be able to support Node versions that don’t come with this! Write me a
promisify, and make sure that errors are handled properly.
The REPL and Debugging
The Node Read-Eval-Print-Loop (REPL) allows developers to execute statements in an interactive environment. We’ll learn how to make the REPL work for you, and a popular workflow involving prototyping code in isolation before bringing it into your program.
EXERCISE: Bug Hunt
You’ll be given a program with a bug that slipped by all of our static code analysis. Set up a Visual Studio Code debugging configuration so that you can “launch” the program from within the editor. Use your knowledge of conditional breakpoints and restarting stack frames to track down the malfunction.
Break for lunch
2 — Standard Library
Buffers, Files and Paths
Two of the most widely-used Node.js APIs are
fs, used to interact with the filesystem, and
path, used to construct proper resource paths that work on all operating systems. We’ll look at basic tasks like reading and managing files; how the “current directory” is affected by the location of our source code; and
fs-extra, a popular open source library that provides a range of commonly-needed filesystem tools beyond those that ship with Node.
EXERCISE: Directory-to-JSON and back again
You have been presented with a hierarchy of text files and folders. Build a
filesToJsonfunction that, given the path to a folder, generates a single JSON object representing the contents (and placement) of the files therein. Then, build a
jsonToFiles function that takes your JSON object along with a folder path, and write the contents to disk as the appropriate hierarchy.
A large portion of the Node api is built around the idea of “event emitters” to allow the registration of “listeners” (functions), which are invoked whenever an event is fired. We’ll learn about creating and consuming our own
EventEmitter, and learn when it is appropriate to respond to events synchronously or asynchronously.
EXERCISE: Operating an Oven
We’ll write some code to control an oven so that we can bake some Node-powered cookies! Your job is to build an
EventEmitter that allows the operator of the oven to do their job in response to the
Streams are a particular type of collection, where data may not be available all at once. When working with a stream of data, we can start immediately processing whatever we have so far, without requiring that “everything” is placed in memory. While it is somewhat uncommon for developers to create new types of streams, you’ll end up working with them extensively as we get deeper into Node.
EXERCISE: Oven Thermocouple
You’ll be provided with some code that represents a kitchen oven. It is set to a “desired temperature”, the heater turns on, and a temperature sensor lets us know when the oven is ready. However, we have a problem… the temperature sensor is cheap and produces a garbage reading sometimes.
Write a program to consume the raw data stream from an oven’s temperature sensor to control the heating system, in order to keep the oven temperature pretty close to what the user set it at. You’ll need to use a
Transform stream, and expose a stream for the oven’s heater to “listen” to.
We’ll recap what we have learned so far, and outline the topics to be covered tomorrow. Homework may be assigned.
3 — Networking
Node comes with some great foundational support for both low and high-level networking. We will begin with writing a program that manages a TCP socket directly, then upgrade it to a turnkey HTTP server to build a simple web application.Agenda
TCP/IP is at the core of how the internet works (After all, the “IP” stands for “internet protocol”). The TCP (Transmission Control Protocol) ensures that when devices receive data, they are in the correct order and not missing any data. We’ll learn about how to open a port and communicate over it using Node’s
EXERCISE: Guess my Number
Build a small Node program that opens up a port on localhost, generates a random number between 0 and 100, and then asks the user to guess it. The user should receive some feedback on their guess (too high, too low), and once they find the correct number, the socket should be closed.
Please guess my number. It is between 0 and 100 10 Too Low! Try again 50 Too High! Try again 30 Too High! Try again 20 GOT IT! The number was 20 and you guessed in 4 tries
HTTP (Hypertext Transfer Protocol) is an application networking protocol that describes concepts like URLs, request methods (GET, POST, PUT, etc…) and more! We’ll learn about Node’s fantastic support for HTTP servers and clients, and walk through typical usage patterns.
EXERCISE: Hello, Web!
We’ll build an HTTP-based version of our number guessing game from the previous exercise. After our work is done, players will be able to play the game using a web browser instead of their terminal.
4 — Processes and Clustering
Node applications run on a single thread, but they can start new processes and communicate with them to form a distributed system. We will look at several use cases for doing something like this, and study how the Actor Concurrency Model helps keep inter-process communication simple and cheap.Agenda
Sometimes we need to run a shell command and observe its output. We’ll study the various functions available in the
child_process Node module in detail, focusing on things like:
- Passing arguments
- Monitoring output
- Detecting successful execution
EXERCISE: Running shell commands
Build a class that can retrieve information about the hardware your program is running on. These are typically not available to a Node application and involve running OS-specific shell commands.
App as a Cluster
In a production environment, running an app on a single threads is dangerous! All it takes is one rogue function to leak, and you have put everything you program can do in jeopardy. We’ll look at how a single script can be “forked” onto multiple processes to form a cluster, leaving the master process available to take on the next task.
EXERCISE: Clustered HTTP server
Upgrade our HTTP server exercise to be run on a pool of worker processes (one per CPU core on your machine), to increase the maximum amount of traffic your program can potentially handle.
Break for lunch
5 — Express
Express is, by a fairly large margin, the most popular web application framework for Node.js. It can be used to respond to HTTP requests with JSON, HTML, or pretty much anything else.Agenda
Express is built on top of Node’s networking concepts, leveraging the power of the
HTTP server we have been using, and the existing
Response types. We’ll take some time to learn more about how
EXERCISE: A JSON API resource
Build a set of Express request handlers for creating, listing, updating and destroying a “course” resource.
EXERCISE: Course as HTML
Build a set of new routes for CRUD (Create, Read, Update, Delete) operations on the same course object we modeled in the last exercise — but render responses as HTML instead of JSON.
Once we start responding to a variety of URLs, we’ll want to start breaking our code up into independent modules. Each module should follow SRP (Single Responsibility Principle) for handling a specific concern, or set of closely-related concerns.
EXERCISE: JSON + HTML
Time for us to combine HTML and JSON. Use hierarchical routing to expose both of the last two exercises, such that:
http://localhost:3000/courses provides an HTML response, and
http://localhost:3000/api/courses provides a JSON response.
Middlewares are modular request/response handling units that can be composed together in a server. You could have one middleware that ensures requests are coming from only a specified origin; one that parses the body (text) into JSON for later use; one that logs the amount of time it took to generate a response; and more! We’ll build a few middlewares together, and try applying them broadly across our whole app, as well as specifically to a family of routes (or a single route).
EXERCISE: CORS headers
Build an Express middleware for CORS (Cross-Origin Resource Sharing) validation. When an incoming
request is received, the
Origin header should be validated against the
allowedOrigins list, and if everything checks out the appropriate CORS response headers should be set on the
Unit tests are fast, lightweight, and designed to validate algorithmic complexity in isolation. We’ll learn how to write our code in such a way that the trickiest parts are easily unit testable without having to build large numbers of “stubs”.
EXERCISE: Unit testing with Mocha
Build a handlebars helper function that formats a number nicely for analytics dashboards. You must write your unit tests to fulfill these requirements:
- 23004 should be represented as
- -914 should be represented as
- 1060241 should be represented as
Integration tests are designed to ensure interfaces or connections between components of your application work properly. We’ll learn how to write tests for two important “interfaces” to any Express app:
- Ensuring that URLs are handled by the appropriate route,
- Ensuring that routes pass the expected data to views.
EXERCISE: Integration testing with Mocha
Write an integration tests suite to ensure that the
GET /course view is passed the correct data to render its template.
Acceptance tests, sometimes called functional tests, are designed to ensure that critical user workflows work correctly and protect them from regression. Acceptance tests are the closest thing we have to simulating user behavior. They should be designed in such a way that they mimic what users may do. Acceptance tests usually involve starting up the entire app and are considerably slower than unit or acceptance tests. You should have a few of these, but the more you have, the slower your test suite will be.
EXERCISE: API acceptance testing with Mocha
Build acceptance tests for the
/api/course JSON Create, Read, Update and Destroy endpoints.
EXERCISE: HTML acceptance testing with Mocha
Build acceptance tests for the
/course HTML Create, Read, Update and Destroy endpoints.
Wrap Up and Recap
We’ll recap everything we have learned throughout the course, and discuss resources for future learning.