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.
Node.js
npm
Packages and Modules
API (Application Programming Interface)
Express.js
Route
Route Parameters: Path vs. Query
We use npm
(Node Package Manager) to install and manage packages (many of which are modules)
A package is any project with a package.json
file
npm init
package.json
, use npm install
npm install <module-name>
, npm will automatically add the module as a dependency to the current package.json
and add the module to node_modules
When sharing your project, you provide package.json
but not node_modules
An application programming interface (API) is a communication protocols that allows two pieces of software to communicate.
A Web API (or web service) is a set of pre-defined URLs with parameters that allow a user to get information from a web server. Can be used by many types of clients.
Node.js is the runtime environment to execute JavaScript on the server
We have access to HTTP functionality, the file system, database interaction, etc. that we wouldn't have in the browser (the client)
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.
"use strict";
// 1. Load required modules
const express = require("express");
const app = express();
// 2. Add routes and other middleware and functions here
// 3. Start the app on an open port!
const PORT = process.env.PORT || 8000;
app.listen(PORT);
Remember to use nodemon app.js
to run your app on localhost:8000
and "mon"-itor any changes in your file so you don't have to restart the server each time.
Routes are used to define endpoints in your web service
Express supports different HTTP requests - we will use GET and POST, but there are also PUT and DELETE
Express will try to match routes in the order they are defined in your code
Name | Description |
---|---|
req.params | Endpoint "path" parameters from the request |
req.query | Query parameters from the request |
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) {
let state = req.params.state; // wa
let city = req.params.city; // Seattle
// do something with variables in the response
});
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
});
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;
res.type("text");
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);
}
});
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 (preferred) |
res.sendStatus() | Sets the response status code and sends 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 use 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!');
});
app.get("/hello", function (req, res) {
// res.type("json");
// res.send({ "msg" : "Hello world!" });
res.json({ "msg" : "Hello world!" }); // same as above
});
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.
For example, we will use the express.static
middleware function to specify the default directory of "static" client-facing files (HTML, CSS, client-side JS, images, etc.)
Every middleware function has access to the request and response objects, and usually modifies the response to send.
You can add multiple middleware functions used in a single request-response cycle with app.use
, but the last (usually app.get
) must close the connection (e.g. with res.send
) to prevent the
connection to be left hanging.
This is a great resource covering middleware more in Express
const express = require("express");
const app = express();
// defining middleware
// one middleware
function logger(req, res, next) {
let requestTime = new Date();
console.log("You sent a request at " + requestTime + "!");
next(); // continue
}
// a second middleware
function hello(req, res, next) {
res.write("Hello \n"); // use write when you want to output, but don't want to end
next();
}
function bye(req, res, next) {
res.write("Bye \n");
res.end();
}
// using middleware
app.use(logger);
app.get("/hello", hello, bye);
const PORT = process.env.PORT || 8000;
app.listen(PORT);
Code: middleware-example.js
app.js
)require("express")
)const app = express();
What if we want to fetch from our app from our HTML/CSS/JS files?
If a project is "full-stack" and contains both client-side "static" files along with your Node.js files, you can
use the express.static
middleware to specify the directory serving static files
The convention is to put your static files in a public
directory, in the same location as your Node.js file
"use strict";
const express = require("express");
const app = express();
app.use(express.static("public"));
// now "/" points to "public/" so we can visit "localhost:8000/greeter.html"
const PORT = process.env.PORT || 8000;
app.listen(PORT);
Demo'd "full-stack" example with our greeter API: greeter.zip
Let's write an API that takes GET requests to get information about CSE154 TAs!
tas/
: Send back JSON response with array of TA names
tas/:section
: Send back plain text response with TA name for given section code (e.g. "AA" for "Tal")
sections/
: Send back JSON response with array of section codes
sections/:ta
: Send back plain text response with section code for given TA (e.g. "Tal" for "AA")
Solution: app.js
The TA API had a constant SECTIONS to map sections to TA names
const SECTIONS = {
"AA" : "Tal",
"AB" : "Hudson",
"AC" : "Sven",
"AD" : "Manny-Theresa"
};
What if we wanted to have multiple SECTION objects for different quarter offerings of CSE 154?
What if we wanted to use the same data across different JS programs?
Often, it's best to factor out data we use in web services using files or databases. We'll start with files.
Unlike the browser, we have access to the file system when running Node.js
We can read all kinds of files, as well as write new files
Next week, we will learn how to process directories as well
fs
Core ModuleWe saw http
as our first core module in Node.js to create a basic HTTP server - this is what the npm module Express simplifies
Another useful Core Module we get in Node is fs
(file system)
There are many functions in the fs
module (with excellent documentation)
Most functions rely on error-first callbacks
fs.readFile(fileName, encodingType, callback)
fileName
(string) file nameencodingType
file encoding (usually "utf8")callback
"error-first" function that takes two arguments: an Error object (undefined if no error) and the result file contents (e.g. string text)fs.readFile("example.txt", "utf8", (err, result) => {
if (err) {
console.error("Something went wrong when reading the file...");
} else {
let lines = result.split("\n");
console.log("First line contents: " + lines[0]);
}
});
Example code: file-reading.js
You can read any file, including JSON. To parse JSON file contents and use as a JS object, use JSON.parse
(works similarly to .json()
you've used in fetch
).
// Example reading/parsing JSON
fs.readFile("package.json", "utf8", (err, result) => {
if (err) {
console.error(err);
} else {
let data = JSON.parse(result);
console.log(data);
}
});
To write a file, use the fs.writeFile(fileName, data, callback)
function.
This function's callback
is also error-first, but only takes an error
argument
fs.writeFile("new-file.txt", "Hello!", (err) => {
if (err) {
console.error("Something went wrong when writing the file...");
} else {
console.log("new-file.txt written to successfully!");
}
});
Example code: file-writing.js
One of the advantages of Node.js is its efficiency to handle many requests
This is possible through an "event loop" - the event loop is also what your browser uses to handle asynchronous functions like setTimeout
However, this can make it difficult to write code with asynchronous functions that are dependent on another asynchronous function
We'll learn some techniques to handle this easily on Monday!