Introduction to Module 4 and Node.js!
Web service: software functionality that can be invoked through the internet using common protocols
It's like a remote function(s) you can call. Done by contacting a program on a web server
Merriam-Webster Dictionary API
NASA APOD API
Trivia API
CSE154 web services:
https://server/path/file
Usually when you type a URL in your browser:
Some URLs actually specify programs that the web server should run, and then send their output back to you as the result:
https://courses.cs.washington.edu/courses/cse154/webservices/pokedex/pokedex.php?pokedex=all
The above URL tells the server courses.cs.washington.edu
to run the
program pokedex.php
and send back its output
Servers are dedicated computers for processing data efficiently and delegating requests sent from many clients (often at once).
These tasks are not possible (or appropriate) in the client's browser.
Server-side programs are written using programming languages/frameworks such as PHP, Java/JSP, Ruby on Rails, ASP.NET, Python, Perl, and JS (Node.js)
Web servers contain software to run those programs and send back their output.
So far, we have used JS on the browser (client) to add interactivity to our web pages
"Under the hood", your browser requests the JS (and other files) from a URL resource, loads the text file of the JS, and interprets it realtime in order to define how the web page behaves.
In Chrome, it does this using the V8 JavaScript engine, which is an open-source JS interpreter made by Google. Other browsers have different JS engines (e.g. Firefox uses SpiderMonkey).
Besides the standard JS language features, you also have access to the DOM when running JS on the browser - this includes the window
and document
Node.js uses the same open-source V8 JavaScript engine as Chrome
Node.js is a runtime environment for running JS programs using the same core language features, but outside of the browser.
When using Node, you do not have access to the browser objects/functions (e.g. document, window, addEventListener).
Instead, you have access to functionality for managing HTTP requests, file i/o, and database interaction.
This functionality is key to building REST APIs!
When you have Node installed (last week's section), you can run it immediately in the command line.
node
(no arguments). This REPL is much like the Chrome browser's JS console tab.node file.js
There are a few steps to starting a Node.js application, but luckily most projects will follow the same structure.
When using Node.js, you will mostly be using the command line (e.g. php-ide-terminal
you should have installed in Atom).
node-practice
)npm init
to initialize a package.json
configuration file (you can keep pressing Enter to use defaults)npm install <package-name>
app.js
)public
directory within the project.Along the way, a tool called npm
will help install and manage packages that are useful in your Node app.
Running npm init
to create package.json
Running npm init
to create package.json
app.js
You can write and execute JS using Node in the command line
When you run a .js
file using Node.js, you have access to default functions in JS (e.g. console.log
)
In order to get functionality like file i/o or handling network requests, you
need to import that functionality from modules - this is similar to the import
keyword you have used in Java or Python.
In Node.js, you do this by using the require()
function, passing the string name of the module you want to import.
For example, the built-in module to provide HTTP request/response functionality in Node.js is called http
. You can import it like this:
const http = require("http");
JS
Using const
to declare a variable inside of JS just means that you can never change what that variable references. We've used this to represent "program constants" indicated by ALL_UPPERCASE naming conventions
For example, the following code would not work:
const specialNumber = 1;
specialNumber = 2; // TypeError: Assignment to constant variable.
JS
When we store modules in Node programs, it is conventional to use const
instead of let
to avoid accidentally overwriting the module.
Unlike the program constants we define with const
(e.g. BASE_URL
), we use camelCase naming instead of ALL_CAPS.
The http
core module provides functionality for handling HTTP requests.
To get started with a simple server, we can write the following Node.js program:
"use strict";
const http = require("http");
let server = http.createServer((request, response) => {
console.log("I got your request!");
response.write("Hello from a Node Server!");
response.end();
});
const PORT = process.env.PORT || 8000;
server.listen(PORT);
JS
Try it! Save http-server-example.js
and then run node http-server-example.js
in the command line when your current
What do you see in the browser? What do you see in the command line console?
As we add endpoints and different types of requests to our APIs, the http
module can become very complicated. We will
instead use the most popular web service module to implement our Node.js APIs, which is built using the same http
module but with functionality that is much easier to use.
But unlike http
, Express is not a core module - it has been developed by a community of developers consistently updating it.
But how can we use modules like Express in our own code?
Node comes with built-in "core modules" you can require in your project without extra installation (http, fs, path, etc.)
But one of the appealing features of Node as a server-side technology is the extensive ecosystem of modules like Express to accomplish different tasks
npm (Node Package Manager), gives us an easy way to download, manage, and update all of these packages.
Node packages can be installed in two different ways, globally or locally.
Global packages are installed by adding a -g
flag in the npm install command and are accessible anywhere on your computer.
npm install -g <package_name>
command line
This is useful if a package contains terminal commands that you want to be used at any time, such as the programs you installed last week (nodemon
and ndb
).
It's best practice to use local installation for project-specific packages.
For example, we will use the Express module to build RESTful APIs in Node - each time we create a project, we will run the following within the project directory:
npm install express
npm will automatically add a dependency to the project's package.json
In addition to installing modules, npm will also automatically manage packages in your Node projects.
Whenever you install a package, npm will add it to package.json
and a node_modules
folder
These files should not be distributed with the rest of your project code (e.g. front-end files and app.js
- node_modules
can get very large
Instead of sending this node_modules
folder containing modules that already exist online, you send them an information file called package.json
that NPM uses to keep track of all of these packages.
When a user downloads your project and its package.json
file, they just run npm install
within the project directory, and npm will install all of those packages required to run the project.
The package.json
file is created by running npm init
which will prompt the user to answer a few questions about the project like the name and version.
Any future npm install <package_name>
commands that are run in the same project will be automatically added to this package.json
file.
A package-lock.json
file will also be created, containing a more detailed version of all of the packages used in a project and their dependencies.
Express.js is one of the most popular Node frameworks used to implement APIs
Serves as an easy-to-use wrapper around Node's more complex core networking modules.
Provides simple functionality through "middleware" to listen and respond to HTTP requests from clients at different endpoints.
Middleware is a term you'll see when working with Express - it refers to any function that works "in the middle" of communication between the client and Node.js.
Every middleware function has access to the request and response objects, and usually modifies the response to send.
express
module locallyTo use Express, you must install it in the project directory with npm install express
. This will automatically update package.json
to include the dependency, and
also create a node_modules
directory (other modules you might install will also be added to package.json
and node_modules
).
app.js
as an Express REST APIYou can write and execute JS using Node in the command line
To start the localhost server to run your Express app, you need to specify a port to listen to.
The express app object has a function app.listen
which takes a port number and optional callback function
At the bottom of your app.js
, add the following code - (process.env.PORT
is needed to use the default port when hosted on an actual server)
const PORT = process.env.PORT || 8000; // Allows us to change the port easily by setting an environment variable, so your app works with our grading software
app.listen(PORT);
Routes are used to define endpoints in your web service
Express supports different HTTP requests - we will learn GET and POST
Express will try to match routes in the order they are defined in your code
app.get(path, (req, res) => {
...
});
JS
app.get
allows us to create a GET endpoint. It takes two
arguments: The endpoint URL path, and a callback function for modifying/sending the response.
req
is the request object, and holds items like the request parameters.res
is the response object, and has methods to send data to the client.res.set(...)
sets header data, like "content-type". Always set either "text/plain" or "application/json" with your response.res.send(response)
returns the response as HTML text to the client.res.json(response)
Does the same, but with a JSON object.When adding a route to the path, you will retrieve information from the request, and send back a response using res (e.g. setting the status code, content-type, etc.)
If the visited endpoint has no matching route in your Express app, the response will be a 404 (resource not found)
Name | Description |
---|---|
req.params | Endpoint "path" parameters from the request |
req.query | query parameters from the request |
Name | Description |
---|---|
res.write(data) | Writes data in the response without ending the communication |
res.end() | Ends the process |
res.send() | Sends information back (default text with HTML content type) |
res.json() | Sends information back as JSON content type |
res.set() | Sets header information, such as "Content-type" |
res.type() | A convenience function to set content type (use "text" for "text/plain", use "json" for "application/json") |
res.status() | Sets the response status code |
res.sendStatus() | Sets the response status code with the default status text |
By default, the content type of a response is HTML - we will only be sending plain text or JSON responses though in our web services
To change the content type, you can use the res.set
function, which is used to set response header information (e.g. content type).
You can alternatively uses res.type("text")
and res.type("json")
which are equivalent to setting text/plain
and application/json
Content-Type headers, respectively.
app.get('/hello', function (req, res) {
// res.set("Content-Type", "text/plain");
res.type("text"); // same as above
res.send('Hello World!');
});
Setting plain text response
app.get('/hello', function (req, res) {
// res.set("Content-Type", "application/json");
rese.type("json");
res.send({ "msg" : "Hello world!" });
// can also do res.json({ "msg" : "Hello world!"});
// which also sets the content type to application/json
});
Setting JSON response content type
When you add a route in your Express app (here, we use '/' for the most basic "root" route), you can use res.send
to send a response to the client (browser) and view using the port (here, 8000 on localhost)
You can add more routes in your file, and Express will use the first one matching the request path
Act as wildcards in routes, letting a user pass in "variables" to an endpoint
Define a route parameter with :param
Route path: /states/:state/cities/:city
Request URL: http://localhost:8000/states/wa/cities/Seattle
req.params: { "state": "wa", "city": "Seattle" }
These are attached to the request object and can be accessed with req.params
app.get("/states/:state/cities/:city", function (req, res) {
res.type("text");
res.send("You sent a request for " + req.params.city + ", " + req.params.state);
});
JS
You can also use query parameters in Express using the req.query
object, though they are more useful for optional parameters.
Route path: /cityInfo
Request URL: http://localhost:8000/cityInfo?state=wa&city=Seattle
req.query: { "state": "wa", "city": "Seattle" }
app.get("/cityInfo", function (req, res) {
let state = req.query.state; // wa
let city = req.query.city; // Seattle
// do something with variables in the response
});
JS
Unlike path parameters, these are not included in the path string (which are matched using Express routes) and we can't be certain that the accessed query key exists.
If the route requires the parameter but is missing, you should send an error to the client in the response.
The Response object has a status
function which takes a status code as an argument.
The 400 status code is what we'll use to send back an error indicating to the client that they made an invalid request.
A helpful message should always be sent with the error.
app.get("/cityInfo", function (req, res) {
let state = req.query.state;
let city = req.query.city;
if (!(state && city)) {
res.status(400).send("Error: Missing required city and state query parameters.");
} else {
res.send("You sent a request for " + city + ", " + state);
}
});
JS
app.js
)require(“express”)
)const app = express();