Just Express (with a bunch of node and http). In detail.
- General Reference
- Helpful Prerequisite Knowledge
- Express is just a node module
- Connecting to the cloud (i.e., what "the cloud" is)
- Data packets
- TCP and UDP (brief overviews)
- HTML, HTTP, and the stateless nature of HTTP
- HTTP messages (start line, header, body; request/response)
- Start Line
- Node server without Express (the basics/fundamentals)
- Node server without Express (routing and serving up static files)
- Comparing Express and Plain Node: Simple Server, Routing, etc.
- Middleware Basics
- Rendering Basics
- Express usage: API development and/or server-side rendering (
res.json
and/orres.render
, respectively) - View Engines
- Rendering
- A preface about rendering in general
- A note about
res.locals
and passing data inres.render
- Passing data that we trust (example of unescaping HTML using EJS)
- Include other files for more robust templating (example using EJS)
- Adding styles, scripts, etc., to template files (properly setting
href
,src
, etc.) - Basic examples (using EJS, Handlebars, and Pug)
- Express usage: API development and/or server-side rendering (
- Request and Response Objects: Revisited
- Forms: getting data from the
request
object - Cookies: using
res.cookie
,res.clearCookie
, and other cookie-related information - Redirects: the optional
status
value inres.redirect([status,] path)
- Passing data: the query string (using
req.query
) - Passing data: parameters via URL wildcards (using
req.params
andapp.param()
) - Passing data: sending files (via
res.download
) and dealing withheaders already sent
error
- Forms: getting data from the
- Miscellany: Advanced Routing, Express Generator, HTTP Headers, etc.
General Reference
Purpose of Express
The main job for Express is to manage HTTP traffic (i.e., manage how the request and response go back and forth). Hence, it makes sense to first talk about what HTTP even is and that relies in part on understanding TCP (transmission control protocol) and UDP (user datagram protocol).
API reference outline (4.x)
- express()
- Application
- Request
- Properties
- Methods
- Response
- Properties
- Methods
- Router
Helpful Prerequisite Knowledge
Express is just a node module
Express is "just" a node module. Literally, you can install Express via NPM because Express is literally just a node module. The Express website makes clear what Express is:
Express is a minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications.
It also makes clear how, implicitly or explicitly, the main job for Express is to manage HTTP traffic via methods on the request
and response
objects:
With a myriad of HTTP utility methods and middleware at your disposal, creating a robust API is quick and easy.
Connecting to the cloud (i.e., what "the cloud" is)
The cloud is not a cloud but just a network of computers (not belonging to you) talking with each other where the language they speak is in "packets" where these packets are little streams of data. See the Wiki entry on cloud computing (first sentence):
Cloud computing is the on-demand availability of computer system resources, especially data storage (cloud storage) and computing power, without direct active management by the user.
Some other relevant terminological excerpts related to the above excerpt:
- Cloud storage: Cloud storage is a model of computer data storage in which the digital data is stored in logical pools, said to be on "the cloud". The physical storage spans multiple servers (sometimes in multiple locations), and the physical environment is typically owned and managed by a hosting company. These cloud storage providers are responsible for keeping the data available and accessible, and the physical environment protected and running. People and organizations buy or lease storage capacity from the providers to store user, organization, or application data. Cloud storage services may be accessed through a colocated cloud computing service, a web service application programming interface (API) or by applications that utilize the API, such as cloud desktop storage, a cloud storage gateway or Web-based content management systems."
- Pool (computer science): In computer science, a pool is a collection of resources that is kept ready to use, rather than acquired on use and released afterwards. In this context, resources can refer to system resources such as file handles, which are external to a process, or internal resources such as objects. A pool client requests a resource from the pool and performs desired operations on the returned resource. When the client finishes its use of the resource, it is returned to the pool rather than released and lost. The pooling of resources can offer a significant response-time boost in situations that have high cost associated with resource acquiring, high rate of the requests for resources, and a low overall count of simultaneously used resources. Pooling is also useful when the latency is a concern, because a pool offers predictable times required to obtain resources since they have already been acquired. These benefits are mostly true for system resources that require a system call, or remote resources that require a network communication, such as database connections, socket connections, threads, and memory allocation. Pooling is also useful for expensive-to-compute data, notably large graphic objects like fonts or bitmaps, acting essentially as a data cache or a memoization technique. Special cases of pools are connection pools, thread pools, and memory pools.
Data packets
The data interchange between client and server happens through packets of data called network packets. Relevant Wiki excerpts provided below:
- Network packet: In telecommunications and computer networking, a network packet is a formatted unit of data carried by a packet-switched network. A packet consists of control information and user data; the latter is also known as the payload. Control information provides data for delivering the payload (e.g., source and destination network addresses, error detection codes, or sequencing information). Typically, control information is found in packet headers and trailers. In packet switching, the bandwidth of the transmission medium is shared between multiple communication sessions, in contrast to circuit switching, in which circuits are preallocated for the duration of one session and data is typically transmitted as a continuous bit stream.
- Payload: In computing and telecommunications, the payload is the part of transmitted data that is the actual intended message. Headers and metadata are sent only to enable payload delivery. In the context of a computer virus or worm, the payload is the portion of the malware which performs malicious action. The term is borrowed from transportation, where payload refers to the part of the load that pays for transportation.
When you deal with Express, you (i.e., the server) are now put in charge of handling what packets of data people (i.e., the client or browser) receive:
So you are now in charge of effectively serving up that content. Node does most of the heavy lifting via low-level C. As can be seen above in the illustration, a "data packet" consists of 5 parts or layers. From the lowest-level to the highest-level, the following are the layers of each packet:
- Physical: Cables. These are the actual physical cables connecting things together. See the submarine cable map for more details and the end of this note for two visuals (the map of cables and a randomly chosen specific cable).
- Link: Wifi or ethernet connection
- Internet/Network: IP (Internet Protocol)
- Transport: UDP/TCP
- Application: HTTP, FTP, SSH, SMTP
The following are more specific examples from Wiki:
- Application layer: FTP, HTTP, HTTPS, SSH, NTP, etc.
- Transport layer: TCP, UDP, DCCP, SCTP, etc.
- Internet layer: IP (IPv4 and IPv6), ICMP, ICMPv6, ECN, etc.
- Link layer: MAC (Ethernet, Wi-Fi, DSL, ISDN, FDDI), NDP, ARP, etc.
The network and transport layers together form the internet protocol suite or TCP/IP.
Wiki note on Internet protocol suite |
---|
The Internet protocol suite is the conceptual model and set of communications protocols used in the Internet and similar computer networks. It is commonly known as TCP/IP because the foundational protocols in the suite are the Transmission Control Protocol (TCP) and the Internet Protocol (IP). During its development, versions of it were known as the Department of Defense (DoD) model because the development of the networking method was funded by the United States Department of Defense through DARPA. Its implementation is a protocol stack. The Internet protocol suite provides end-to-end data communication specifying how data should be packetized, addressed, transmitted, routed, and received. This functionality is organized into four abstraction layers, which classify all related protocols according to the scope of networking involved. From lowest to highest, the layers are the link layer, containing communication methods for data that remains within a single network segment (link); the internet layer, providing internetworking between independent networks; the transport layer, handling host-to-host communication; and the application layer, providing process-to-process data exchange for applications. The technical standards underlying the Internet protocol suite and its constituent protocols are maintained by the Internet Engineering Task Force (IETF). The Internet protocol suite predates the OSI model, a more comprehensive reference framework for general networking systems. |
Developers generally spend the majority of their time in the application, transport, and internet layers, with most time being spent specifically in the application layer. Express only handles HTTP requests but it's important to note that the HTTP application layer uses the transport layer and specifically TCP instead of UDP.
Submarine cable map
Here is a picture of the entire cable map as of March 27, 2020:
Particular cable from the submarine cable map
Here is a specific cable from the submarine cable map (note it is 11,000km in length and where its landing points are):
TCP and UDP (brief overviews)
You have a computer with an internet connection. The transport layer creates 216 = 65,536 ~ 65k ports on your computer. Whenever you start a Node app, say on port 3000, the reason you have that port 3000 at all is because you are using one of the 65k ports that the transport layer creates. If you started an app on port 5000 (like a Flask app or Rails app), then again you are using one of those ports.
Think of your network connection as being a hotel, where the hotel is a single building but with tons of individual rooms that are all numbered. If someone comes to the hotel, then in order to find a guest, they need to know the room number. Armed with the room number, they can actually find who they are looking for.
Typically what happens is an application of a given machine will issue a network request. Suppose this request is an HTTP request. And suppose the request originates from port 49742 (an arbitrary port of the 65k available). Let's suppose the request wants to talk with port 80 on another computer. That request will get handed off to the transport layer and that will get wrapped up in what's called a segment. Inside of the segment there will be metadata which will have the destination port (i.e., port 80) and the source report (i.e., port 49742). The transport layer will hand that off to the network layer for further processing. When it gets to the receiving machine, it will go through the process in reverse and eventually find the right port.
There are two different kinds of transport layer protocols: UDP and TCP. They can broadly be characterized in the following manner:
UDP (User Datagram Protocol)
Basically the win is that UDP is crazy fast but the loss is that it is incredibly unreliable. Since Express is based on HTTP and not UDP, Express will not have these faults (but it will also miss out on some of the positives). Here's the high-level view of UDP:
- Lightweight: Only 8 bytes for a header. Very little overhead required to work.
- Connectionless: If a client wants to talk with a server, then you do not have to create a connection first. You can go ahead and start sending data from the client without a connection to a server being established.
- Consistency: UDP is good and bad.
- Bad: UDP will send data no matter what. This may seem good on the surface but it can also be quite bad. What if there's packet loss? UDP doesn't care. It will keep right on sending packets. It doesn't make any difference. What if the network is very congested? It doesn't care. It will just keep right on sending packets and just make the network more and more congested. What if the packets are out of order? It doesn't care. It's not UDP's problem. They're just going to show up out of order--that will be the other side's problem.
- Good: Everything mentioned above is bad. So what is good about UDP? What's the win? It's blazing fast. It's very lightweight (the headers are incredibly small). You don't have to bother to set up a connection to start. You can just start sending data. It's consistent in how it sends data. Packets will always show up whether they are ordered properly or not.
- Use cases: UDP is primarily used for things like video games or real-time communication. If you have ever experienced "lag" in a video game where everything seems to stop or go back in time and then suddenly catch up...that's UDP. That's the client screaming at the server without making a connection. And suddenly the server updates your machine with "Whoops, you're actually way behind. I'm going to start sending some different data."
TCP (Transmission Control Protocol)
Connection-based: Unlike UDP, if you are a client and you want to start talking with a computer via TCP (i.e., if you are a browser and want to start communicating with a server), then you don't just start screaming and sending data as in UDP. You have to go through what's called a 3-way handshake (the "TLS Handshake" or "Transport Layer Security Handshake" you have probably seen before when waiting for a webpage to load). Before you are going to transmit any data, you are going to have to initiate a connection. The 3-way handshake goes like this:
- The client says, "Hey, I'd like to talk."
- The server responds with yes or no. Hopefully the server responds with yes and that it is happy to set up a connection.
- Actual data starts being transmitted.
These are the 3 steps that will happen before a TCP connection actually goes through.
Reliable: From the above, we can see TCP is reliable because we actually know the connection is going to happen before any data is transmitted. Additionally, for TCP, there are data acknowledgments. What this means is that every time data is transmitted the server will let the client know that it received the client's data and vice-versa. There's also retransmission of data in TCP. If data isn't received, then the server can let the client know (and vice-versa) that some data was not received and the client can send it again.
Ordered packets: With UDP, there may be packet loss or disorganized packets. With TCP, you can guarantee that the packets arrive in the correct order regardless of what happens with the network.
Congestion control: With TCP, if the network is overwhelmed, it may intentionally introduce latency to try and keep packet loss to a minimum to not make the problem worse.
The upshot of all of this is to use TCP when you need reliability and probably UDP when you need something fast and you don't need it to be reliable.
What you need to remember: TCP and IP, together, get two computers ready to talk with each other. They create an environment that will allow two computers to talk with each other. And HTTP uses TCP as the transport layer because it is reliable whereas UDP is not.
HTML, HTTP, and the stateless nature of HTTP
What do HTTP and HTML have in common? The first two letters: Hyper Text. Something fun to check out: info.cern.ch. This was the very first webpage that was ever made. It's not just HTML. It was the magic that was being able to get all of the networking happening together to be able to pass the HTML around. But HTTP doesn't just pass around HTML anymore. HTTP definitely still passes around HTML, but it also passes around images, 4k videos, etc.
Some highlights about HTTP as a protocol:
- Efficiency: It is incredibly efficient. TCP remains connected. It connects and then remains connected until all of the data has been sent. HTTP does not have to stay open. HTTP is only connected when absolutely necessary. Once the request arrives, the machines will disconnect entirely from each other. As soon as the responder is ready, the HTTP connection will reestablish across TCP and will send the response.
- Stateless: No dialogue. This means the machines only know about each other for as long as the connection is open. As soon as the connection closes, everything is completely forgotten. So if they need to talk again for any reason, they have to start over completely again, which is like to say it's the very first conversation (i.e., the 3-way handshake needs to occur: tell me who you are and what you want). It's not like a phone conversation where the first person says something (request) and listens for a response and there's a running memory throughout of what is being said back and forth (i.e., there's a history and different points that can be looked back to and referenced to inform and continue the dialogue). Stateless means, "I only know about what you just said right now. And I'm going to respond based on that and then completely forget everything." So the requestor gets one thing to say and the responder gets one thing to say and then they are totally done talking.
Illustration of how this process works in practice: Suppose you have a user on a computer who is connected to the internet. Let's say they go to Udemy's website. Here's the process:
- The user is going to go through their internet connection (through their ISP) and they'll bounce around however many times before eventually getting to the host machine, Udemy's servers, via TCP and IP. They will go through the process of establishing that 3-way handshake. This is the first step. TCP says the client (i.e., the user's browser) would like to make a connection to the server (i.e., Udemy's servers).
- Via TCP, the server will respond in the affirmative that a connection can be made.
- Then the data will start to come. Part of that data is going to be the HTTP request. So the HTTP request will come in to the server and once the request has hit, then that connection is terminated. The TCP connection is still open but the HTTP request has terminated. The client computer is still patiently waiting for some kind of response. It still wants an HTTP response. While the server does its song and dance, it will get an HTTP response together and send back the response. The connection will then be terminated. And then the TCP connection is also terminated and everything goes away.
That's the basic gist of how the networking will interact and how the HTTP messages will go back and forth. What does an HTTP message actually look like though? See the next note.
HTTP messages (start line, header, body; request/response)
There are three parts to an HTTP message:
- Start line
- Header
- Body
HTTP messages are generally all text so you usually can read an HTTP message. Let's unpack each piece of the above.
Start Line
The start line is a single line, and it describes the type of request on the way there (i.e., what kind of action or method to which resource endpoint or path) and on the way back in the response it's the status.
- Request:
method
|path
|version-of-HTTP
; for example:get /blog HTTP/1.1
- Response:
version-of-HTTP
|status-code
; for example:HTTP/1.1 404
Header
The header specifies the request or response and describes its body (discussed below). So essentially what the header contains is metadata. And it always comes in the form of key-value pairs. So as a JS developer, it will look like an object or JSON. There are loads of options in there and the earliest we will use is a mime-type. For example, we might see something like content-type: text/html
. There will always be a blank line between the header and body. And that is to indicate that all of the header is done and that it's time for the body.
MDN has a great article about MIME types, an excerpt from which appears below.
MDN note about MIME types |
---|
A media type (also known as a Multipurpose Internet Mail Extensions or MIME type) is a standard that indicates the nature and format of a document, file, or assortment of bytes. It is defined and standardized in IETF's RFC 6838. The Internet Assigned Numbers Authority (IANA) is responsible for all official MIME types, and you can find the most up-to-date and complete list at their Media Types page. Browsers use the MIME type, not the file extension, to determine how to process a URL, so it's important that web servers send the correct MIME type in the response's Content-Type header. If this is not correctly configured, browsers are likely to misinterpret the contents of files and sites will not work correctly, and downloaded files may be mishandled. |
An answer on Stack Overflow also sheds nice light on what a MIME type is (reproduced below).
Stack Overflow answer about what a MIME type is exactly |
---|
A MIME type is a label used to identify a type of data. It is used so software can know how to handle the data. It serves the same purpose on the Internet that file extensions do on Microsoft Windows. So if a server says "This is text/html" the client can go "Ah, this is an HTML document, I can render that internally", while if the server says "This is application/pdf" the client can go "Ah, I need to launch the FoxIt PDF Reader plugin that the user has installed and that has registered itself as the application/pdf handler." You'll most commonly find them in the headers of HTTP messages (to describe the content that an HTTP server is responding with or the formatting of the data that is being POSTed in a request) and in email headers (to describe the message format and attachments). |
Body
The "actual stuff" or data of the request or response or what you may think of as maybe the content or HTML, the image, etc.
Recap and Example |
---|
All of the above is what makes up HTTP messages. You have to follow that protocol, namely having a start line, header, and body. |
Example: We can use something like curl
to illustrate the above with curl -v www.google.com
in Bash to get the following (comments added to make what is printed to the console more sensible):
# Notes that curl gives us are offset by * markers (this isn't part of the HTTP request):
* Rebuilt URL to: www.google.com/
* Trying 2607:f8b0:4009:80d::2004...
* TCP_NODELAY set
* Connected to www.google.com (2607:f8b0:4009:80d::2004) port 80 (#0)
# Start line (of request)
> GET / HTTP/1.1 # GET method; path requested is / (i.e., the root); and protocol is HTTP/1.1
# Headers (of request)
> Host: www.google.com
> User-Agent: curl/7.54.0
> Accept: */*
# End of request headers
>
# In our request, we didn't actually send a body because it's a GET request
# Note the meaning of the left-most arrows: > correspond to request and < to response
# Start line (of response)
< HTTP/1.1 200 OK
# Headers (of response)
< Date: Thu, 02 Apr 2020 05:08:30 GMT
< Expires: -1
< Cache-Control: private, max-age=0
< Content-Type: text/html; charset=ISO-8859-1
...
< Accept-Ranges: none
< Vary: Accept-Encoding
< Transfer-Encoding: chunked
# End of response headers
<
# Response body
<!doctype html>
...
Node server without Express (the basics/fundamentals)
See immediately below for the TLDR version (this is explained in detail in the rest of this note).
TLDR Version
Here is the TLDR version of possibly the most basic Node server without Express:
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, {'content-type': 'text/html'});
res.write('<h1>Hello, World!</h1>');
res.end();
});
server.listen(3000);
Note |
---|
It is always a good idea to consult the docs through the Node website. There you will find documentation about all things Node. For example, under HTTP you will see the following: "To use the HTTP server and client one must require('http') ." |
In setting up a Node server without Express, we use the following (all of which can be read up on through the links to the docs as noted above):
Class | Syntax | Documentation |
---|---|---|
N/A | http.createServer([options][, requestListener]) | Link |
http.ServerResponse | response.writeHead(statusCode[, statusMessage][, headers]) | Link |
http.ServerResponse | response.write(chunk[, encoding][, callback]) | Link |
http.ServerResponse | response.end([data[, encoding]][, callback]) | Link |
http.Server | server.listen() | Link |
The HTTP module is simply part of Node--it is not a third-party module (like Express) that we need to install with NPM or Yarn or something like that. In fact, as noted in the docs, the core modules (e.g., http
, path
, etc.) are compiled into the binary; that is, the core modules are defined within the Node.js source code which means if you have Node.js installed then you definitely have the core modules installed. You still have to require
the core modules to use them (e.g., require('http')
), they're baked into the source code of Node.js so you don't have to reach out to NPM or some other source! One core module of many is the http
module and this is the module that will allow us to make HTTP requests and responses (it has those request and response objects for us to interact with, typically denoted by req
and res
, respectively). The HTTP module has a createServer
method that comes with it.
Note from the docs about createServer |
---|
In the docs we see the syntax http.createServer([options][, requestListener]) which indicates we are expected to pass createServer a requestListener function, where the requestListener is a function which is automatically added to the request event, and http.createServer returns a new instance of http.Server , which is itself an extension of net.Server , a class used to create a TCP or IPC server. |
createServer
As noted above, the requestListener
function passed to http.createServer
is automatically added to the request
event, an event that is emitted each time there is a request to the server (where there may be multiple requests per connection such as in the case of HTTP Keep-Alive connections). The docs entry for request
begin in the following manner:
request
<http.IncomingMessage>
response
<http.ServerResponse>
The docs note that the http.IncomingMessage
object extends the stream.Readable
class, is created by http.Server
or http.ClientRequest
, and is passed as the first argument to the request
and response
event respectively. It may be used to access response status, headers, and data.
The docs note that the http.ServerResponse
class object extends Stream and is an object created internally by an HTTP server (not by the user). It is passed as the second parameter to the request
event.
In simple terms, the createServer
method takes one argument, a callback function, where this callback function takes two arguments, the request
and response
objects, respectively, which are typically denoted as req
and res
to avoid potential naming conflicts while using other node modules (e.g., the now-deprecated request
node module).
The request
and response
objects, or req
and res
, are just that (i.e., what was noted earlier about HTTP traffic and how requests and responses work): they represent the HTTP request message and the HTTP response message, respectively. The req
object represents what we know about the requesting machine or the HTTP request that has come in. And we actually know quite a lot about it. Because we need to. We need to be able to get back to them. We need to be able to find them. So their IP address will be in there, we'll know something about their client (i.e., what type of browser it was and stuff like that), what page or route they wanted to find, all the stuff in the headers, whether or not there was any data passed to us, etc. There will be lots of stuff inside of the request object.
The response
object is what we are going to send back. And, generally speaking, in Express, we are going to get stuff out of the request
object, and we are going to add stuff to the response
object. That is somewhat of an oversimplification, but with strict Node we have fairly limited options (at least in the sense of not being a major pain). Using Express will make our lives much easier.
Before we log anything to the console to see anything about a request or possibly putting together a response, we need to be able to listen for traffic on the server so we can respond appropriately. Fittingly, createServer
returns an object with a listen
method, where listen
has the following syntax for TCP servers according to the docs:
server.listen([port[, host[, backlog]]][, callback])
As the docs note, the listen
function is asynchronous, and when the server starts listening the listening
event will be emitted. If a callback
function is passed to listen
, then the callback
will be added as a listener for the listening
event. Hence, it's fairly common to see something like the following:
const http = require('http');
const port = 3000;
const server = http.createServer((req, res) => {
// Stuff
});
server.listen(port, () => console.log(`Server listening on port ${port}!`));
The callback
passed to listen
in this case is really just a sanity check to make sure the server is listening and that we've started things up effectively.
Note about the port number |
---|
The number 3000 is not special. Neither is 5000 nor 8000 nor several other arbitrary port numbers (within the 65k possible ports). But there are some exceptions. The port number has to be greater than 1000 because unless your role is root then you do not have access to ports 1000 and below unless you change the permissions which is not often advised. |
Recap: The HTTP module is native to Node--we do not have to install it. We simply have to ask for it in the form of const http = require('http');
. We create a server
. We use the HTTP module to create the server with http.createServer
, where createServer
is a function that takes a callback which will run whenever an HTTP request is made to the server. When is an HTTP request made to the server? It is made whenever the port
on which the server is listening gets an HTTP request. We can try this out with the following simple program:
const http = require('http');
const server = http.createServer((req, res) => {
console.log(req);
});
server.listen(3000);
Execute this with Node and then open the browser and go to localhost:3000
. You should see an enormous object logged to the console.
Partial req
object logged
You'll see a ton of different things but here's an example of a few of the things:
IncomingMessage {
_readableState: ReadableState {
objectMode: false,
highWaterMark: 16384,
buffer: BufferList { head: null, tail: null, length: 0 },
length: 0,
pipes: null,
pipesCount: 0,
flowing: null,
ended: false,
...
httpVersionMajor: 1,
httpVersionMinor: 1,
httpVersion: '1.1',
complete: false,
headers: {
host: 'localhost:3000',
connection: 'keep-alive',
'cache-control': 'max-age=0',
'upgrade-insecure-requests': '1',
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36',
'sec-fetch-dest': 'document',
accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'sec-fetch-site': 'none',
'sec-fetch-mode': 'navigate',
'sec-fetch-user': '?1',
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'en-US,en;q=0.9,la;q=0.8'
},
rawHeaders: [
'Host',
'localhost:3000',
'Connection',
'keep-alive',
'Cache-Control',
'max-age=0',
'Upgrade-Insecure-Requests',
'1',
'User-Agent',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36',
'Sec-Fetch-Dest',
'document',
'Accept',
'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'Sec-Fetch-Site',
'none',
'Sec-Fetch-Mode',
'navigate',
'Sec-Fetch-User',
'?1',
'Accept-Encoding',
'gzip, deflate, br',
'Accept-Language',
'en-US,en;q=0.9,la;q=0.8'
],
trailers: {},
rawTrailers: [],
aborted: false,
upgrade: false,
url: '/',
method: 'GET',
statusCode: null,
statusMessage: null,
client: Socket {
connecting: false,
_hadError: false,
...
If you look at the above snippet for the req
object, then you'll see stuff like the headers
object which is a bunch of key-value pairs like host: 'localhost:3000'
, what the user-agent
is, etc. We are not interested in all of this right now. The point is that we get a ton of information about the HTTP request that's being made in the form of an IncomingMessage
.
With the above code, if you actually made the request, then you may see your browser having a spinning wheel or hanging. What's happening? Basically, the browser is waiting for a response. We fielded the request, but we never sent anything back. And that's a problem for the browser because the browser needs a response to know that we are actually finished. This is where the response
object, or res
, comes in. It will be our way of responding to the requestor.
Before putting together the response
object ourselves, recall the following about what each HTTP message (request or response) is comprised of:
- start line: Node will take care of this for us.
- header: We need to deal with this in Node even though Express will largely take care of this for us in the future.
- body: We are absolutely in charge of this. It's a pain to handle in Node but will be much easier in Express.
Let's put together our response message:
Start line
Node takes care of this for us so we do not need to worry about attaching a start line to the res
object to send back to the requestor.
Header
The response
object has a writeHead
method we can use which takes two arguments, the first being a status code and the second being an object for the mime-type. We can do something like the following:
res.writeHead(200, {'content-type': 'text/html'});
This will write out our headers, and this is where we attach the header to the res
object to send back to the requestor.
Body
The response
object has a write
method we can use and we can pass it some HTML to be used as the body for the response:
res.write('<h1>Hello, World!</h1>');
This is where we attach the body to the res
object to send back to the requestor.
Great! We have now attached a start line, header, and body to the res
object (really Node automatically attached the start line and we attached the header and body by means of res.writeHead
and res.write
, respectively). But what do we actually do with the response object now? How do we send it back to the requesting agent (most often a browser)? We finally need to use something like res.end()
at the very end because we need to let the browser know to close the connection.
So here's the final stripped down Node web server without Express:
// nodeServer.js
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, {'content-type': 'text/html'});
res.write('<h1>Hello, World!</h1>');
res.end();
});
server.listen(3000);
If we now run curl -v localhost:3000
, we'll get something like the following:
* Rebuilt URL to: localhost:3000/
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 3000 (#0)
> GET / HTTP/1.1 # start line (request)
> Host: localhost:3000 # header
> User-Agent: curl/7.54.0 # header
> Accept: */* # header
> # end of start line/headers (and no body)
< HTTP/1.1 200 OK # start line (response)
< content-type: text/html # header (response)
< Date: Thu, 02 Apr 2020 08:55:29 GMT # header (inserted by Node)
< Connection: keep-alive # header (inserted by Node)
< Transfer-Encoding: chunked # header (inserted by Node)
< # end of start line/headers
* Connection #0 to host localhost left intact # inserted by Node
<h1>Hello, World!</h1> # body (response)
Very cool!
Note about routing |
---|
Our little "Hello, World!" message will show up regardless of the path the user tries to visit because we are simply listening on port 3000 for HTTP traffic. Whenever there is traffic, the code above says hey send this response right along. In nodeServerTwo.js , we add a conditional to say what to do based on certain routing (i.e., we will have something specific happen for the home page, etc.) |
Node server without Express (routing and serving up static files)
TLDR |
---|
The driving point here is that serving up routes and static files in plain Node without Express is horrible. It is no fun at all. But as observed at the end of the previous note, we have to have some way of selectively choosing when something renders and when something doesn't. We do not want everything to render for a site just when somebody visits the root. Hence, we have to introduce routing of some sort. |
Recall the most basic of Node servers from the previous note:
// nodeServer.js
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'content-type': 'text/html' });
res.write('<h1>Hello, World!</h1>');
res.end();
})
server.listen(3000);
For the web server illustrated above, no matter what kind of request is issued (e.g., get
, put
, post
, delete
) or to what endpoint or route (e.g., /books
, /books?author=Arthur
, /crazyyy
), we will always respond with '<h1>Hello, World!</h1>'
. But of course this isn't really the desired behavior--what we really want is to selectively respond based on what kind of request the user is issuing (e.g., simply get
ting a web page, post
ing a review, etc.) and to what endpoint or route (i.e., post
ing to the /reviews
route should result in something different than post
ing to the /comments
route).
How can we selectively send back a response based on what kind of request the user issued as well as to which path or route they issued the request? To effectively answer this question, we really need to know more about the request object itself. We can gain a better understanding of the request object if we inspect the entire object ourselves. To do this, create a new file, nodeServerWithLoggedRequest.js
, and make the following adjustments to the basic server in nodeServer.js
:
// nodeServerWithLoggedRequest.js
const http = require('http');
const path = require('path');
const fs = require('fs');
const util = require('util');
const server = http.createServer((req, res) => {
if (req.url !== '/favicon.ico') {
fs.writeFile(path.join(__dirname, 'sampleRequest'), util.inspect(req), err => console.log(err));
}
res.writeHead(200, { 'content-type': 'text/html' });
res.write('<h1>Hello, World!</h1>');
res.end();
})
server.listen(3000);
Some notes about the modifications above:
path
: We make use of thepath
module: "Thepath
module provides utilities for working with file and directory paths." Specifically, we use thepath.join
method to effectively bring together the folder where the Node process is currently executing (i.e.,__dirname
) and what we want our file to be called that we are going to write data to (i.e.,sampleRequest
):path.join(__dirname, 'sampleRequest')
.fs
: We make use of thefs
module: "Thefs
module enables interacting with the file system in a way modeled on standard POSIX functions." Specifically, we use thefs.writeFile
method to write data from the request object to a file.util
: We make use of theutil
module: "Theutil
module supports the needs of Node.js internal APIs. Many of the utilities are useful for application and module developers as well." Specifically, we make use of theutil.inspect()
method. As noted on Stack Overflow and further in theutil
docs,util.inspect(obj)
automatically replaces circular links with[Circular]
. This is important because there are circular references on the request object, and if you try the above withoututil.inspect(req)
and justreq
, then you will end up with a file containing only[object Object]
. The next thought may be to try to useJSON.stringify(req)
, but you will then end up with the following error message:TypeError: Converting circular structure to JSON
. This is becauseJSON.stringify
has two exceptions: It will throw aTypeError
when either a circular reference is found (as in our case) or when trying to stringify aBigInt
value. There you have it. Usingutil.inspect
is an easy workaround.if
statement: The statementif (req.url !== '/favicon.ico') { ... }
is simply meant to ensure that only the data we want will be written to file. As noted here, "Browsers will by default try to request/favicon.ico
from the root of a hostname, in order to show an icon in the browser tab." We don't care about the request for the favicon, hence theif
statement.
With the modifications above in mind, start listening on the server and issue a GET
request to the following URL (i.e., simply visit the following URL): http://localhost:3000/books?author=Arthur&title=Wow
. The full request object is shown below (over 700 lines!).
Sample req
object logged to console after request
IncomingMessage {
_readableState: ReadableState {
objectMode: false,
highWaterMark: 16384,
buffer: BufferList { head: null, tail: null, length: 0 },
length: 0,
pipes: null,
pipesCount: 0,
flowing: null,
ended: false,
endEmitted: false,
reading: false,
sync: true,
needReadable: false,
emittedReadable: false,
readableListening: false,
resumeScheduled: false,
emitClose: true,
autoDestroy: false,
destroyed: false,
defaultEncoding: 'utf8',
awaitDrain: 0,
readingMore: true,
decoder: null,
encoding: null,
[Symbol(kPaused)]: null
},
readable: true,
_events: [Object: null prototype] {
end: [Function: resetHeadersTimeoutOnReqEnd]
},
_eventsCount: 1,
_maxListeners: undefined,
socket: Socket {
connecting: false,
_hadError: false,
_parent: null,
_host: null,
_readableState: ReadableState {
objectMode: false,
highWaterMark: 16384,
buffer: BufferList { head: null, tail: null, length: 0 },
length: 0,
pipes: null,
pipesCount: 0,
flowing: true,
ended: false,
endEmitted: false,
reading: true,
sync: false,
needReadable: true,
emittedReadable: false,
readableListening: false,
resumeScheduled: false,
emitClose: false,
autoDestroy: false,
destroyed: false,
defaultEncoding: 'utf8',
awaitDrain: 0,
readingMore: false,
decoder: null,
encoding: null,
[Symbol(kPaused)]: false
},
readable: true,
_events: [Object: null prototype] {
end: [Array],
timeout: [Function: socketOnTimeout],
data: [Function: bound socketOnData],
error: [Function: socketOnError],
close: [Array],
drain: [Function: bound socketOnDrain],
resume: [Function: onSocketResume],
pause: [Function: onSocketPause]
},
_eventsCount: 8,
_maxListeners: undefined,
_writableState: WritableState {
objectMode: false,
highWaterMark: 16384,
finalCalled: false,
needDrain: false,
ending: false,
ended: false,
finished: false,
destroyed: false,
decodeStrings: false,
defaultEncoding: 'utf8',
length: 0,
writing: false,
corked: 0,
sync: true,
bufferProcessing: false,
onwrite: [Function: bound onwrite],
writecb: null,
writelen: 0,
afterWriteTickInfo: null,
bufferedRequest: null,
lastBufferedRequest: null,
pendingcb: 0,
prefinished: false,
errorEmitted: false,
emitClose: false,
autoDestroy: false,
bufferedRequestCount: 0,
corkedRequestsFree: [Object]
},
writable: true,
allowHalfOpen: true,
_sockname: null,
_pendingData: null,
_pendingEncoding: '',
server: Server {
insecureHTTPParser: undefined,
_events: [Object: null prototype],
_eventsCount: 2,
_maxListeners: undefined,
_connections: 2,
_handle: [TCP],
_usingWorkers: false,
_workers: [],
_unref: false,
allowHalfOpen: true,
pauseOnConnect: false,
httpAllowHalfOpen: false,
timeout: 120000,
keepAliveTimeout: 5000,
maxHeadersCount: null,
headersTimeout: 40000,
_connectionKey: '6::::3000',
[Symbol(IncomingMessage)]: [Function: IncomingMessage],
[Symbol(ServerResponse)]: [Function: ServerResponse],
[Symbol(kCapture)]: false,
[Symbol(asyncId)]: 3
},
_server: Server {
insecureHTTPParser: undefined,
_events: [Object: null prototype],
_eventsCount: 2,
_maxListeners: undefined,
_connections: 2,
_handle: [TCP],
_usingWorkers: false,
_workers: [],
_unref: false,
allowHalfOpen: true,
pauseOnConnect: false,
httpAllowHalfOpen: false,
timeout: 120000,
keepAliveTimeout: 5000,
maxHeadersCount: null,
headersTimeout: 40000,
_connectionKey: '6::::3000',
[Symbol(IncomingMessage)]: [Function: IncomingMessage],
[Symbol(ServerResponse)]: [Function: ServerResponse],
[Symbol(kCapture)]: false,
[Symbol(asyncId)]: 3
},
timeout: 120000,
parser: HTTPParser {
'0': [Function: parserOnHeaders],
'1': [Function: parserOnHeadersComplete],
'2': [Function: parserOnBody],
'3': [Function: parserOnMessageComplete],
'4': [Function: bound onParserExecute],
_headers: [],
_url: '',
socket: [Circular],
incoming: [Circular],
outgoing: null,
maxHeaderPairs: 2000,
_consumed: true,
onIncoming: [Function: bound parserOnIncoming],
parsingHeadersStart: 0
},
on: [Function: socketListenerWrap],
addListener: [Function: socketListenerWrap],
prependListener: [Function: socketListenerWrap],
_paused: false,
_httpMessage: ServerResponse {
_events: [Object: null prototype],
_eventsCount: 1,
_maxListeners: undefined,
outputData: [],
outputSize: 0,
writable: true,
_last: false,
chunkedEncoding: false,
shouldKeepAlive: true,
useChunkedEncodingByDefault: true,
sendDate: true,
_removedConnection: false,
_removedContLen: false,
_removedTE: false,
_contentLength: null,
_hasBody: true,
_trailer: '',
finished: false,
_headerSent: false,
socket: [Circular],
connection: [Circular],
_header: null,
_onPendingData: [Function: bound updateOutgoingData],
_sent100: false,
_expect_continue: false,
[Symbol(kCapture)]: false,
[Symbol(kNeedDrain)]: false,
[Symbol(corked)]: 0,
[Symbol(kOutHeaders)]: null
},
[Symbol(asyncId)]: 10,
[Symbol(kHandle)]: TCP {
reading: true,
onconnection: null,
_consumed: true,
[Symbol(owner)]: [Circular]
},
[Symbol(lastWriteQueueSize)]: 0,
[Symbol(timeout)]: Timeout {
_idleTimeout: 120000,
_idlePrev: [Timeout],
_idleNext: [TimersList],
_idleStart: 4629,
_onTimeout: [Function: bound ],
_timerArgs: undefined,
_repeat: null,
_destroyed: false,
[Symbol(refed)]: false,
[Symbol(asyncId)]: 11,
[Symbol(triggerId)]: 10
},
[Symbol(kBuffer)]: null,
[Symbol(kBufferCb)]: null,
[Symbol(kBufferGen)]: null,
[Symbol(kCapture)]: false,
[Symbol(kBytesRead)]: 0,
[Symbol(kBytesWritten)]: 0
},
connection: Socket {
connecting: false,
_hadError: false,
_parent: null,
_host: null,
_readableState: ReadableState {
objectMode: false,
highWaterMark: 16384,
buffer: BufferList { head: null, tail: null, length: 0 },
length: 0,
pipes: null,
pipesCount: 0,
flowing: true,
ended: false,
endEmitted: false,
reading: true,
sync: false,
needReadable: true,
emittedReadable: false,
readableListening: false,
resumeScheduled: false,
emitClose: false,
autoDestroy: false,
destroyed: false,
defaultEncoding: 'utf8',
awaitDrain: 0,
readingMore: false,
decoder: null,
encoding: null,
[Symbol(kPaused)]: false
},
readable: true,
_events: [Object: null prototype] {
end: [Array],
timeout: [Function: socketOnTimeout],
data: [Function: bound socketOnData],
error: [Function: socketOnError],
close: [Array],
drain: [Function: bound socketOnDrain],
resume: [Function: onSocketResume],
pause: [Function: onSocketPause]
},
_eventsCount: 8,
_maxListeners: undefined,
_writableState: WritableState {
objectMode: false,
highWaterMark: 16384,
finalCalled: false,
needDrain: false,
ending: false,
ended: false,
finished: false,
destroyed: false,
decodeStrings: false,
defaultEncoding: 'utf8',
length: 0,
writing: false,
corked: 0,
sync: true,
bufferProcessing: false,
onwrite: [Function: bound onwrite],
writecb: null,
writelen: 0,
afterWriteTickInfo: null,
bufferedRequest: null,
lastBufferedRequest: null,
pendingcb: 0,
prefinished: false,
errorEmitted: false,
emitClose: false,
autoDestroy: false,
bufferedRequestCount: 0,
corkedRequestsFree: [Object]
},
writable: true,
allowHalfOpen: true,
_sockname: null,
_pendingData: null,
_pendingEncoding: '',
server: Server {
insecureHTTPParser: undefined,
_events: [Object: null prototype],
_eventsCount: 2,
_maxListeners: undefined,
_connections: 2,
_handle: [TCP],
_usingWorkers: false,
_workers: [],
_unref: false,
allowHalfOpen: true,
pauseOnConnect: false,
httpAllowHalfOpen: false,
timeout: 120000,
keepAliveTimeout: 5000,
maxHeadersCount: null,
headersTimeout: 40000,
_connectionKey: '6::::3000',
[Symbol(IncomingMessage)]: [Function: IncomingMessage],
[Symbol(ServerResponse)]: [Function: ServerResponse],
[Symbol(kCapture)]: false,
[Symbol(asyncId)]: 3
},
_server: Server {
insecureHTTPParser: undefined,
_events: [Object: null prototype],
_eventsCount: 2,
_maxListeners: undefined,
_connections: 2,
_handle: [TCP],
_usingWorkers: false,
_workers: [],
_unref: false,
allowHalfOpen: true,
pauseOnConnect: false,
httpAllowHalfOpen: false,
timeout: 120000,
keepAliveTimeout: 5000,
maxHeadersCount: null,
headersTimeout: 40000,
_connectionKey: '6::::3000',
[Symbol(IncomingMessage)]: [Function: IncomingMessage],
[Symbol(ServerResponse)]: [Function: ServerResponse],
[Symbol(kCapture)]: false,
[Symbol(asyncId)]: 3
},
timeout: 120000,
parser: HTTPParser {
'0': [Function: parserOnHeaders],
'1': [Function: parserOnHeadersComplete],
'2': [Function: parserOnBody],
'3': [Function: parserOnMessageComplete],
'4': [Function: bound onParserExecute],
_headers: [],
_url: '',
socket: [Circular],
incoming: [Circular],
outgoing: null,
maxHeaderPairs: 2000,
_consumed: true,
onIncoming: [Function: bound parserOnIncoming],
parsingHeadersStart: 0
},
on: [Function: socketListenerWrap],
addListener: [Function: socketListenerWrap],
prependListener: [Function: socketListenerWrap],
_paused: false,
_httpMessage: ServerResponse {
_events: [Object: null prototype],
_eventsCount: 1,
_maxListeners: undefined,
outputData: [],
outputSize: 0,
writable: true,
_last: false,
chunkedEncoding: false,
shouldKeepAlive: true,
useChunkedEncodingByDefault: true,
sendDate: true,
_removedConnection: false,
_removedContLen: false,
_removedTE: false,
_contentLength: null,
_hasBody: true,
_trailer: '',
finished: false,
_headerSent: false,
socket: [Circular],
connection: [Circular],
_header: null,
_onPendingData: [Function: bound updateOutgoingData],
_sent100: false,
_expect_continue: false,
[Symbol(kCapture)]: false,
[Symbol(kNeedDrain)]: false,
[Symbol(corked)]: 0,
[Symbol(kOutHeaders)]: null
},
[Symbol(asyncId)]: 10,
[Symbol(kHandle)]: TCP {
reading: true,
onconnection: null,
_consumed: true,
[Symbol(owner)]: [Circular]
},
[Symbol(lastWriteQueueSize)]: 0,
[Symbol(timeout)]: Timeout {
_idleTimeout: 120000,
_idlePrev: [Timeout],
_idleNext: [TimersList],
_idleStart: 4629,
_onTimeout: [Function: bound ],
_timerArgs: undefined,
_repeat: null,
_destroyed: false,
[Symbol(refed)]: false,
[Symbol(asyncId)]: 11,
[Symbol(triggerId)]: 10
},
[Symbol(kBuffer)]: null,
[Symbol(kBufferCb)]: null,
[Symbol(kBufferGen)]: null,
[Symbol(kCapture)]: false,
[Symbol(kBytesRead)]: 0,
[Symbol(kBytesWritten)]: 0
},
httpVersionMajor: 1,
httpVersionMinor: 1,
httpVersion: '1.1',
complete: false,
headers: {
host: 'localhost:3000',
connection: 'keep-alive',
'cache-control': 'max-age=0',
'upgrade-insecure-requests': '1',
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36',
accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'sec-fetch-site': 'none',
'sec-fetch-mode': 'navigate',
'sec-fetch-user': '?1',
'sec-fetch-dest': 'document',
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'en-US,en;q=0.9,la;q=0.8'
},
rawHeaders: [
'Host',
'localhost:3000',
'Connection',
'keep-alive',
'Cache-Control',
'max-age=0',
'Upgrade-Insecure-Requests',
'1',
'User-Agent',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36',
'Accept',
'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'Sec-Fetch-Site',
'none',
'Sec-Fetch-Mode',
'navigate',
'Sec-Fetch-User',
'?1',
'Sec-Fetch-Dest',
'document',
'Accept-Encoding',
'gzip, deflate, br',
'Accept-Language',
'en-US,en;q=0.9,la;q=0.8'
],
trailers: {},
rawTrailers: [],
aborted: false,
upgrade: false,
url: '/books?author=Arthur&title=Wow',
method: 'GET',
statusCode: null,
statusMessage: null,
client: Socket {
connecting: false,
_hadError: false,
_parent: null,
_host: null,
_readableState: ReadableState {
objectMode: false,
highWaterMark: 16384,
buffer: BufferList { head: null, tail: null, length: 0 },
length: 0,
pipes: null,
pipesCount: 0,
flowing: true,
ended: false,
endEmitted: false,
reading: true,
sync: false,
needReadable: true,
emittedReadable: false,
readableListening: false,
resumeScheduled: false,
emitClose: false,
autoDestroy: false,
destroyed: false,
defaultEncoding: 'utf8',
awaitDrain: 0,
readingMore: false,
decoder: null,
encoding: null,
[Symbol(kPaused)]: false
},
readable: true,
_events: [Object: null prototype] {
end: [Array],
timeout: [Function: socketOnTimeout],
data: [Function: bound socketOnData],
error: [Function: socketOnError],
close: [Array],
drain: [Function: bound socketOnDrain],
resume: [Function: onSocketResume],
pause: [Function: onSocketPause]
},
_eventsCount: 8,
_maxListeners: undefined,
_writableState: WritableState {
objectMode: false,
highWaterMark: 16384,
finalCalled: false,
needDrain: false,
ending: false,
ended: false,
finished: false,
destroyed: false,
decodeStrings: false,
defaultEncoding: 'utf8',
length: 0,
writing: false,
corked: 0,
sync: true,
bufferProcessing: false,
onwrite: [Function: bound onwrite],
writecb: null,
writelen: 0,
afterWriteTickInfo: null,
bufferedRequest: null,
lastBufferedRequest: null,
pendingcb: 0,
prefinished: false,
errorEmitted: false,
emitClose: false,
autoDestroy: false,
bufferedRequestCount: 0,
corkedRequestsFree: [Object]
},
writable: true,
allowHalfOpen: true,
_sockname: null,
_pendingData: null,
_pendingEncoding: '',
server: Server {
insecureHTTPParser: undefined,
_events: [Object: null prototype],
_eventsCount: 2,
_maxListeners: undefined,
_connections: 2,
_handle: [TCP],
_usingWorkers: false,
_workers: [],
_unref: false,
allowHalfOpen: true,
pauseOnConnect: false,
httpAllowHalfOpen: false,
timeout: 120000,
keepAliveTimeout: 5000,
maxHeadersCount: null,
headersTimeout: 40000,
_connectionKey: '6::::3000',
[Symbol(IncomingMessage)]: [Function: IncomingMessage],
[Symbol(ServerResponse)]: [Function: ServerResponse],
[Symbol(kCapture)]: false,
[Symbol(asyncId)]: 3
},
_server: Server {
insecureHTTPParser: undefined,
_events: [Object: null prototype],
_eventsCount: 2,
_maxListeners: undefined,
_connections: 2,
_handle: [TCP],
_usingWorkers: false,
_workers: [],
_unref: false,
allowHalfOpen: true,
pauseOnConnect: false,
httpAllowHalfOpen: false,
timeout: 120000,
keepAliveTimeout: 5000,
maxHeadersCount: null,
headersTimeout: 40000,
_connectionKey: '6::::3000',
[Symbol(IncomingMessage)]: [Function: IncomingMessage],
[Symbol(ServerResponse)]: [Function: ServerResponse],
[Symbol(kCapture)]: false,
[Symbol(asyncId)]: 3
},
timeout: 120000,
parser: HTTPParser {
'0': [Function: parserOnHeaders],
'1': [Function: parserOnHeadersComplete],
'2': [Function: parserOnBody],
'3': [Function: parserOnMessageComplete],
'4': [Function: bound onParserExecute],
_headers: [],
_url: '',
socket: [Circular],
incoming: [Circular],
outgoing: null,
maxHeaderPairs: 2000,
_consumed: true,
onIncoming: [Function: bound parserOnIncoming],
parsingHeadersStart: 0
},
on: [Function: socketListenerWrap],
addListener: [Function: socketListenerWrap],
prependListener: [Function: socketListenerWrap],
_paused: false,
_httpMessage: ServerResponse {
_events: [Object: null prototype],
_eventsCount: 1,
_maxListeners: undefined,
outputData: [],
outputSize: 0,
writable: true,
_last: false,
chunkedEncoding: false,
shouldKeepAlive: true,
useChunkedEncodingByDefault: true,
sendDate: true,
_removedConnection: false,
_removedContLen: false,
_removedTE: false,
_contentLength: null,
_hasBody: true,
_trailer: '',
finished: false,
_headerSent: false,
socket: [Circular],
connection: [Circular],
_header: null,
_onPendingData: [Function: bound updateOutgoingData],
_sent100: false,
_expect_continue: false,
[Symbol(kCapture)]: false,
[Symbol(kNeedDrain)]: false,
[Symbol(corked)]: 0,
[Symbol(kOutHeaders)]: null
},
[Symbol(asyncId)]: 10,
[Symbol(kHandle)]: TCP {
reading: true,
onconnection: null,
_consumed: true,
[Symbol(owner)]: [Circular]
},
[Symbol(lastWriteQueueSize)]: 0,
[Symbol(timeout)]: Timeout {
_idleTimeout: 120000,
_idlePrev: [Timeout],
_idleNext: [TimersList],
_idleStart: 4629,
_onTimeout: [Function: bound ],
_timerArgs: undefined,
_repeat: null,
_destroyed: false,
[Symbol(refed)]: false,
[Symbol(asyncId)]: 11,
[Symbol(triggerId)]: 10
},
[Symbol(kBuffer)]: null,
[Symbol(kBufferCb)]: null,
[Symbol(kBufferGen)]: null,
[Symbol(kCapture)]: false,
[Symbol(kBytesRead)]: 0,
[Symbol(kBytesWritten)]: 0
},
_consuming: false,
_dumped: false,
[Symbol(kCapture)]: false
}
If you do a quick search for "Circular" in the request object shown above, then you will see there are 15 instances found (mostly having to do with sockets, connections, and symbols). In any case, how does this help us with our problem concerning how to come up with an appropriate response? First of all, note what the req
object looks like: IncomingMessage { ... information about the request ... }
. What does IncomingMessage
mean? As noted in the docs about http.IncomingMessage
: "An IncomingMessage
object is created by http.Server
or http.ClientRequest
and passed as the first argument to the 'request'
and 'response'
event respectively. It may be used to access response status, headers and data."
Our IncomingMessage
is created by http.Server
. How do we know this? The docs on http.createServer
make this clear with the following concluding line: "Returns a new instance of http.Server
." What kind of information belongs to this IncomingMessage
and, furthermore, could any of this information be of use to us? The information belonging to the IncomingMessage
object has everything to do with the request issued by a user to our server.
Within the IncomingMessage
object, do a search for "method" and you will get 1 result. In our example, we have the following: method: 'GET'
. This tells us that a GET
request was issued. Furthermore, look at the url
property above the method
property: url: '/books?author=Arthur&title=Wow'
. Based on this information, you can imagine putting together a server that responded in different ways based on what its method
value was as well as its url
value. Since we are using only Node and not Express just yet, we will not make use of req.method
, but we can put together a basic server with customized responses based on req.url
(i.e., what endpoint the user issues a request to):
const http = require('http'); // enable ability to manage HTTP traffic
const fs = require('fs'); // access THIS computer's file system (yours, not the requestor's) with Node
const server = http.createServer((req, res) => {
console.log(`Requested URL: ${req.url}`); // observe what URL is requested
if (req.url === '/') { // user wants the home page
res.writeHead(200, { 'content-type': 'text/html' });
const homePageHTML = fs.readFileSync('node.html');
res.write(homePageHTML);
res.end();
} else if (req.url === "/node.png") {
res.writeHead(200, { 'content-type': 'image/png' });
const image = fs.readFileSync('./node.png');
res.write(image);
res.end();
} else if (req.url === "/styles.css") {
res.writeHead(200, { 'content-type': 'text/css' });
const css = fs.readFileSync('./styles.css');
res.write(css);
res.end();
} else {
res.writeHead(404, { 'content-type': 'text/html' });
res.write(`<h4>Sorry, this isn't the page you're looking for!</h4>`)
res.end()
}
});
server.listen(3000);
Quick disclaimer about the above code: It may seem like we are "serving up" different files, but that is not what we are doing. We are serving up a response. So what we are going to do is read from a file, and then we're going to send the contents of that file back to the requestor. (The file's contents we will attach to our response will be node.html
, shown below.)
The node.png
file is just the logo on the Node.js Wiki page, and the styles.css
file is very basic:
body {
text-align: center;
}
img {
max-width: 200px;
}
To really understand what's going on though, we need to take a look at the basic node.html
file:
<!-- node.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<link rel="stylesheet" href="./styles.css"> <!--This requires an HTTP request-->
<title>Home Page</title>
</head>
<body>
<div class="container">
<h1>Node.js is an open-source, cross-platform JavaScript run-time environment that executes JavaScript code outside of a browser.</h1>
<img src="./node.png" alt="Picture of Node"> <!--This requires an HTTP request-->
</div>
</body>
</html>
The server design will become abundantly clear when we start our server up and visit http://localhost:3000
. We see the following logged to the console:
Requested URL: /
Requested URL: /styles.css
Requested URL: /node.png
What is happening here? The /
indicates a request to the root of our host, and we can verify this by means of using curl
:
bash-3.2$ curl -v localhost:3000
* Rebuilt URL to: localhost:3000/
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 3000 (#0)
> GET / HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200 OK
< content-type: text/html
< Date: Sat, 03 Oct 2020 16:14:04 GMT
< Connection: keep-alive
< Transfer-Encoding: chunked
<
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<link rel="stylesheet" href="./styles.css"> <!--This requires an HTTP request-->
<title>Home Page</title>
</head>
<body>
<div class="container">
<h1>Node.js is an open-source, cross-platform JavaScript run-time environment that executes JavaScript code outside of a browser.</h1>
<img src="./node.png" alt="Picture of Node"> <!--This requires an HTTP request-->
</div>
</body>
* Connection #0 to host localhost left intact
</html>
Quick aside: Note that the url
property on the request object (i.e., req.url
) is the path relative to the root domain that the request hit--since our root domain is http://localhost:3000
, req.url
will be everything after that. So if the user went to http://localhost:3000/books/book?title=Andronicus&author=Blanksy
, for example, then req.url
would be /books/book?title=Andronicus&author=Blanksy
. More can be read about req.url
by visiting the docs for http.IncomingMessage
(since the req
object is an IncomingMessage
as is made clear when logging the req
object which looks like IncomingMessage { ... }
, as noted previously) and then jumping to message.url
: "[message.url
is] only valid for request obtained from http.Server
[which is true in our case]. [message.url
stands for the] request URL string. This contains only the URL that is present in the actual HTTP request."
If we simply visit http://localhost:3000
, then what will req.url
be? It will simply be /
. Why? The curl
request above illustrates this--note how near the top we have * Rebuilt URL to: localhost:3000/
. So whenever an HTTP request is made, a /
gets added if there isn't one, and this simply indicates a request to the root of the host domain if /
is all there is.
If we revisit the node.html
code above, we can start to make sense of what is going on with our more advanced albeit still basic web server: We first make a request to /
and get back the HTML present in the node.html
file, but there are two lines in the node.html
file worth looking at in more detail:
<link rel="stylesheet" href="./styles.css">
<img src="./node.png" alt="Picture of Node">
We essentially have three requests:
/
: The browser starts to process thenode.html
file whose contents we have written to the response object to send back to the requestor. When the browser is processing and gets to the line<link rel="stylesheet" href="./styles.css">
, what happens? This line leads back to our server (i.e.,./
indicates a relative path to the location of our server), which means we are going to have another HTTP request. So the initial request to/
runs because someone (i.e., the browser) made a request to port 3000. Then our callback inside ofcreateServer
begins executing, following the directions laid out for whenreq.url === '/'
. The browser then gets its response and starts doing its thing (i.e., processing thetext/html
we sent it from thenode.html
file whose contents we read in). As soon as it hits the line<link rel="stylesheet" href="./styles.css">
, a new request comes in to port 3000. Will thestyles.css
stylesheet be loaded if we don't have routing logic for it based onreq.url
? No! Why? Because our server is not a file server. We're not sending files back. So what happens? We respond based on whatever routing logic we have--if we don't have routing built for a request to/styles.css
, then either nothing will happen or we'll get a 404 message if we have that configured. The server does not send back files--it serves back responses./styles.css
: The request is made to this endpoint as soon as the browser hits the line<link rel="stylesheet" href="./styles.css">
. The./
indicates the request should go tohttp://localhost:3000
or, more specifically,http://localhost:3000/styles.css
./node.png
: The request is made to this endpoint as soon as the browser hits the line<img src="./node.png" alt="Picture of Node">
. Again,./
indicates the request should go tohttp://localhost:3000
or, more specifically,http://localhost:3000/node.png
.
In summary, three separate HTTP requests are needed, and this was evident when logging req.url
to the console when using our web server. But now we know why three separate requests were issued. It should now be painfully clear how burdensome this would be to build out an entire application like this. So much toiling for basic functionality! Express will help enormously in regards to all of this.
Comparing Express and Plain Node: Simple Server, Routing, etc.
What is Express and why should we care (i.e., how does it help with Node)?
If we visit the Express website, then we are greeted with the following:
Express: Fast, unopinionated, minimalist web framework for Node.js
Cool. But what does this mean? Well, let's first make it clear that Express is literally "just" a node module. That is, we cannot have Express without Node even though we can have Node without Express (as painful as that might be).
The "fast" part is debatable because everything is fast until it's not. But Express has put in a lot of work to trim things down and make it as lean and lightweight as possible. The "unopinionated" is a market-friendly word but sometimes it's good and sometimes it's bad. Basically, "unopinionated" means they don't force things on you. Rails is basically the opposite--they make decisions for you like sort of corralling you into using Postgres. The "framework" element is mostly a remark on how the Express architects have tried to make everything that one might commonly use when employing Node in an application.
There are a lot of reasons to use Express. One of the obvious reasons (or it will be obvious) is all of the utilities Express gives us to avoid a lot of the torment we had to endure previously when trying to serve things up and routing stuff in Node. Web applications can be made quickly and easily with Express. There are lots of templating engines for Express like Jade/Pug, EJS, Handlebars, etc. Express truly shines in building APIs. It's almost unfair how quickly and easily you can build one and process JSON and respond with JSON.
Basic Express server without routing (reworking the Node server in Express)
Recall the most basic Node server we set up previously:
// nodeServer.js
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, {'content-type': 'text/html'});
res.write('<h1>Hello, World!</h1>');
res.end();
})
server.listen(3000);
We will now recreate the above basic server in Express.
Recall that previously we could just write const http = require('http');
because the HTTP module is native to Node. Well, the Express module is not native to Node. It is a third-party module and thus we need to install it with NPM. So we need to have a package.json
file in our folder structure.
Installing a new node module and where it is saved |
---|
When you install a new node module, it is going to install itself relative to the first package.json it finds. It will put itself in the node_modules folder. |
The best way to handle this is to run npm init
inside of whatever folder your project is in where your node modules should be located (generally at the root-level).
Since we want to use the Express module, we will execute npm install express
which then adds a bunch of different dependencies to the node_modules
folder. Subsequently, we can use the Express module just as we use other modules:
const express = require('express');
const app = express();
In almost all Express applications, you will include the line const app = express();
as well. What do these lines do? Well, the express
variable declared above holds whatever has been exported by the express
node module. If you look in the node_modules
folder for express
, then you can inspect it to see what is being exported (from its index.js
file). We find the following in index.js
:
module.exports = require('./lib/express');
So now we open the lib
folder inside of the express
module and look at the express.js
file. Lots of stuff is being exported but the thing we are interested in is this line which contains what is being exported by default:
exports = module.exports = createApplication;;
What is createApplication
? It is a function in that file:
function createApplication() {
var app = function(req, res, next) {
app.handle(req, res, next);
};
mixin(app, EventEmitter.prototype, false);
mixin(app, proto, false);
// expose the prototype that will get set on requests
app.request = Object.create(req, {
app: { configurable: true, enumerable: true, writable: true, value: app }
})
// expose the prototype that will get set on responses
app.response = Object.create(res, {
app: { configurable: true, enumerable: true, writable: true, value: app }
})
app.init();
return app;
}
So basically writing const express = require('express');
is equivalent to writing const express = createApplication;
. So when we write const app = express();
what we are really doing is invoking the createApplication
function. What's the return value of this function? It is app
(with everything Express makes possible on app
).
The point of all of this, which may seem like just a bunch of rigmarole at first, is to train ourselves to think about what is really going on under the hood so we can better inspect problems we may encounter and to better understand boilerplate syntax.
As noted in the docs, the Express app
comes with a whole bunch of methods, one of which is all
, which takes two arguments, the first being a route or path and the second being a callback function to invoke if the path specified as the first argument is requested, where the callback accepts three arguments: req
, res
, and next
. The first two we are already acquainted with and the next
one we will become more acquainted with when we start looking at middleware.
Let's use app.all
in a very generic way:
app.all('*', (req, res, next) => {
res.send(`<h1>This is the home page!</h1>`)
})
Some things to note here: The '*'
means we will listen for HTTP traffic for any route on the specified port, much as we did previously. The very first win is that Express handles the basic headers (i.e., status code, mime-type; we may have to modify them once we get fancy) which is awesome. Another awesome win is that Express handles closing the connection so we do not manually need to do something like res.end()
. What we need to deal with is the in-between. We need to come up with the response
body we want to send back to the requestor. As the docs note, we will not use res.write
but res.send
. Finally, the last thing we need to do, as noted in the docs, is app.listen
instead of server.listen
.
In sum, we can create a basic server in either Node or Express.
In Node:
// nodeServer.js
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, {'content-type': 'text/html'});
res.write('<h1>Hello, World!</h1>');
res.end();
})
server.listen(3000);
In Express:
// expressServer.js
const express = require('express');
const app = express();
app.all('*', (req, res, next) => {
res.send(`<h1>This is the home page!</h1>`)
})
app.listen(3000);
Basic Express routing concepts and implementation
We will recreate the plain Node server that served static files (or the content of those files, that is) in a bit but we should get some basic routing concepts down from Express.
To get started, we can immediately include the express
module since we are inside the same directory in which express
was installed before and we can go ahead and plan to listen on port 3000:
const express = require('express');
const app = express();
// routing and other stuff
app.listen(3000);
As can be seen in the docs, app
has a ton of methods (but we are especially interested right now in the ones in bold):
app.all()
app.delete()
app.disable()
app.disabled()
app.enable()
app.enabled()
app.engine()
app.get()
app.get()
app.listen()
app.METHOD()
app.param()
app.path()
app.post()
app.put()
app.render()
app.route()
app.set()
app.use()
We are interested in the bolded ones because they correspond to HTTP verbs! REST verbs! Worth noting is that when you make an HTTP request you are making a specific type of HTTP request. We can easily see these methods correspond to what many people would think of as a CRUD application where you can create, read, update, and delete, all of which correspond to app.post
, app.get
, app.put
, and app.delete
, respectively. (The app.all
method simply accepts any type of request.)
Of course, a GET
request is the default for all browsers. This is why a tool like Postman is so useful. You can make all sorts of requests besides a GET
request (even though you can do that too of course).
So the application methods we want to focus on right now are the following:
app.all()
app.delete()
app.get()
app.post()
app.put()
Each of these methods takes two arguments, the first being a route or path and the second being a callback function to invoke if an HTTP request is made to the first argument (i.e., the route or path) with a verb that matches the application method name for that route; that is, something like
app.post('/post-something', (req, res, next) => {
res.send(`<h1>I tried to POST something!</h1>`)
})
indicates that we are looking out for an HTTP request to the post-something
route where the HTTP request is specifically a POST
request.
The great thing is we can handle all types of requests on the same path/route with very little overhead. The callbacks for the routes will only respond when a request is made to the specified path and the request is of a type equivalent to the application method being used as above with post
. This is great news!
The point here is that the routing system in Express is meant to handle two things, namely the type of HTTP request and also the path you actually want to fetch (req.method
and req.url
in plain Node, respectively). The application methods are named to correspond with the HTTP verbs they are looking out for.
Express works from the top down. That is, as soon as we have sent a response, subsequent matching routes won't get run (unless we explicitly architect it in a way to do this).
Basic Express server with routing (reworking the Node server in Express)
Recall the basic albeit tedious Node server we set up that handled basic routing:
// nodeServerTwo.js
const http = require('http');
const fs = require('fs');
const server = http.createServer((req, res) => {
if (req.url === '/') { // user wants the home page
res.writeHead(200, { 'content-type': 'text/html' });
const homePageHTML = fs.readFileSync('node.html');
res.write(homePageHTML);
res.end();
} else if (req.url === "/node.png") {
res.writeHead(200, { 'content-type': 'image/png' });
const image = fs.readFileSync('./node.png');
res.write(image);
res.end();
} else if (req.url === "/styles.css") {
res.writeHead(200, { 'content-type': 'text/css' });
const css = fs.readFileSync('./styles.css');
res.write(css);
res.end();
} else {
res.writeHead(404, { 'content-type': 'text/html' });
res.write(`<h4>Sorry, this isn't the page you're looking for!</h4>`)
res.end()
}
});
server.listen(3000);
Using the routing concepts discussed in the previous note, we will now try to recreate the plain Node server above but in Express.
For us to accomplish this, we will start by noting that app
comes with a use
method. Per the docs, the app.use([path,] callback [, callback...])
syntax results in mounting the specified middleware function or functions at the specified path: the middleware function is executed when the base of the requested path matches path
.
In our specific use case, we will not explicitly provide the path or callback but instead pass a built-in middleware function in Express, namely express.static('public')
. As noted in the docs for express.static(root, [options])
: This is a built-in middleware function in Express. It serves static files and is based on serve-static
. The root
argument specifies the root directory from which to serve static assets. The function determines the file to serve by combining req.url
with the provided root directory. When a file is not found, instead of sending a 404 response, it instead calls next()
to move on to the next middleware, allowing for stacking and fall-backs. (See the rest of the docs for more details as well as adding options to express.static
.)
Worth noting is how express.static
actually works in light of how most Express applications begin:
const express = require('express');
const app = express();
We noted previously how the createApplication
function is the default export from the express
module. But there are several other exports, one of which is the static
method we are now going to use. If we look in the node_modules
folder as we did before, then we will see the following lines among others:
/**
* Expose middleware
*/
exports.json = bodyParser.json
exports.query = require('./middleware/query');
exports.raw = bodyParser.raw
exports.static = require('serve-static');
exports.text = bodyParser.text
exports.urlencoded = bodyParser.urlencoded
Right now, of course, we are interested in the exports.static = require('serve-static');
line. We can see how express.static
is based on the serve-static
module. We could inspect the serve-static
module and see what the default export is (it's the function serveStatic
), but we will not go into the details here. The important thing is that we can pass this function a directory name, say public
as is often the case, and anytime anybody wants to see a resource located in public
, we do not have to worry about routing or anything like that.
NOTE: How express.static works (and generally serving up numerous files) |
---|
Some people actually use express.static to serve up entire front-end sites. If using express.static('public') , you could drop an entire front-end site into the public folder and you're done. You don't have to deal with many headaches you might have to otherwise endure. You do not (and should not ... it will not work if you do ... you will get a 404 error) put public in front of the path to the resource you want to access that is being statically served. For example, if node.png is in the public folder and we are listening on port 3000, then we can access this picture by going to localhost:3000/node.png instead of localhost:3000/public/node.png .For the sake of clarity, although you would never do this in practice, you could also have app.use(express.static('node_modules')) and then you'd be statically serving up all the files in the node_modules folder. And then we could access whichever one we want, say the HISTORY.md one in the accepts node module, like so: localhost:3000/accepts/HISTORY.md .The point is that if we execute app.use(express.static('folder-name')) then the server knows that everything in folder-name is going to be served up as part of the root domain. It's worth noting too that you can have as many app.use(express.static('folder-name')) commands as you want because they're being attached to the entire app lication which is being served up on port 3000; that is, you can statically serve the contents of as many folders as you want. Do take care, however, that you do not statically serve something that should not be readily accessible. This is why the convention is to name the folder public whose contents you want to be made publicly available. Typically, the stuff you would want to be statically served are things like stylesheets, images, etc. |
To fully recreate our Node server, we will not use Node to read in our node.html
file with the fs
module but instead use the sendFile
method on the response
object to achieve the same thing (courtesy of the native path
module so we can use an absolute path which is required). That is, instead of having to deal with
if (req.url === '/') {
res.writeHead(200, { 'content-type': 'text/html' });
const homePageHTML = fs.readFileSync('node.html');
res.write(homePageHTML);
res.end();
}
we will simply have something like the following:
app.all('/', (req, res) => {
res.sendFile(path.join(__dirname, 'node.html'));
})
It's very uncommon to do something like res.sendFile
; instead, typically you would do something like res.render
, but we have not gotten to templates yet which will make that feasible.
NOTE: How __dirname works |
---|
As noted on a Stack Overflow thread, __dirname is only defined in scripts (i.e., .js files). It's not available in the Node REPL. Basically, __dirname means "the directory of this script." In REPL, you do not have a script. Hence, __dirname would not have any real meaning. It's too bad this is the case because loading a script file while inside the REPL using .load will result in an error if you used __dirname in your script. One way to get around this is, inside of the REPL, do something like __dirname = process.cwd() . In fact, in light of this, instead of using path.join(__dirname, 'node.html') as we did above, we could just as well use process.cwd() + '/node.html' as the argument to res.sendFile . There's a lot more about process and its available methods in the docs. Always look at the docs. |
Recapping, we have the following two equivalent ways of handling the basic routes and serving up static files:
Using Express:
// expressServerTwo.js
const path = require('path');
const express = require('express');
const app = express();
app.use(express.static('public'));
app.all('/', (req, res) => {
res.sendFile(path.join(__dirname, 'node.html'));
})
app.all('*', (req,res) => {
res.send(`<h1>Sorry, this page does not exist</h1>`)
})
app.listen(3000);
Using plain Node:
// nodeServerTwo.js
const http = require('http');
const fs = require('fs');
const server = http.createServer((req, res) => {
if (req.url === '/') { // user wants the home page
res.writeHead(200, { 'content-type': 'text/html' });
const homePageHTML = fs.readFileSync('node.html');
res.write(homePageHTML);
res.end();
} else if (req.url === "/node.png") {
res.writeHead(200, { 'content-type': 'image/png' });
const image = fs.readFileSync('./node.png');
res.write(image);
res.end();
} else if (req.url === "/styles.css") {
res.writeHead(200, { 'content-type': 'text/css' });
const css = fs.readFileSync('./styles.css');
res.write(css);
res.end();
} else {
res.writeHead(404, { 'content-type': 'text/html' });
res.write(`<h4>Sorry, this isn't the page you're looking for!</h4>`)
res.end()
}
});
server.listen(3000);
Hopefully it is clear just how much nicer Express is to work with and how much easier we could get things to scale if we wanted or needed to.
Middleware Basics
Middleware in Express: general overview and examples
Express claims itself to really be two things:
- A router. We saw several of the possibilities previously with stuff like
app.post
,app.get
, etc. - A series of middleware that comprises a web framework.
What does the second point really mean? Middleware is something that happens ... in the middle ... of something. What do we do a lot of in web applications? We get requests and send responses. Middleware is stuff we can do between getting the request and sending back the response: req ---MIDDLEWARE---> res
. In more "Express-ish" terms, a middleware function is ANY function that has access to the req
, res
, and next
objects. That said, basically Express is just a bunch of middleware! It's a whole bunch of little functions working in unison that have access to req
, res
, and next
that slowly piece together a cool web framework (that and a router).
Maybe think of an illustrative example like this:
- Request comes in
- We need to validate the user (sometimes)
- We need to store some things in the database
- If there is data from the user, then we need to parse it and store it
- Respond
Steps 2-4 above are all situations that must be addressed with middleware functions. They are everything that happens between getting the request and firing back a response.
The response always depends on the request. The nature of the dependence (i.e., large or small) could be negligible as we have seen with what we have done so far (i.e., basically sending back some stuff regardless of what kind of request we receive). But in some cases it could matter quite a bit (e.g., whether or not a user is validated). The point is that how the response is constructed always depends on the request in some manner, and Express has a locals
property on every response
object intended to effectively capture whatever we want from a specific request--what we capture from the specific incoming request
can be stored as local variables on the response
object via res.locals
.
As the docs communicate about res.locals
: An object that contains response local variables scoped to the request, and therefore available only to the view(s) rendered during that request/response cycle (if any). Otherwise, this property is identical to app.locals
. This property is useful for exposing request-level information such as the request path name, authenticated user, user settings, and so on:
// Documentation example usage of how you might want to use res.locals
app.use(function (req, res, next) {
res.locals.user = req.user
res.locals.authenticated = !req.user.anonymous
next()
})
Basically, the response
object has a property called locals
that is pre-built into Express--it is attached to every response, and it will live for the life of the response and it's very useful for passing data over to a template. For now, it is simply nice to know that we will be able to pass res.locals
around from place to place. Every middleware function will have access to res.locals
for the life of the response. How? Because every middleware function has access to the response
object.
We can illustrate all of this by means of a somewhat phony example involving some validateUser
middleware:
const express = require('express');
const app = express();
function validateUser(req, res, next) {
res.locals.validated = true;
next();
}
// app.use(validateUser); // <-- COMMENTED OUT ON PURPOSE
app.use('/admin', validateUser);
app.get('/', validateUser);
app.get('/', (req, res, next) => {
res.send(`<h1>Main Page</h1>`)
console.log(`Validated? ${!!res.locals.validated}`);
})
app.get('/admin', (req, res, next) => {
res.send(`<h1>Admin Page</h1>`)
console.log(`Validated? ${!!res.locals.validated}`);
})
app.get('/secret', (req, res, next) => {
res.send(`<h1>This is a secret page. Go away!</h1>`);
console.log(`Validated? ${!!res.locals.validated}`);
})
app.use('*', (req, res) => {
res.send(`<h1>Woops! Is no good.</h1>`);
console.log(`Validated? ${!!res.locals.validated}`);
})
app.listen(3000);
Let's now unpack some of the stuff from above:
Artificial use of res.locals
in validateUser
As can be seen with our definition of validateUser
and our use of res.locals
, no reference is even made to the request
object. Nearly always the local variables you want on the response
object will depend, in some way, on the request
object as the example in the docs shows:
app.use(function (req, res, next) {
res.locals.user = req.user
res.locals.authenticated = !req.user.anonymous
next()
})
We will get to such common use cases very soon.
Non-anonymous middleware by defining validateUser
globally
The validateUser
function seen above is an explicit, named function declaration which is different than much of the other middleware we have used to this point. Since validateUser
is a function whose signature contains req
, res
, and next
, we should note that something like
app.get('/', validateUser);
is basically equivalent to
app.get('/', (req, res, next) => {
res.locals.validated = true;
next();
})
The main difference is that declaring the validateUser
middleware globally will give us access to it globally; that is, we can use validateUser
wherever we want whereas the callback function/middleware in
app.get('/', (req, res, next) => {
res.locals.validated = true;
next();
})
is only available when a GET
request is made to the root.
Effect of next()
It is hard to overstate how critical next()
really is. Suppose we omitted it in our definition of validateUser
:
...
function validateUser(req, res, next) {
res.locals.validated = true;
// next();
}
app.use(validateUser);
...
What would happen here? Any route that expected us to use (i.e., invoke) the validateUser
function would not actually get around to sending a response. If we actually ran app.use(validateUser);
, then validateUser
would be invoked for whatever path we could try to reach, and doing so would result in validateUser
running but us never actually sending a response back. The browser would just hang. Not good! Don't forget next()
: you want to hand control off to the next piece of middleware in the cycle (probably the actual routing you have set up).
app.use(validateUser);
The reason app.use(validateUser);
is commented out is exactly because of its effect: it results in invoking validateUser
every time any HTTP request is made to any path (i.e., validateUser
is used at the "application level"). Of course, in some cases you may want to do this. But the use cases are likely few.
app.use('/admin', validateUser);
The effect of app.use('/admin', validateUser);
is that we are telling Express to use validateUser
for any type of HTTP request to only the /admin
path.
app.get('/', validateUser);
The effect of app.get('/', validateUser);
is that we are telling Express to use validateUser
only on GET
requests for only the path /
.
Third-party security middleware: helmet
As noted in the Express docs on security best practices: "Helmet can help protect your app from some well-known web vulnerabilities by setting HTTP headers appropriately. Helmet is actually just a collection of smaller middleware functions that set security-related HTTP response headers: [...]."
To use the middleware offered by helmet
, it's as simple as app.use(helmet());
. You do, however, need to be somewhat mindful in terms of what major version of helmet
you use (now 4+; the version history indicates the major version was updated from 3 to 4 in July of 2020).
The simple story:
app.use(helmet());
in major version 3 will probably not cause issues (7 of 11 middleware functions are used by default), but using helmet
in that way in major version 4 could cause issues (all 11 middleware functions are used by default), specifically errors related to not being able to load resources (stylesheets, scripts, etc.): "Refused to load the <resource>
... because it violates the following Content Security Policy directive ...":
The simple solution now is to use
app.use(helmet({ contentSecurityPolicy: false }));
instead of just app.use(helmet())
(all 11 middleware functions will be used except the sometimes problematic contentSecurityPolicy
one). See below for more details.
Helmet: Major Version 3
For major version 3 of helmet
, we saw this in the documentation:
Helmet is a collection of 11 smaller middleware functions that set HTTP response headers. Running app.use(helmet())
will not include all of these middleware functions by default.
Module | Default? |
---|---|
contentSecurityPolicy for setting Content Security Policy | |
crossdomain for handling Adobe products' crossdomain requests | |
dnsPrefetchControl controls browser DNS prefetching | ✔ |
expectCt for handling Certificate Transparency | |
frameguard to prevent clickjacking | ✔ |
hidePoweredBy to remove the X-Powered-By header | ✔ |
hsts for HTTP Strict Transport Security | ✔ |
ieNoOpen sets X-Download-Options for IE8+ | ✔ |
noSniff to keep clients from sniffing the MIME type | ✔ |
referrerPolicy to hide the Referer header | |
xssFilter adds some small XSS protections | ✔ |
In the table above, take note that the contentSecurityPolicy
middleware is not loaded by default.
Helmet: Major Version 4
As noted in the docs for the current version of helmet
:
The top-level helmet function is a wrapper around 11 smaller middlewares. In other words, these two things are equivalent:
// This...
app.use(helmet());
// ...is equivalent to this:
app.use(helmet.contentSecurityPolicy());
app.use(helmet.dnsPrefetchControl());
app.use(helmet.expectCt());
app.use(helmet.frameguard());
app.use(helmet.hidePoweredBy());
app.use(helmet.hsts());
app.use(helmet.ieNoOpen());
app.use(helmet.noSniff());
app.use(helmet.permittedCrossDomainPolicies());
app.use(helmet.referrerPolicy());
app.use(helmet.xssFilter());
To set custom options for one of the middleware, add options like this:
// This sets custom options for the `referrerPolicy` middleware.
app.use(
helmet({
referrerPolicy: { policy: "no-referrer" },
})
);
You can also disable a middleware:
// This disables the `contentSecurityPolicy` middleware but keeps the rest.
app.use(
helmet({
contentSecurityPolicy: false,
})
);
Major Changes with Helmet from v3 to v4
The changelog for helmet
details what all has changed, but the Helmet 4 upgrade guide is really the useful source of information, specifically the section on which middlewares were added by default:
When you use the top-level Helmet function (i.e., app.use(helmet())
), Helmet 4 now includes the following middlewares by default:
helmet.contentSecurityPolicy
(which sets theContent-Security-Policy
header; see below)helmet.expectCt
(which sets theExpect-CT
header)helmet.permittedCrossDomainPolicies
(which sets theX-Permitted-Cross-Domain-Policies
header)helmet.referrerPolicy
(which sets theReferrer-Policy
header)
These were present in Helmet 3 but were disabled by default.
The section immediately below this in the guide details what changed in the Content-Security-Policy
middleware: The Content-Security-Policy
middleware had the biggest changes.
There is now a default policy. Helmet 3 disabled CSP by default. Helmet 4 does not, and sets one. If this is causing problems, you can disable the CSP header:
app.use(helmet({ contentSecurityPolicy: false }));
Alternatively, for more security, you can craft a Content Security Policy for your site.
Built-in middleware: express.json
and express.urlencoded
In this note we will talk about two important methods that belong to the express
module that have not been used yet, namely express.json
and express.urlencoded
, and then one other piece of middleware (from the helmet
module) that is not native to Express but that we really should always use for security reasons (always wear your helmet
!).
Looking at the docs, we see a few Express methods we can use:
express.json()
express.raw()
express.Router()
express.static()
express.text()
express.urlencoded()
We have already touched on express.static
, but we want to look at express.json
and express.urlencoded
now. We'll look at the others later. Let's start with express.json
.
As the docs note, express.json([options])
is a built-in middleware function in Express. It parses incoming requests with JSON payloads and is based on body-parser
. Returns middleware that only parses JSON and only looks at requests where the Content-Type
header matches the type
option (the default type
option is application/json
... see the link for all options and defaults). This parser accepts any Unicode encoding of the body and supports automatic inflation of gzip
and deflate
encodings. A new body
object containing the parsed data is populated on the request
object after the middleware (i.e., req.body
), or an empty object ({}
) if there was no body to parse, the Content-Type
was not matched, or an error occurred.
Read the above excerpt from the docs again (and actually visit the docs for all sorts of good and more detailed information). Since express.json
is based on body-parser
, just like express.static
is based on serve-static
, we can see that by installing the express
module we also install the body-parser
module as a dependency (we cannot use express.json
without body-parser
and express.json
is built-in middleware in Express), we can take a look inside the express
node module and we will find body-parser
listed as one of the dependencies
in the package.json
. So what does this mean? Well, if someone sends you JSON, meaning the Content-Type
is going to come through as application/json
or something along those lines, then express.json
will kick into action and parse the body for us. Note that any data that comes into any server (via form submission or whatever), even if it's an Express server, is still going to be interpreted as a basic string. It doesn't make any difference. That's how servers work. The string needs to be parsed to be of much use and we can do that thanks to express.json
. Of course, we will want to use this everywhere in our Express application (not just on select paths or routes) and thus we will use it like so:
app.use(express.json())
Now let's consider express.urlencoded([options])
. As the docs note, this is a built-in middleware function in Express. It parses incoming requests with urlencoded payloads and is based on body-parser. Returns middleware that only parses urlencoded bodies and only looks at requests where the Content-Type
header matches the type
option (the default type
option is application/x-www-form-urlencoded
... see the link for all options and defaults). This parser accepts only UTF-8 encoding of the body and supports automatic inflation of gzip
and deflate
encodings. A new body
object containing the parsed data is populated on the request
object after the middleware (i.e., req.body
), or an empty object ({}
) if there was no body to parse, the Content-Type
was not matched, or an error occurred. This object will contain key-value pairs, where the value can be a string or array (when extended
is false
), or any type (when extended
is true
).
We will want to use the express.urlencoded([options])
middleware at the application level just like express.json([options])
and it's typically good to set the extended
property to false
(the default is true
) to ensure each value in the key-value pairs that make up the body
of the request
(i.e., req.body
) will be a string or an array:
app.use(express.urlencoded({extended: false}))
See this Stack Overflow post about using application/x-www-form-urlencoded
as opposed to multipart/form-data
when post
ing data using HTTP (typical when posting data via forms, for instance). As the top answer on the SO thread notes: "For application/x-www-form-urlencoded
, the body of the HTTP message sent to the server is essentially one giant query string -- name/value pairs are separated by the ampersand (&
), and names are separated from values by the equals symbol (=
). An example of this would be: MyVariableOne=ValueOne&MyVariableTwo=ValueTwo
."
Example of express.json
and express.urlencoded
middleware in action by making an AJAX post request
To make much of what appeared in the previous note more concrete, consider the following setup: in the root of the project folder, make a public
folder and create an ajax.html
file with the following contents:
<!-- ajax.html -->
<h1>AJAX test page has been loaded!</h1>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
function makeOurRequest() {
return $.ajax({
method: "POST",
url: "http://localhost:3000/ajax",
dataType: "text",
// dataType: "json",
data: {
name: "Daniel"
}
});
}
const theRequest = makeOurRequest();
theRequest
.then(response => console.log(`AJAX request successful! Response: `, JSON.parse(JSON.stringify(response))))
.catch(err => console.log(`AJAX request failed! Error: `, err))
</script>
Then at the root of the project folder create a server.js
file:
const express = require('express');
const app = express();
const helmet = require('helmet');
app.use(helmet());
app.use(express.static('public'));
app.use(express.json());
app.use(express.urlencoded({extended: false}));
app.post('/ajax', (req, res) => {
console.log(req)
// console.log(req.headers)
res.send('THIS IS A TEST RESPONSE AS PLAIN TEXT') // for dataType: 'text' in ajax request
// res.send({message: 'this is a test response as JSON'}) // for dataType: 'json' in ajax request
});
app.listen(3000);
Let's walk through what happens when you visit http://localhost:3000/ajax.html
:
- First, we can only access the
ajax.html
file because it is being statically served thanks toapp.use(express.static('public'))
. Once the document is served and loaded, the scripts in the file fire: jQuery is made available to us and then we make an AJAX request with the following properties:method: 'POST'
: We are making a post request (which will almost always be the case with form submissions and the like).url: "http://localhost:3000/ajax"
: We are making a POST request to the/ajax
route--Express will be responsible for accepting the request and putting together a response.dataType: 'text'
ordataType: 'json'
: What kind of data are we sending through?data: { name: 'Daniel' }
: The actual data we are sending through.- Finally, once the request has been made, the return value from the AJAX request is a promise. If that promise is resolved or rejected, then we will respond to indicate so and this can be seen in the browser console.
- In
server.js
, we have constructed things so that when apost
request is made to theajax
route, we first log the request in the Node console (and later the request headers specifically) withconsole.log(req)
(which will become relevant soon) and then we send our response (which we have set up to be either text or JSON).
Note about dataType and arg in res.send(arg) |
---|
The data type of what we send back in our response should match the dataType of the AJAX request. If we specify dataType: 'json' in the AJAX request but send back text (e.g., 'THIS IS A TEST RESPONSE AS PLAIN TEXT' ) as our response, then the returned promise from the AJAX request will be rejected. On the other hand, if we specify dataType: 'text' on the AJAX request but send back JSON (e.g., {message: 'this is a test response as JSON'} ), then what we actually get back will be the object as text which is not what we want: {"message":"this is a test response as JSON"} instead of {message: "this is a test response as JSON"} . |
The above note touches on making sure what is being sent and requested are as expected, but what we really do not want to happen is to leave off app.use(express.json());
or app.use(express.urlencoded({extended: false}));
in our server.js
file. Why? Run the server and keep an eye on the console (not the browser console but the console in Node) for what shows up when you visit http://localhost:3000/ajax.html
. With everything as it is currently, you should see something like the following towards the end of the logged request object:
...
body: [Object: null prototype] { name: 'Daniel' },
...
What does this mean? It means the data
sent through (i.e., POST
ed) by the AJAX request is coming through as a value (notably as JSON) on the body
property for the incoming req
uest object. This is great! What good would form submissions and stuff of that nature be if you could not actually pull the data from the form submissions effectively? That is what app.use(express.json());
and app.use(express.urlencoded({extended: false}));
allow us to do.
Try repeating the above process but this time comment out app.use(express.urlencoded({extended: false}));
. What do you see logged to the console for the body
object? You probably see a line like the following:
...
body: {},
...
Now repeat the process yet again but this time also commenting out app.use(express.json());
. What do you get for the body
object? Nothing at all! That's not good. Now, you could probably try to parse things yourself and find the data you want somewhere in the req
object, but why do this when all of it has been made easier for you? Express middleware to the rescue!
One point of curiosity: Why did we get the data we wanted when we used app.use(express.urlencoded({extended: false}));
but only got {}
when it was commented out? This has to do with the headers. Visit the page again without commenting out app.use(express.json());
or app.use(express.urlencoded({extended: false}));
while also changing console.log(req)
to just console.log(req.headers)
. You should then see a line like the following:
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
From above, we can see what the mime-type is: application/x-www-form-urlencoded
. Now that is why we need the app.use(express.urlencoded({extended: false}));
middleware. When a form comes through, unless specified otherwise, usually the default is going to be application/x-www-form-urlencoded
for the content-type
. That's typically how data is passed around by default. Oftentimes it will also be passed around as application/json
or text/json
. That's what app.use(express.json());
is for. But we need urlencoded
because if someone sends us data with the header shown above, then we need some middleware to parse it and the parsing result is made available to us in the body
object. The data is stored on the body
property of the req
object likely because the middleware is based on body-parser
.
From everything we have seen so far, it is safe to say good practice is to basically always include the following on any Express application:
app.use(helmet());
app.use(express.static('public'));
app.use(express.json());
app.use(express.urlencoded({extended: false}));
It will cover most of your bases and save you from a bunch of headaches of why something doesn't work, why your data isn't coming through, etc.
One last thing to note concerns the use of helmet
middleware. It sets HTTP headers right upfront and protects you from a bunch of well-known vulnerabilitie. There's really no reason not to use this middleware. It's very simple:
npm i helment // install helmet in your package.json
const helmet = require('helmet'); // import its contents (check npm for more options)
app.use(helmet()); // use it at the application level
Remember: When using Express, make sure to use your helmet
.
Using res.json()
to respond with JSON
In the previous note, in one of the examples, we manually tried to respond with JSON by including res.send({message: 'this is a test response as JSON'})
within Express and having dataType: 'json'
in our AJAX request. But we can do much better. Looking at the docs in the section about the response
object, we see res.json([body])
. This seems to be what we want.
From the docs: res.json([body])
sends a JSON response. This method sends a response (with the correct content-type
) that is the parameter converted to a JSON string using JSON.stringify. The parameter can be any JSON type, including object, array, string, Boolean, number, or null, and you can also use it to convert other values to JSON.
With this new method, we can drop the dataType
on the AJAX request completely and just use res.json
:
res.json('Some text');
res.json({message: 'an object without setting dataType on AJAX request'});
res.json(['Some text', {message: 'a message'}, [1,2,3], 4]);
As the docs note, any one of the above attempts to respond would result in the parameter (whether it be simple text, an object, an array of a bunch of other things, etc.) being converted to a JSON string using JSON.stringify
before being sent over the server (remember everything sent over the server is done so as a string ... we simply want the string to be a JSON string which can be effectively parsed).
To recap: By default, res.send
is going to set a mime-type of content-type: text/html
. If, however, we use res.json
, then the mime-type will be set as content-type: application/json
. The takeaway is that res.json
is incredibly important because anytime you need to respond with JSON, which will be very often depending on what you're building, then you're going to use res.json
and not res.send
. Any time you are going to respond with HTML, then you will probably use res.render
, something we will get to shortly.
Rendering Basics
Express usage: API development and/or server-side rendering (res.json
and/or res.render
, respectively)
Something to note which will become more and more evident as you build with Express: res.render
and res.json
loosely represent the two main things you would typically do with Express. To make the upcoming need/use of res.render
evident, let's consider a scenario back in the day where say you wanted to visit MySpace before Facebook killed everything.
You would get on your computer and go to www.myspace.com
and you'd connect with their servers. You'd send in a request and the server would need to kick back a response to your browser (which basically only understands HTML, CSS, and JavaScript). So the question becomes: What goes on inside of the server when your request is received? What happens inside? It may be helpful to consider what full stack development is really all about and to look at what makes up a server:
You have a number of different layers in the stack. From bottom to top:
- OS (operating system): The server is a computer and you have to have an operating system. You can't do anything without an operating system because you need some means of your software interacting with the hardware. Typically the OS will be Linux, Windows, UNIX, etc.
- WS (web server): Apache, Nginx, IIS, etc.
- DB (database layer): You'd have a whole host of SQL and NoSQL options: MySQL, PostgreSQL, Oracle, MongoDB, Apache CouchDB, etc.
- PL (programming layer): C, C++, Java, Python, Ruby, PHP, R, etc.
- Front-end UI (bonus): Not really a part of the server stack but part of full stack development (the front-end layer). You have React, Vue, Angular, etc.
The OS, WS, DB, and PL layers make up the server. When a user sends a request to port 80 (a port created by the transport layer), what happens? How does the server decide how to respond with HTML, CSS, and JavaScript? Since everyone on MySpace has their own page (just like Facebook), the question becomes: Does MySpace have individual HTML pages stored on the hard drive for every single user (millions of HTML pages, all very similar) that they serve up? Of course not.
What happens is that the user gets to port 80, and then the web server kicks into gear (say it's Apache). Suppose it's running PHP. It starts processing and interpreting PHP. It realizes it needs some stuff from the database (say from MySQL), go back to running some PHP, get some more stuff from the database (hopping back and forth, back and forth, ...). Eventually the back and forth process finishes, and Apache has finished all of its processing. It's read everything, and Apache sends it back out the door. At this point, then, PHP and MySQL have worked together to create HTML, CSS, and JavaScript. What it created was handed off to Apache/Nginx/IIS and that was sent back across the wire via HTTP.
That's how it happens. There's not an individual HTML file that is requested and then sent back. A whole bunch of stuff goes on inside of the server to ensure all of the proper information is being grabbed. A specific HTML file for you does not exist. What likely exists is some sort of template file. Every user page, no matter how complicated, appears basically the same. Maybe colors are different, the song was different, etc. But the structural integrity (think HTML) of each user page was the same. But all of the particulars about different parts of the structure depended on user-supplied information (stored in databases).
The key here is that the initial request goes out and grabs the HTML, CSS, and JavaScript once it's been made available. But the servers at MySpace are going to have to prepare a new response for every new request. Each new request is like starting over from scratch. So every time we go to our user page, the user page is built up and sent back. Built up and sent back. Every single time. This is called server-side rendering. Because the server is in charge of putting together the HTML, CSS, and JavaScript and sending it back to the browser. Wikipedia still does this. Each page you go to you've restarted the whole process from scratch. That is what we will be doing with res.render
. So within Express, we will be able to create a template, however complicated. And the server is going to process our template into HTML, CSS, and JavaScript. And the template will ultimately be replaced by user-specific information retrieved in a variety of ways. That is server-side rendering. The server is in charge of everything on every page load always.
The other weapon is res.json
. And what would happen in a more modern sense is you would go out to a place like Facebook and the first time you go out you have to get everything. You have to get all the necessary HTML, CSS, and JavaScript. Every following request, every time you click on something after that, because it is a single page application in the case of something like React, when you make a new request, instead of making a full-blown request, you are going to use AJAX and, instead of sending back HTML, CSS, and JavaScript, the server is just going to send back JSON. And that original HTML document which was loaded upon the first request, the JSON is going to go there and update the DOM. So it will look like a new page, but it's really just the same HTML, CSS, and JavaScript, but it's going to have some new data in it thanks to the JSON. Since we're using AJAX, we will still have a new request. We will still have a req
and a res
, it will still be our responsibility to handle that network traffic, but instead of responding with a template (with all the HTML, CSS, and JavaScript), we are just going to respond with JSON.
So the quick review: res.render
is server-side rendering whereas res.json
is going to mostly be for API/JSON needs. Server-side rendering is, "I am going to the server and every time the server is going to respond with new, fresh HTML, CSS, and JavaScript. Every single time. Always. Think Wikipedia. With res.json
, or an API type situation, you as the user are going to go out, hit the server, and the server the first time is going to send you a whole bunch of HTML, CSS, and JavaScript. But every time after that, the server is just going to send JSON. And the page or the DOM will update itself accordingly to reflect the incoming data (i.e., JSON) after an AJAX request. Think Facebook or Amazon.
In one case, you always have to come back to the server. The nice thing with res.render
is you can make use of session variables, cookies, etc. The user always has to come to the server for everything because the server contains everything. The other architecture is very fast, it creates a great UI/UX opportunity, but you have to start storing stuff on the browser you would maybe not normally want to store there.
View Engines
Making server-side rendering a reality
Let's wire up a basic Express server as we have done previously:
const express = require('express');
const app = express();
const helmet = require('helmet');
app.use(helmet());
app.use(express.static('public'));
app.use(express.json());
app.use(express.urlencoded({extended: false}));
app.get('/', (req, res, next) => {
// res.send('Sanity check')
// res.json({msg: 'Success!'}) // Send back some legitimate JSON
res.render('index') // We get an error without a view engine
})
app.listen(3000)
Right now, if we visit http://localhost:3000/
, we'll get an error:
Error: No default engine was specified and no extension was provided.
The error message you might get sometimes can be rather scary, but often a good strategy is to look for whatever files you have created. If we do that, then we will see (in my case)
at /Users/danielfarlow/[...]/just-express/express201/rendering.js:14:7
This is telling us that there is an issue in the rendering.js
file (which we have created) on line 14, character 7. In order to use res.render
, the process goes something like this:
- Typical Express build/app template: Express as we know it "happens" (i.e., our
rendering.js
file is nothing special except for trying to useres.render
in one of its route handling response methods; otherwise it's business as usual). All the Node happens. We include express (i.e.,const express = require('express')
), we make our app (i.e.,const app = express()
), we do our middleware, we build our routes, etc. - Specify view enginer: We define a view engine. There are several options (these are just a few of the most popular ones):
- EJS (Embedded JavaScript): From the home page: What is the "E" for? "Embedded?" Could be. How about "Effective," "Elegant," or just "Easy"? EJS is a simple templating language that lets you generate HTML markup with plain JavaScript. No religiousness about how to organize things. No reinvention of iteration and control-flow. It's just plain JavaScript. Install via NPM with
npm install ejs
. - Mustache: Logic-less templates for a variety of templates. If we were to use this, then we would be interested in mustache.js, a zero-dependency implementation of the mustache template system in JavaScript. Install via NPM with
npm install mustache
. - Handlebars: Minimal templating on steroids. Handlebars.js is an extension to the Mustache templating language created by Chris Wanstrath. Handlebars.js and Mustache are both logicless templating languages that keep the view and the code separated like we all know they should be. Install via NPM with
npm install handlebars
. - Jade/Pug: Pug is a high-performance template engine heavily influenced by Haml and implemented with JavaScript for Node.js and browsers. Previously named "Jade," it is now named "Pug" thanks to the fact that "Jade" was a registered trademark. Basically, Pug is a clean, whitespace sensitive syntax for writing HTML (see the Pug GitHub page or its API reference for more). Install via NPM with
npm install pug
.
- Employ
res.render
: Inside one of our routes we have ares.render
. - Give
res.render
a file and data: We pass thatres.render
two things: 1. The file we want to use (e.g., an.ejs
file, a.mustache
file, a.handlebars
or.hbs
file, a.pug
file, etc.). 2. The data we want to send to that file. - Combine Node code with HTML/CSS/JS via view engine: Express uses the node module for our specified view engine and parses the file accordingly. That means it takes the HTML, CSS, and JavaScript and combines it with whatever "node" there is in the file (i.e., the data available in
res.locals
). - Finished product (just HTML/CSS/JS): The final result of this process is a compiled product of the things the browser can read (i.e., HTML, CSS, and JavaScript).
All the steps above constitute "the round trip" for a res.render
. The templating engine serves as a bridge between Node and the front-end stuff. We can make a template out of HTML, CSS, and JS, and we can have a bridge that will allow us to access Node.js stuff. The specific bridge is the second argument to res.render
(i.e., the data we want to send to our template file that Node has access to). That object, the data we want to send to our template file, is made available as res.locals
. It will give us the ability to pass in a user's name, whether or not the user is validated, and generally any kind of data we might want to send over to the template. And then the template engine can fill out the HTML accodingly with the given data. So Express uses the node module for the view engine and will parse the template file out. It will combine all of the "node stuff" (i.e., the data we make available in res.locals
) and combine it with HTML, CSS, and JavaScript to return a product of only HTML, CSS, and JavaScript that can be sent to the requesting client.
Before we can effectively use res.render
, we need to use app.set
to tell Express what will be used as the templating engine. Note that app.set
is used for more than just this functionality though.
Note from The docs on app.set(name, value) |
---|
app.set(name, value) assigns setting name to value . You may store any value that you want, but certain names (like 'view engine' in our case) can be used to configure the behavior of the server. These special names are listed in the app settings table.Calling app.set('foo', true) for a Boolean property is the same as calling app.enable('foo') . Similarly, calling app.set('foo', false) for a Boolean property is the same as calling app.disable('foo') . Retrieve the value of a setting with app.get() : app.set('title', 'My Site') and then app.get('title') // "My Site" |
Underneath the high-level description above in the docs, we see a section about "Application Settings" that provides a table of different properties (where each property corresponds to a name
, the first argument to app.set
), the type for that property, a description of the property, and what the default value is. In particular, we see view engine
is one such property, it's a string, a description (the default engine extension, file name extension that is, to use when omitted; note: sub-apps will inherit the value of this setting), and finally a default value of undefined
(we will need to provide a value that reflects the template or view engine we want to use whether that's ejs
, hbs
, pug
, etc.).
With all of the above said, let's modify our server by running npm install ejs
and then add app.set('view engine', 'ejs')
and see if we encounter any errors:
const express = require('express');
const app = express();
const helmet = require('helmet');
app.use(helmet());
app.use(express.static('public'));
app.use(express.json());
app.use(express.urlencoded({extended: false}));
app.set('view engine', 'ejs');
app.get('/', (req, res, next) => {
res.render('index');
})
app.listen(3000)
If we go to http://localhost:3000/
, then we do get an error, namely the following:
Error: Failed to lookup view "index" in views directory "/Users/danielfarlow/[...]/express201/views"
So what happened? We failed to lookup view 'index'
. Why index
? Well, we told Express to go looking for a file index
by using res.render('index')
. What should the file extension of index
be? It should be .ejs
according to app.set('view engine', 'ejs')
. Well, we don't have an index.ejs
file. Furthermore, Express went looking for the index.ejs
file in the views
folder. But we don't currently have a views
folder. Why did Express go looking for this file in the views
folder?
The answer, as usual, is in the documentation. In the "Application Settings" table under the docs entry for app.set
we see a name
of views
that is expected to be a string or array, with a description (a directory or an array of directories for the application's views. If an array, the views are looked up in the order they occur in the array), and finally a default value of process.cwd() + '/views'
(i.e., the current working directory or cwd
with /views
appended to it; basically, it's just looking for the views
folder in your project directory). We're going to be more explicit than process.cwd() + '/views'
by using the path
module and using path.join(__dirname, 'views')
. What's the difference?
Note: process.cwd() vs __dirname in Node.js |
---|
Some helpful comments can be found on a Stack Overflow post. Basically, the cwd in process.cwd() is a method of the global object process where the return value is a string representing the current working directory of the Node.js process (i.e., where you are currently running Node or simply the directory from which you invoked the node command). On the other hand, __dirname is the string value of the directory name for the directory containing the current script. The big difference is that __dirname is not actually a global but rather local to each module. You can always execute process.cwd() to find out where the Node.js process originated or is running (you can actually change this with process.chdir but we do not need to worry about that right now).In a nutshell, knowing the scope of each makes things easier to remember. process is node 's global object, where .cwd() returns where Node is running. __dirname is module 's property, where the value represents the file path of the module. Similarly, __filename is another module 's property which holds the file name of the module. |
All that said, require
the native path
module at the top of your server like so: const path = require('path');
. Then, underneath app.set('view engine', 'ejs');
we can add app.set('views', path.join(__dirname, 'views'));
. Then create a views
folder and place an index.ejs
file inside of the views
folder with just a <h1>Rendered file!</h1>
for right now. So here are the three pieces to res.render
for a specific file:
- The file name: For example:
index
inres.render('index');
- The type or extension of the file: For example:
app.set('view engine', 'ejs');
. This tells us we will be looking forindex.ejs
. - The location of the file: For example:
app.set('views', path.join(__dirname, 'views'));
tells us the file will be in theviews
directory which should be at the same level at whatever script we are writing our code in (see note above). If we have more than one folder for the views, then the docs note we can include something likeapp.set('views', [folder1, folder2, ...]);
where each folder will be searched for the file until the first one is found. It should be noted here that Express will not search subdirectories you create within theviews
directory if you plan on creating subdirectories.
Using more than one view engine
In the note above, we learned the basics of wiring up Express with a view engine. It's entirely plausible (albeit somewhat unlikely) that you would want to use more than one view engine. Maybe you liks EJS for certain things and Pug more for others. Whatever the case, you can use as many view engines as you like. The only catch is you will have to be explicit for what files you want to render.
Recall from the docs concerning app.set(name,value)
, the 'view engine'
name
takes one value
, the extension to be used for a file name when the file name extension is omitted. Hence, if we declare
app.set('view engine', 'ejs');
and later invoke res.render('index');
, then we have basically told Express to assume there's an .ejs
extension on the end of the index
file name given to res.render
. If we did not use app.set('view engine', 'file-extension')
, then Express would not know what to do with res.render('index')
. Instead, we would have to explicitly (i.e., manually) include the filename extension like so: res.render('index.ejs')
. The upshot of all this is basically we should use app.set('view engine', 'file-extension')
to tell Express the default template engine we want to use when filenames are provided to res.render
when the file extension for the file name is omitted. If you want to use a view engine other than the default one set by app.set('view engine', 'file-extension')
, then you must explicitly provide the file extension for whatever file name you pass into res.render
.
Here's the process in more detail with a working server example below it:
NPM install the engines you need:
npm install ejs
npm install pug
npm install hbsSet the engine you want to be your default view engine:
In the commented out JS code block below, uncomment whichever line you want to set your default view engine.
Whichever one you uncomment means you do not need to provide the file extension for that file when the file name is passed to
res.render
. For example, if you uncommented the line setting ejs to the default view engine, then you could useres.render('index')
and Express would automatically look forindex.ejs
If you do not uncomment one of the lines below, then you will always have to manually specify the file extension for the file name passed to
res.render
.If you set ejs as the default view engine, then whenever you want to use hbs, pug, or something else, then you will need to explicitly provide the file extension for the file name passed to
res.render
.In summary, if we set ejs to be the default view engine, then we could use ejs, hbs, and pug in our application like so:
res.render('index'); // assumes the file is index.ejs
res.render('index.hbs'); // explicitly tell Express to use Handblebars
res.render('index.pug'); // explicitly tell Express to use Pug
/* Uncomment one of the lines below to set your DEFAULT view engine */
// app.set('view engine', 'ejs'); // EJS
// app.set('view engine', 'hbs'); // Handlebars
// app.set('view engine', 'pug'); // Pug
Render your template (be sure to set the file extension when rendering a template without the default extension):
res.render('index'); // assumes default use of .ejs extension per above
res.render('index.hbs'); // specify use of handlebars as hbs is not the default
res.render('index.pug'); // specify use of pug as pug is not the default
Finally, here's an example putting all of this together (see below the code block for directory structure and file contents):
// server.js
/* native node modules */
const path = require('path');
/* third-party node modules */
const express = require('express');
const app = express();
const helmet = require('helmet');
/* application-level middleware */
app.use(helmet());
app.use(express.static('public'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
/* configure view engine settings */
// Set the default view engine
app.set('view engine', 'ejs'); // uncomment to make default
// app.set('view engine', 'hbs'); // uncomment to make default
// app.set('view engine', 'pug'); // uncomment to make default
// Specify folders for Express to look in for views files
app.set('views', [
path.join(__dirname, 'views'),
path.join(__dirname, 'views/ejsViews'),
path.join(__dirname, 'views/handlebarsViews'),
path.join(__dirname, 'views/pugViews'),
path.join(__dirname, 'viewsFakeOne'),
]);
// For visit to the host root http://localhost:3000 (load index.ejs since ejs is the default view engine)
app.get('/', (req, res, next) => {
res.render('index');
})
// For all routes below, { name: ... } is being used as the "locals" argument to pass data to the rendered view
// For files directly in the views folder (which will be typical)
app.get('/sampleejs', (req, res, next) => {
res.render('sample', { name: 'EJS' });
})
app.get('/samplehandlebars', (req, res, next) => {
res.render('sample.hbs', { name: 'HANDLEBARS' });
})
app.get('/samplepug', (req, res, next) => {
res.render('sample.pug', { name: 'PUG' });
})
// For files in subdirectories of the views folder (somewhat common)
app.get('/subfolderejs', (req, res, next) => {
res.render('subEjsView', { name: 'EJS in a subfolder' });
})
app.get('/subfolderhandlebars', (req, res, next) => {
res.render('subHandlebarsView.hbs', { name: 'HANDLEBARS in a subfolder' });
})
app.get('/subfolderpug', (req, res, next) => {
res.render('subPugView.pug', { name: 'PUG in a subfolder' });
})
// For a file in a viewsFakeOne folder not within views folder
app.get('/fakeview', (req, res, next) => {
res.render('fakeview', { name: 'EJS in a viewsFakeOne directory' });
})
app.listen(3000)
Directory structure needed for this code:
express201
├── node_modules
│ ├── ...
├── views
│ ├── ejsViews
│ │ └── subEjsView.ejs
│ ├── handlebarsViews
│ │ └── subHandlebarsView.hbs
│ ├── index.ejs
│ ├── pugViews
│ │ └── subPugView.pug
│ ├── sample.ejs
│ ├── sample.hbs
│ └── sample.pug
├── viewsFakeOne
│ └── fakeview.ejs
├── package-lock.json
├── package.json
└── server.js
File contents:
// subEjsView.ejs
<h1>A template file rendered using <%= name %>!</h1>
// subHandlebarsView.hbs
<h1>A template file rendered using {{name}}!</h1>
// subPugView.pug
h1 A template file rendered using #{name}!
// index.ejs
<h1>Rendered template page!</h1>
// sample.ejs
<h1>A template file rendered using <%= name %>!</h1>
// sample.hbs
<h1>A template file rendered using {{name}}!</h1>
// sample.pug
h1 A template file rendered using #{name}!
// fakeview.ejs
<h1>A template file rendered using <%= name %>!</h1>
// server.js (the code snippet previously)
Statically serving an entire front-end site
As mentioned previously, we can host entire frontend sites using Express by just dumping everything in a public
folder and statically serving it. Execute the following commands in the terminal (e.g., bash):
# Navigate to desktop or wherever you want the project to be created
cd ~/Desktop
# Clone the repository at https://github.com/rbunch-dc/jquery-todo
git clone https://github.com/rbunch-dc/jquery-todo.git
cd jquery-todo
# Get rid of the git repository--we don't need it for this example
rm -rf .git
mkdir public
touch server.js
# Initialize a project using npm
npm init -y
npm i express helmet
npm i nodemon -g # if you haven't already
nodemon server.js
Now, move all of the files in the directory except server.js
into the public
folder (using VSCode or whatever editor you are using) and paste the following code into the empty server.js
folder and save the file:
// server.js
const express = require('express');
const app = express();
const helmet = require('helmet');
app.use(helmet());
app.use(express.static('public'));
app.use(express.json());
app.use(express.urlencoded({extended: false}));
app.get('/', (req, res, next) => {
res.send('index.html')
})
app.listen(3000);
Now navigate to http://localhost:3000
and behold!
Rendering
A preface about rendering in general
When using send
, sendFile
, or express.static
, we are always sending back HTML, CSS, and JavaScript. Why? Because the browser is on the other end and it is expecting ... HTML, CSS, and JavaScript. The goal of Express is to always get to this point somehow (i.e., sending back HTML, CSS, and JavaScript). The goal of Express will be either to send back JSON (like in the case of using React or some modern rendering for single page applications) or likely to send back something that is not a static product. That is, we do not want to send back static code (as is the case when just dumping a bunch of code in the public
folder). What if instead of using JavaScript to manipulate the DOM, what if we actually wrote the DOM the way that we wanted it from the beginning? So when the response is initially sent out, instead of having something where blanks are waiting to be filled in with JavaScript, what if instead of that the DOM were written so that when it showed up it showed up in the correct way? Then we wouldn't need JavaScript to get involved because the DOM would already be correct. We wouldn't need that extra step. But in order to know what would be in the blanks, we need information from Node. In order to pull that off, if we're not going to use JavaScript to manipulate the DOM directly, then we need something in between that can speak both Node and front-end. That thing that speaks both Node and front-end ... that thing is a template engine.
When a request comes in, Express does its thing internally (our routes and stuff). Before the response goes out, we send a template some Node (i.e., some data from Express) for the template engine to make sense of. Since the template engine speaks both Node and front-end, it will build HTML, CSS, and JavaScript for us, and then it will take that final product and send it back out as the response. So the main job of the template engine is to marry the data from Express to the front-end so that what we get in the end is not a static front-end site but a dynamic front-end site where we can build the DOM based on Node.js.
In a previous note we talked about a variety of things that went on to use res.render
. Recall the last 3 parts:
- Give
res.render
a file and data: We pass thatres.render
two things: 1. The file we want to use (e.g., an.ejs
file, a.mustache
file, a.handlebars
or.hbs
file, a.pug
file, etc.). 2. The data we want to send to that file. - Combine Node code with HTML/CSS/JS via view engine: Express uses the node module for our specified view engine and parses the file accordingly. That means it takes the HTML, CSS, and JavaScript and combines it with whatever "node" there is in the file (i.e., the data available in
res.locals
). - Finished product (just HTML/CSS/JS): The final result of this process is a compiled product of the things the browser can read (i.e., HTML, CSS, and JavaScript).
Note how step 5 is the translation part. It is where Express uses the node module for the specified view engine to parse the template file from a little bit of Node and some HTML, CSS, and JavaScript to only HTML, CSS, and JavaScript.
A note about res.locals
and passing data in res.render
As noted in the docs for res.render(view [, locals] [, callback])
: Renders a view
and sends the rendered HTML string to the client. Optional parameters:
locals
, an object whose properties define local variables for the view.callback
, a callback function. If provided, the method returns both the possible error and rendered string, but does not perform an automated response. When an error occurs, the method invokesnext(err)
internally.
There's more from the docs, but what's important to us is the first point about locals
. Whatever data is passed to res.render
as the second argument is automatically appended to the locals
object available in whatever view we are dealing with. Not only that but we can use the data property names directly for their values instead of having to worry about a bunch of destructuring (unless we want to).
As an example, suppose we have the following in our server file:
app.get('/', (req, res) => {
res.render('index', {
msg: 'Here is a message.',
secret: 'This is a secret',
friends: ['John', 'Jeff', 'Eric']
});
});
And suppose our EJS view is like this:
<h1>Silly example of locals object and its use!</h1>
<h2>The message</h2>
<li><%= msg %></li>
<h2>The secret</h2>
<li><%= secret %></li>
<h2>The friends</h2>
<li>As a basic array: <%= JSON.stringify(friends) %>. But below we list them:</li>
<% for (let i = 0; i < friends.length; i++) { %>
<li><%= friends[i] %></li>
<% } %>
<h2>The entire locals object</h2>
<pre><%= JSON.stringify(locals, null, 2) %></pre>
Then what you will see will be something like the following:
Look at all of the properties on the locals
object! In particular, note how our data was appended to the locals
object and made available as local variables in the view. We didn't have to use locals.msg
or anything like that. We also never had to do anything like res.locals
because it is already a given that we are inside of the response
object (i.e., res
) when dealing with our view. This should make sense because we are, after all, inside of res.render
when specifying what data gets passed to the view template. In fact, trying to access res.locals
won't even work inside of the template file because that's basically the same as trying to do res.res.locals
since we are already inside of the response object. So when using locals
just remember you are already inside of the response
object and that your data is accessible directly using the data property names instead of locals.<property-name>
.
That is why templating is powerful. We essentially have a bridge between our .ejs
, .hbs
, .pug
or whatever template file/engine you use and the Express server. The locals
made available in the template comes from the route visited by the user (and whatever middleware is involved in that process). When we're ready to build the DOM, we need something from Express, and we'll write it to our response before it's ever sent out so the browser will never have any idea what's going on behind the scenes.
It's important to note that res.locals
lives throughout the lifetime of preparing the response
(i.e., res
). When we are actually ready to render and use res.render
, the second argument passed in to res.render
, if any, will simply be appended to whatever res.locals
already is up to that point; that is, we have lots of opportunities between receiving the request
and calling res.render
to modify the res.locals
object using middleware.
Let's consider a somewhat contrived example to make this more concrete. Suppose we have the following in our index.ejs
file:
<h2><%= bannerMsg %></h2>
<h2><%= _locals.bannerMsg %></h2>
<p style=<%= userType === 'premium' ? 'color:green;' : 'color:red;' %>> <%= greetingMsg %> </p>
<h2>The entire locals object</h2>
<pre><%= JSON.stringify(locals, null, 2) %></pre>
And we have the following in our Express server file:
const path = require('path');
const express = require('express');
const app = express();
const helmet = require('helmet');
app.use(helmet());
app.set('views', path.join(__dirname + '/practice-views'));
app.set('view engine', 'ejs');
app.use(express.static('public'));
app.use(express.json());
app.use(express.urlencoded({extended: false}));
function randomNumInclusive(min,max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function validateUser(req, res, next) {
res.locals.validated = (randomNumInclusive(0,1) === 1 ? true : false);
next();
}
function userType(req, res, next) {
let {validated} = res.locals;
res.locals.userType = (validated ? 'premium' : 'basic');
next();
}
function greetingMsg(req, res, next) {
let {userType} = res.locals;
res.locals.greetingMsg = (userType === 'premium' ?
'Thanks for using premium! Drop us a line if you see room for improvement.'
: 'Enjoying your basic plan? Consider upgrading to premium!')
res.locals.bannerMsg = 'bannerMsg set on res.locals during middleware sequence'
next();
}
app.get('/', validateUser);
app.get('/', userType);
app.get('/', greetingMsg);
app.get('/', (req, res, next) => {
res.render('index', {
bannerMsg: 'bannerMsg passed manually in second argument in res.render'
});
});
app.listen(3000);
First let's take a look at the two possible outcomes and then tease apart what all is happening/happened:
The randomNumInclusive
function is only meant to ensure we get a random number between 0 and 1, inclusive, to simulate randomly determining whether or not a user is validated so as to start the cascading effect that will become apparent:
app.get('/', validateUser);
: Express knows to run thevalidateUser
middleware as soon as a GET request is made to the root. We randomly determine whether or not the user is validated and store the result on theres.locals
object asvalidate: true/false
. Sincenext()
is in this middleware, control gets handed off to the next piece of middleware.app.get('/', userType);
: This is thenext()
piece of middleware referred to at the end ofvalidateUser
. TheuserType
,premium
orbasic
, is determined at this step based on whether or not the user was (randomly) validated from the previous piece of middleware. Note how we actively useres.locals
in this piece of middleware to further add data tores.locals
in the form of whatuserType
we have. If the user is validated, then theuserType
is set topremium
. If not, thenuserType
is set tobasic
. Control is now passed to the next piece of middleware.app.get('/', greetingMsg);
: This is thenext()
piece of middleware referred to at the end ofuserType
. A greeting message is formed based on whatuserType
we have. Control is now passed to the next and final piece of middleware whereres.render
is called.- We use
res.render
: At this point,res.locals
has a number of different properties and corresponding values on it. Note that we do not have to passres.render
a second argument in order to have access tolocals
as local variables in the template file. That is, when we callres.render
, it is up to us if we want to pass additional data to be appended tores.locals
. At this point, we can use anything/everything on thelocals
object in the template file.
Note: Properties and values manually set on res.locals
throughout the middleware process before calling res.render
are available as local variables in a template file, but what if one of the property names manually set on res.locals
conflicts with a property of the same name given as part of the second argument to res.render
? If we use the variable name just on its own, the last one assigned "wins" or takes precedence. But note that we can still access the one we set manually by accessing it through the _locals
object on res.locals
. Properties and values manually set on res.locals
are appended to the _locals
object on res.locals
. Hence, if we still want to access the property value we manually set, then we will need to use _locals.<property-name>
. For example, suppose we set
res.locals.bannerMsg = 'bannerMsg set on res.locals during middleware sequence';
somewhere in the middleware process and later invoked the res.render
method like so:
res.render('index', {bannerMsg: 'bannerMsg passed manually in second argument in res.render'});
If we just try to access bannerMsg
in our template file, then we will get 'bannerMsg passed manually in second argument in res.render'
. However, we can access _locals.bannerMsg
and that will give us 'bannerMsg set on res.locals during middleware sequence'
.
Passing data that we trust (example of unescaping HTML using EJS)
As the EJS docs note:
<%= val %>
: Outputs the unescaped value ofval
into the template (HTML escaped)<%- val %>
: Outputs the unescaped value ofval
into the template
How could this be useful? Well, suppose we have some HTML in our database that we want to retrieve and drop into our template. The default way of dropping stuff into the DOM <%= val %>
will result in literally printing the HTML string in the DOM (since the HTML is escaped for <%= val %>
) which is obviously not what we want. We don't want to escape the HTML in this case. We want the browser to interpret it. Hence, in such cases, we use <%- val %>
.
Why would we normally not want to use <%- val %>
? Well, HTML is generally considered unsafe because if Express or the templating engine doesn't know where the HTML came from, then it could be that someone sneaked a script
tag in there and is trying to do some kind of cross-origin attack or something of that nature. So by default HTML is escaped. But if you want the HTML to be evaluated and you trust the source of the HTML, like if you yourself are pulling it from your own database, then instead of using <%= val %>
you will use <%- val %>
. This will print off the HTML and tell the browser to interpret it rather than to just print the string off as text. Basically, using <%- val %>
indicates that you trust the data. You know that it's safe because it's coming from us somehow or we just trust it for whatever reason. It is the only way to get escaped data out and to have it print as desired.
Consider an example of having a company directory where all the pictures were stored in base64-encoded format. So in the database you'd have a large string representing a picture and anytime you'd want to drop the picture in the DOM, well you would want to use <%- val %>
very likely.
Example with base64-encoded picture
The .ejs
file:
<h1><%= msg %></h1>
<h2><%= msg2 %></h2>
<h3><%= validated %></h3>
<%- html %>
The Express server.js
file:
const path = require('path')
const express = require('express');
const app = express();
const helmet = require('helmet');
app.use(helmet());
app.use(express.static('public'))
app.use(express.json());
app.use(express.urlencoded({extended: false}));
app.set('view engine', 'ejs')
app.set('views',path.join(__dirname, 'practice-views'))
app.get('/about',(req, res, next)=>{
res.render('about',{})
})
function validateUser(req, res, next) {
res.locals.validated = true;
next();
}
app.get('/', validateUser);
app.get('/',(req, res, next)=>{
// the data, in the 2nd arg, is going to be appended to res.locals
res.render("indexWithUnescapedHTML", {
msg: "Failure!",
msg2: "Success!",
// HTML came from the DB and we want to drop it in the template
html: `<p><img src="data:image/jpeg;base64,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" /></p>`
})
})
app.listen(3000)
Include other files for more robust templating (example using EJS)
One thing that's awesome about template files is the ability to pass around code amongst the templating files themselves. That is, basically you can make template files to make your code more modular. Maybe you always want the head for your HTML files to be the same, the navbar the same, etc.
For example, suppose we make a head.ejs
file where we will put regular HTML header stuff (the idea is that we want this to appear on every page, hence the template):
<!-- head.ejs -->
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<link rel="stylesheet" href="/css/styles.css">
</head>
We can now create another template for the navbar:
<!-- navbar.ejs -->
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="#">WebSiteName</a>
</div>
<ul class="nav navbar-nav">
<li class="active"><a href="#">Home</a></li>
<li><a href="#">Page 1</a></li>
<li><a href="#">Page 2</a></li>
<li><a href="#">Page 3</a></li>
</ul>
</div>
</nav>
Finally, our index.ejs
would look something like the following (make sure to unescape the HTML you are including):
<!-- index.ejs -->
<%- include('./head') %>
<%- include('./navbar') %>
<h1>Home page!</h1>
<%= msg %>
As the last step we have our Express server.js
file we have been making every step of the way:
const path = require('path');
const express = require('express');
const app = express();
const helmet = require('helmet');
app.use(helmet());
app.set('views', path.join(__dirname + '/practice-views'));
app.set('view engine', 'ejs');
app.use(express.static('public'));
app.use(express.json());
app.use(express.urlencoded({extended: false}));
app.get('/', (req, res, next) => {
res.render('index', {
msg: 'a message'
});
});
app.listen(3000);
Using <%- include('./head') %>
is the equivalent of basically copying and pasting all of the code in the head.ejs
file and dumping it at the top of the index.ejs
file. The idea is that if you have multiple pages then you can just include
whatever you want on whatever page and keep things nice and tidy. Assuming we have app.set('view engine', 'ejs');
in our Express server, which we do, then we can omit the file extension when we use include
with EJS. If, however, EJS is not the default engine, then you will need to include the file extension.
Finally, we could easily make an about page by including the head and navbar and then something else by making an about.ejs
file like so:
<%- include('./head.ejs') %>
<%- include('./navbar.ejs') %>
<h1>About page!</h1>
<%= msg %>
Then we can add the following to our Express server:
app.get('/about', (req, res, next) => {
res.render('about', {
msg: 'an about message'
})
})
The effect is that our about and home pages look the same in the way that we want them to look the same.
Adding styles, scripts, etc., to template files (properly setting href
, src
, etc.)
Suppose your directory structure looks something like the following:
project
┣ node_modules
┣ public
┃ ┗ stylesheets
┃ ┃ ┗ styles.css
┣ views
┃ ┣ login.ejs
┃ ┗ welcome.ejs
┣ package-lock.json
┗ package.json
Within login.ejs
we may want to use some styles from our stylesheets
directory. Why might it make sense to have a link
tag as such:
<link rel="stylesheet" type="text/css" href="/stylesheets/styles.css">
The answer is that since the styles are statically being served from the public
directory and we have used app.use(express.static('public'))
, Express will look through the public
directory for a folder stylesheets
and a file styles.css
within that folder. Express knows to do this because when it looks through the public
folder the path is simply
/Users/danielfarlow/Desktop/[...]/project/public
Hence, if we set the href
to href="/stylesheets/styles.css"
, this is equivalent to
/Users/danielfarlow/Desktop/[...]/project/public/stylesheets/styles.css
The same principle can be applied to scripts or whatever else you may have in your public
folder or wherever it is you are statically serving files from.
Basic examples (using EJS, Handlebars, and Pug)
The best way to learn is to consult the docs for each view engine:
Since most of the previous notes relate to using EJS, the examples with Handlebars and Pug are more sparse. Nonetheless, hbsPractice.js
and pugPractice.js
files have been provided as the Express server, where they are intended to serve up the index
file in the practice-views
folder accordingly (i.e., index.hbs
for handlebars and index.pug
for pug). The strictly necessary code blocks/files are provided below for each view engine though. In each case, the index
file is the view being rendered while the other file acts as the server.js
file that we need to start up with node
or nodemon
.
EJS Rendering Example
index.ejs
<h1><%= msg %></h1>
<h2><%= msg2 %></h2>
<h3><%= validated %></h3>
<%- html %>
ejsPractice.js
const path = require('path')
const express = require('express');
const app = express();
const helmet = require('helmet');
app.use(helmet());
app.use(express.static('public'))
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.set('view engine', 'ejs')
app.set('views', path.join(__dirname, 'practice-views'))
app.get('/about', (req, res, next) => {
res.render('about', {})
})
function validateUser(req, res, next) {
res.locals.validated = true;
next();
}
app.get('/', validateUser);
app.get('/', (req, res, next) => {
// the data, in the 2nd arg, is going to be appened to res.locals
res.render("indexWithUnescapedHTML", {
msg: "Failure!",
msg2: "Success!",
// HTML came from the DB and we want to drop it in the template
html: `<p><img src="data:image/jpeg;base64,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" /></p>`
})
})
app.listen(3000)
Handlebars Rendering Example
index.hbs
<h1>HBS Rendered file!</h1>
Your message is: {{msg}} !!!
{{{html}}}
{{!-- Country name: {{country.name}}
Country capital: {{country.capital}} --}}
{{!-- each, if, unless --}}
{{#each countries}}
{{#unless this.western}}
<li>{{this.name}} -- {{this.capital}}</li>
{{/unless}}
{{/each}}
hbsPractice.js
const path = require('path')
const express = require('express');
const app = express();
const helmet = require('helmet');
app.use(helmet());
app.use(express.static('public'))
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.set('view engine', 'hbs')
app.set('views', path.join(__dirname, 'practice-views'))
app.get('/about', (req, res, next) => {
res.render('about', {})
})
function validateUser(req, res, next) {
res.locals.validated = true;
next();
}
app.get('/', validateUser);
app.get('/', (req, res, next) => {
// the data, in the 2nd arg, is going to be appened to res.locals
res.render("index", {
countries: [
{
name: 'Russia',
capital: 'Moscow',
western: false
},
{
name: 'England',
capital: 'London',
western: true
}
],
msg: "Failure!",
msg2: "Success!",
// HTML came from the DB and we want to drop it in the template
html: `<p><img src="data:image/jpeg;base64,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" /></p>`
})
})
app.listen(3000)
Pug Rendering Example
index.pug
doctype html
html
head
title This is Pug
script(src='https://code.jquery.com/jquery.js')
link(href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css', rel='stylesheet')
body
h1 Rendered Pug file!
div
p A message!
p Another message!
div.container
div.row
div.col-sm-3 #{msg}
div.col-sm-3 Your message is ... #{msg}
div.col-sm-6 Your message is ... #{msg2}
div.col-sm-12 !{html}
div.row
ul.col-sm-12
each country in countries
li= country.name + ' -- ' + country.capital
if country.western
div.western This country is in the western hemisphere
div Message of the day: 2+2 = #{2+2}
pugPractice.js
const path = require('path')
const express = require('express');
const app = express();
const helmet = require('helmet');
app.use(helmet());
app.use(express.static('public'))
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.set('view engine', 'pug')
app.set('views', path.join(__dirname, 'practice-views'))
app.get('/about', (req, res, next) => {
res.render('about', {})
})
function validateUser(req, res, next) {
res.locals.validated = true;
next();
}
app.get('/', validateUser);
app.get('/', (req, res, next) => {
// the data, in the 2nd arg, is going to be appened to res.locals
res.render("index", {
countries: [
{
name: 'Russia',
capital: 'Moscow',
western: false
},
{
name: 'England',
capital: 'London',
western: true
}
],
msg: "Failure!",
msg2: "Success!",
// HTML came from the DB and we want to drop it in the template
html: `<p><img src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBxMTEhUTEhMVFhUWGRoWFxgXGBgXFxgaGRkdGBoYGhcYHSggGxolHxgeIjEhJSkrLi4uFyAzODMtNygtLisBCgoKDg0OGxAQGy0lICYtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLf/AABEIAKMBNgMBIgACEQEDEQH/xAAcAAACAgMBAQAAAAAAAAAAAAAEBQMGAAIHAQj/xAA+EAABAgUCBAQEAwcDBQADAAABAhEAAxIhMQRBBSJRYRNxgZEGMqGxQsHwByNSYoLR4RRy8RUzQ5KiFiRz/8QAGgEAAwEBAQEAAAAAAAAAAAAAAQIDAAQFBv/EADARAAICAQMCBAUEAgMBAAAAAAABAhEDEiExBEETIjJRBRRhcYFCkaHB4fBisdEj/9oADAMBAAIRAxEAPwBspIGdsxlEGTuFBKiGRcklgQOQBWXyAw8rRojRhPIEBhdSU3JAwkdz54j1X8X/AOP8njL4I3zP+P8AJBK0ylB0gkdRjLZxG6tCsAkpYAEm6cDO8EanWTDNQkEUvSzuAwKnLNskh+4y7R5xfV0ghLAMz3Pfa4iMvi2V+lJF4fBMP6m3+yF5KRYliWDHN8Wj0I6QtnzWKXKSupIPKMEhnLZAL38oNQlkrDutKD4fVTKLlZdiA9nYCrNorj+LS/XH9iOX4JGv/nL9/wDBL4cZ4ce6NalJBIB6kY3v5WidAcAjBxHp4uqx5V5WePn6PLgfnX57A1EZ4cE0RlEX1nPpBqIyiCaIyiNqBpBgiPaIIojKY2oOkHojKIJojKIGo2kHojKIIpjKI2o2kHoibSaVS1BKQ5+3fyjaiLD8OaNkqWXDlh5Df3+0Rz5/Dg2dPS9P4uRR7dwTiXCxKlg1FS1ED+W8DS+BKJDqDHPaLaUA2IeNJ/Dpa9qT1TY+o3jzYdbJKm/yezk+Hwk7S29ivztNKkC6aiUtlwo72MKp8kG6bC1ovo0KCgJUHaz49ukezOHSlAAoBCcdoMOuUebbFyfDnNUqS7Kv7OeoqTcOAfrFk4F4tLlm7m/tD9XD5TjkTYMxAYeQwImRp0gMAITP10cka0j9N8PlileoglahsxMrUDrGqdNtA+qkDeOCotnpbpEpnjrHqZzwBL0yTa4gyVowncmM0kZNs3SY3AiQoAEaiFsNGJX1iUKgeamNDNAOY1BDgqPCuIc7tEUySBcOT5wtBC6o0MAf61vmYdojm8Vlp/EPeDpYLQxCYyEp+JJIsVRkHRL2BqXuKdYlqi5c1FiQRZ3BD/oGB+HoUSlmfJOX6ntfz+hjaTrUz3Wlil1F0gitmBPNkd8Fu0eTAU00kDfrupnDYdz1tHK2dUUTaWQklSS5NYU7H+l2s7jNsDdoG4/oSmWp/lSa3CsAXdlY3t3HeC+H6pc1KyaOYoDgEDcv5sAfWCZ2lMyyjZjU+LkPtccpy/q0ZOmY5ZrZ6gqaJtQCEeJUQXdJsw7t9odfCWpUrWrXY+HLuQXCSZiVbAufmtkti8DfGOmQk6ikC6cAsFIT84ANiaUE9W8hHvwnLCkzAlx4xDKs4R4RLkAkO8wsC7knpHTtosl+ouqp6WWpCHUfEKE0l7l1czMkgJxfs7xTuHadUvTeOqaqozly0osoMlVO2TY33i68I1EtRUkEApXyXYmpDKAG4ZzFN+KJdMuTJQ1KtROYIJJNSFG4AZgZiPXyiOOTTpDTimtw/h3EBMRWHKQsyyprFQewxbv2Ig6WUqBIIt3H5GFkvThEiVISWVKleMQRyhZW4KwMhIJLA7+TL9TpCJ2nlpWQJiFplklSnmJQJiSoOAXLDfLdX9DF12SLq7X1PNz/AAzDPdKn9P8Awsxlx54cLfggzFaX94VKKJq5QUrJoax8nb0h8ZcevDLqVnz+TBok4vsB+HGeHBfhxnhw2sTQC+HGeHBfhxnhxtZtBDp9NUcs0ZOkMcNBCEXgvUTkkCztE3kakXhig4O9mD6HTprlgpck36N/eLaUggCEmhmoUoJpAYZbfe8NioNYx53VScpKz2eihGMHVGx042MeCQXzEBnnrEnjvHNTOy0bgEWjZEzaNpbKiZSBCNjJGoMepAEakjrHhWIATcriGeQY0mL6QFqZ9NlQVEDZIWEFSZtormo1TbxAOM0/ieKvG2JrSLb48eTprByfeKbP+KqQSA7bO0U/W8Ymz1EzpzhJVyhglmFgh8uxFTnl2vEMnkKQ8x1wzXuMdYHXTu/oY4lxL4jMs0SSrIc3YkMQWBGDEnD/AI61SWTNZQ2Jdxfc9L9/KGg21bRpKuDsGrmy0IKitYA9foIR/wD5Cg/JMVuOYFOC2+9oruh+IU6j/wAibMWq/E+OtmuRC3i8mYo5AclylNsKDpc3ukZO4xE55dMqW6GULjbLFreIg/jc+cJp+t9Yj0+kDJQFpUpgXYJDszirftn1tBatRKkEeOjxUvfw/mSG9AS/lHRi6vG9nsQngkt1uLF6kxkWbhug0erFWmmhX8SFGmYjspOez46ExkdilBrk5nadUx5M4dQUc5SlgFBICU8uXG34vp0gbUSQbYLu5ybAbbWeLDOkBVTi298uHVnz+8KdRpkJU7GwTdiQ5wXFzjytHhSPYgwbh0xSSlDBnUqq7uRkWt/n3aaxIqUoMWSE3wCCd/JR9oEl6b94hRAuDc9lJxb2Hcwi+Nfin/TDw5bFbgltrP8AkIMIuTpGlJLdlV/aNMCZiQSaihYF9wAlz/8AXvBHwryolAH+FYD3ShICiS24TMXbofSKxxviX+pWkrZ2KX6VKz5t9YtfACkTgjZWnow7HwpUsgY8o6mnGCTI3cm0WDgqvBnVBNSl+JbNvEIACujpWf6ugYgyp0tQCxLKiJ3hrWoAqNCqyEPegFQTsOXtD+ZogCgpDEkOCXIYzFEepmHHSKTKmpPiE1ciyE9CSlK7XZ6lmIcj8Fq03DqUlKAkqpKVG4qC6UgktlkgenlFY+IJ/hK08xYA8DUeI6X+TxUco/pf0SItnAjypIIKVJKVF3D3UD7ERWP2gyuUqexQFY/iCVel42F+en7hyenYZ/s1WoydXKZ6dQVA/wC4AEf/ACD6xbUcOUSA2b+kU/8AZbq2RN/mXUbZPhSX+pV7x0iTNqAKfWPUjllGOx5OXpoZJ2yCRwhG7mN5nCJZwCPI/wB4NqiR3ESead3ZZdNiqtKFsvgiGuSfpHiuCo2JhmFRgMDxsnuD5XDXpQnVwI7KHqIjHBVDLN2h+lUbQfmciF+Sw+wq02jQAzX67+8TI0jAh/KJNZpiQSlVPpC9OqFIFbKILZIcRrlLdMpphDajzUS2e8AjWsWjTU6hSSQth+Y6wErVAiLxx7bk3P2HUvW2cFo2VxE7mK1OmrSbJMQL4gpOQRG8BM3jFnPEB1iKZxLoYqs3ipNmiRGi1C8S1X62jeAlybxr4G2p4uRuBCvVcXfcn1gKbw3UHEpR8gT9oEPD5xfkVbLgxRY4iubJpmpKjYmB5iepjVeinJ2aIRpZijf6xRRQrkwzSyUXUpykDYAuXDAA7wu49wNAWGZD1FJfLnlDt8twItGk0yaES0tUlJKnwVE2fsGvE2s0SwQ6HYEWwEiwZti/6aPEzZnObZ6mPGoxo5BxLReGq5vv/gx6RUAbve0OPibTpVMoSXULkuOgt3hMUGW6VWIv+hF4O1uTmqewLOTQsKHuDce0Fj4imAEHmSb3d3YDO1hAmsll3PoAIGUkHI/tDShGXJPcYTPiWccMLuw+zn8+kRDiCpvKtbjzL/5gI6azgvEaUGF8OK4QLZZtAlKQ4W2zk/3/AFmMgf4f4L4oUtRNI5drntdyA0ZHPKCvdlYp0fQa5YUgAXSUi4IIIZreYAbzgXWyTUL9G6ctX9zf0vHM/g/imo08j9yjxELIdCybXL0kfI9QBLEOnGSekcK4nL1aEKRYlxMSSCqWd0qT1sWcXF4jJMvGQt+J+ITdNKPhh7fMTZyWNh269I51xXhswpVPnElaklXMC7Uslh1YfaOx8S4agpIIJDXvsL72JhT8TSkHSzCaflDdGy3k/wB4tjloXAslqOCzSUqcOG94s/whxWvVaRJZ3MvsxrU/YlQT7CE2v4V4SJRJNUxJUQcghs+ZUfaF+kWZcyWpNik58o7pRU4nMpOLPoGYmoSTg1qGeqFW87GKPr9GAqYgnlVNqWpmZLqlpSGs5Mn/AOxvDn4U+I0TZKgtQTNlzamP4k11W9CR5QP8UpCJimAdc0m7u0v95LADsAK1PbYR58U4y0s6m01Zv8N6o+DOQpgBPUkMC4FKbX2YpHqYX/G8oK0wVY0oCerKASCfYEx7wLTkSJ/OFKK/EUQXasJ6YahmvG3EJR1GnWkD/tOJjFsoLC+XKVY2F2g1U7+puY0J+E8WTopennEFUta5aZiQ7qQvTpqUP5kqlhurEbvHYuD62ROlCZIWmZLVgpxaxBGQRuDcRwj4ikFGikJUlplcslzsJBSBYswUFe5iX9nPxWNBNWmc/gTSApgTQsfLMYbM4IFyKc0gHuUW4nG3TO/kiNawIXydQFpSuWakrDpUkulQOCCMiNZqVdDAUDOQzCnxGPAUjUsGMaK4gxuA0DQzakHKlqyC0b6dRcgm8L1cUBxGydaI2iVbh1K9hqRAsxIJZQFsRpL1Y6xtM1aDkiESaYzaZHquHpXlr5sPvA2i4HLQai58y4jbWalkjw1PAaOKlINYsLv084vFZHHZkpOCluhrN4cgsQkWjxWhCvmCe3KD94qOq/aBpkVcy1EBwEBwe1Rt9YXK49xTUSwuSZMiWqpnYrZId3UDYg2YfhPqHimuXX3CskHwi/6fh6Umo0MP5QG9YW8a+MdFpVFEyaCsZQgVqFwLtYZdiXYGOZzNDxGepVeoWoEFKyFmkggOmkMMKuOx3EAzuBhEyguWy3ufVoPhxvzSv7A1yS2VFw4j+1RDNptOpy/NNYAH/agl/cRW5vxPrlEqGoUCdqUAObsE0sfvGiNHJQAxBKnZwbixvfIBfa46QXLnJqCkILfKLkVWbF+j52HWIPqYQlUY39SiwzkrbDD8W6mXLbUSErUQChSVABT4dIf6e0aaTjU+csVSBKkOAWS6ySeUDo5IexYPvGqDVdfzJsks/Km1z1cYG2N4O0iHP8qQLepwzM2b9RAl1SapKhl07TtjXRz0pcgqGLnOS4uB+Fh+nio8Y+K5pUpEokJAp6vdi5/PvF2EpKZUxRyA/wDj6Rz3h/Cpk6YaQwrqc9HufS/tEMairbLTt8CCbLWtXMOb1uWYnptt+cQf6dS1iq7Wu4LDZ3i2DQoQFK8RJUjmLF+Xm5h1xjoHxFb1E5YWSk2KiQCAC3Rh5xdT1cE9OkGPDpmwLD5T1HR3iCfplA3Hr5w74NqFFbHG72HT2EN+IcNUVUmWQbMC2/Rji4N9mhXNp0x4wVWijiSx+hg/hPCVT5gSCycqV0HX8osavhJDgqWcXADMds+u0WLh2kRJliWlBQrNTukhgSQcl7C/frCy6hJVECxNvcj0WgEtICBYBgHsBnZi8exbpelQhIbmxcEHbrZ48jieVWX0nIeF8RmadKpEyWVAhbTZbnmUoKdVuXDbQy+F9fOkLOolTEhBtMMwEgAP8+FEByas2s73GRJTNKaVpVLWAEqD2P8ACW/EXAdRIsbQMdYZJWkqBQsUlFyO72YAMLj16xbU3yc6dcnStT+0LRhSUaoLkqZRWkipIUklBS6bkEgsWDtdoz9oYI0aTINVdbUlwQJM2YCCDgUAiObccR/q0JdRBCqqqHyWJNw4JUHN7tDz4Y4rMQmRodcAJaSPBmgiikilMpVIBS5NitncgxSMotJrn2CnvRX/AIx1SipAalquXpZP0tAmr0YBKhhRJDdAsgf+zfWLb8T8J8Y1nAlCnupy7N1CT/mBdLwQqFLEcqGI2YUmw8n7kk7x1RmkkI4ttldnhlOCQ5JB9TDbi3xIudLQCf3iCTVvdID38n9O8R8e4GuWyrU3vuP0DCEJc+0PojNpianFUdCkatUopQmnwzppBSDcqWZQLXsAA5fckAQR8Ia5M08pDrFKknpUtAJH9aT6RzpWsmAJSVE0WTf8IHKB2H5RHoNVMQsLlqKSC9s5eJy6a0Os5e/2hcM/c+IAeRSH2dLFJt2UtP8A7HMc4nyrH9dYsk3juomS1S5igoLQU9wM+hsLxX1odJO946MMHFUyOSSb2Ld+y/4qXpdRL061gaeaohQVhCiGStJ/C5YHZvJ47pMQDkAx8qagDltYx2L4c+J5szTSjdSkpCFElyVI5aj3LP6wzwOcvKTeVQW50RchByIgmcLQoWJB84rmm4hqlKYC+W3bq0FK1upd6qUuzM56H29IDwzi/Uv3MssZL0sLXwNexEBTeGzu3uIF1vHZyTZSlAWYhN+pJT/xHmh4suatKAUgqUAUuSQP4sMRvmLKGVLU6JOeNulYbodLMqZSSAASVG48h3hlK0SFocLPsLen+YUa/VKpnSD/AAkF+gu79G+0I0cbXLlolyVEKAvgtVdr52vE5qTWq6Hi4rZqyw6jUokWmmp/lpd1dxew7m0J0zBOUCouCQQhzyi4ZQPzHqfbur0yJi1KKjUo8xUouT6m/tD34ZluVkj5WG1rKMRnNjwVvgB4pw2VM1EoTZdQWhQcOCKWITYi7FRhjMQmaihQFSVpHvazHBBIgviGmZcpf8Ki9suhSG9yD6RDNkkKcXIYt/ELOP7G7GE1XRfTVivW6dSZqzKpR4iTY4CgMjGQq9v/ABjpEEvSqXLWZigVB3LAD+LlLuxwO42h3PmIWpEwAhLBgcgMxsOl/b1hJxfWApZDAbkuRyuQHAwCM9e0JPJpQVC2INRJGCQ5U48nICSbWLE/3g/TIABckOScktgNcuHG3r2gnTcOC0pK03JJScMQbPfA3PYekMzSKlzFUDkALOenyp62L37v5+fK3wdSVEiQkHObHIuHvvb/ADE5ms4FAOM5DW/x5iIZIa7F7EAC42Fg9n+xhTM4rzULSLOl05ba2w8mibk3sM5KJYdNql00uaXZ7B7FwzAMSz2yTtEsvjGmlC5oKQwFJJuGazg9fXEV7x+UfvSWuSSV5ax6Xxb0jYaiWtDLAwS6kkKBYgtyh7sbjaNqF1LsK9VrkKUuqW6DYB83cEv9vtAqZskKcFTuDsQCLO7E+1/WC5HBFzTVJah7lVvO3obOX92uA4HJlyglCEKWGcgcyiQz5Jy1seUNqZNQlIr+lkKU60S00luYVXUC5IBJyHZt+m1g0qytJK7lIpThgB1beJOFvUZa0umk0+ZLEAO7XG20emSUpUz3JT3eo/kl42pydMslSs0WSpQcDBwMBj/aMkSHWoswa/nf+0MdPJYKe4YhOcm3qLxillyfTHbtBWzM2Qr1JS1JGBY37ux84yMKKji7dPeMjUKcl4TJBVMaZSBhNRCnB+blDpG7g3eDuOT60TJSQkqmhFADtUiYtSyCouHEynOEgHtshV2RQlNN7Wcqdi1LWZ+/lE+n1aS6CTYU3qA3Lur5mbMHxZKVo5u1CPh65mnKUTxSksErHMk8yWSSCwvub8rRYFJQUKTNKFOGUmxcEDuaT0s7h92geZPGEkhIId2a5BJYs7g9sg7vHmomS5aS6FFRUFFaUqIZwKXxso2xfpActTTWzMth5wGfOARInzQtKK6FfjIKbJWrekAsreq+A9n8AUkBIJCihNibXIFs3UYpJWhdPh1ZweYEn5Q/KCA9jj3s5+GPiOSH0WpWmXMSpNFbALBNk1Yr5hboxDsYvCTn+CiaDuI6ZPhlCkEghLKAuWlmpR658rRy/VIAqbZm9zHblfhwaVAgk2Zr+hxEXEPg7SzXNHzAFx/ufHXMdEMqg9xZQ1HE7KHcfbf7R6gRcfiD4O8B1I5kA+oeKnqtOUt+r3jsjJTVo55RcXR4gsX+v68oBmliUvbaGcsP9XB9IB18vfs3t/zDrYRgk5YP5R0P9kHFJImTtPOLA/vZdskClaSfIJIHZUcyTtBfD9YuTMTNTlJcdDsR5EOD2JhqtULZ9B67jsv/AMKWVitQGOgu+0IdUtRJK1kjJ/4gnhk2VrJEudLvWkgpb/slPz1kYa7blwRmNNNoDOnBH4RkAZZnPTf2BaK4XjgrRDKsknTIuEcC8X96tXhyk3VspTbDoP5u9oZyuPISpNElKUJwBkk8tz1ucu5ObvEXEv3dVJPgOUsWqChkhlPm7YhOCakhFT52cpIcqYe9+h3iOfIpq5P7L2+/1Hxw0bR/L9wnWasla1BBZZfnyOoIGevoIW1FFykNmwBAh0dAQEigFQy5ztu4fEb6S2E4DEBg/X9GORyb3Ojw7FnDOIoqIJpsWLX6DyPeHPBVqBb8JW5LNkAN0PWNDppS+VKAl9m6N08sGGQRSlASRYuXzZI9sZ7NAkxoxa5GM0AsD1ffYv8A59IhnyS1SBckWvcP9MdIi0QIUXUS4Ktmqexfc3AH+0RPPkvRUTlyRbH6+kT4LCriWmCArBIFtnKnZI7Yt5dIr/8A09uV7KdxaybPvaz+6jFh1TTVKqLhNz/uNzi4YcvoYEmzghZANJc1PihL1G5dyKQ+1XkIhm5Q8Ko2lg3WwUEg2TYsCx7PbHaBNTKCjynmIbo569xkt2gycVpCbBKDfvcszDu143lISljdTFwMWyWL/TsO8RcuxZLuK5elmVAKRWAak4SpJAGSpWHbyhR8U8OW5mUKYcqiRaxNw2LMf6vMRb9QQsp+ZJ5KiAOtTX2sxthUFaWaFINQaouAwcVAC/eIN72NKFqjlOk1CpbkM24IsYbaTTTtQUugBCCCrY5Zh1H9rxLxIy5+o1ISilOl065luWqZUEpKqM0hVk9mOIt/Ak+Lo5C1FlKRUGSN7i1sQ04uKUiWOCumey5cxDMlIpYCl2DWs3kP0I3nTCoF7EgAkeeG8w8NJswjDW++/wBe8aaGSiZLJDcxyD0chj+swqdlmB8NkAJBDAksDa3Yff0iAykqCCCxPNi5uXPaG8iWEpUN7gHu2frAOjk1eEP5b/8As59bn2h477isn1Mihs3Bt6pJ8sQGpnFIJJdxs+/pfeG+pkKVu4Zr5zs36tA0nhZASnCzUXy1rX8yC27GGT2sXuSSOGSxzAm75tv09I8hdx/4kkaIIROmJrI+UAkgDdrbiPYyjN9jWvc4dw4KJZxZySpTDYMA3qNswTrtdMljkVhQ5Q9JFyAQDdzscktvA0kXpSlyGJx73+8OtNU7WJ9n69H+0POSTto417BE7SoI/e85UEumyrubVAhwkk9MHyiHXamWmakXCqBSAaQWAcFsku93dj3dpptZSlUtYdCi7FCSpB/jQThQAe7u0KtdKlTG5gsOCDcE4Yl7gtkPe0STTf0/6HPJE2oIpWEJbCSHXSDmk5qVsc5G0K/iLgilqMxJQokDlBYsEtawBNvVosElIT4QKAlDsqkVWZyQqxFwOY5JAgLUy0A1iUFzLH94pIDWAVzWDAVZcF+rh8eRxlcTNbC/4X+L9RpFALUqbKAbwyoukBmCSbpAbAt26dj+FPimRrZf7pQEwBX7tRAWzguBuL5Ecw1fw3JWQUrUicS5KSMi6mSC1ux6XMK9JxESZvhzk86FOmZJFBqF6lIDObu4pUMuq0dOuGZeXn2BGTjyd11ukUpJSC1i7erern6RyT4m4NMlLUFhxkEYN4uHB/jVRpE8FSF/JNQkMdw6bYY2HMLOMw71kuXqQUIYslLLvcKB9mpx1eDhy6JUx5pTRxzRpu2Yg4ihINywP3LRZOO8BmaZaVlilXTDX/MfWK3x5FUqr+Ej7sPoqPTjJSWxySTQjQlncW3gmpCkgC5x0gSWQbHd79enrG8hCfXvDiFo+CviNOmrTMSpUpakKLM6SDzEpJuCG/8AQZjo8jjomrC9Or93QaSG5sOSSzXDdQx6mOQqSCAff9dXH2h/8FcREub4KiAiZYF25msHwKmA82jOopurM05bXRd0SlAOQCFEkkkAGxO/u4fDRFLnEXBQlJbZ1IDgvh37i5byMMOJ6BCTdyzs5LA5pYCyfaBNdozlCQzPl1Zb3vHDLI5O2W0aVSGmj4pKmqShyDfIYObm48jEmvQErLsP1b3ipodxt5Zft/eJ/wDUKS7qKi17kgHZi7E/zbfWNQFl9xno1rVODkMkkhOQ4ch+p7eWC0WLTS2LbX83Ny/3tFL02rMtYUGU2AruSbe598QTxDjS5hZIKEcpVgnYm+AY1WaM0luWKZxWVLWQtQSWJIAfsMbv9jG6uLSlEIQSVEhhfdyCSdgL532ikplElg7kjG5Nm7lzD7R6ZEqYE1VTmwPkTUMOzqV1NmeBJIMckmxkqVRiz3LW8k39fpC7j04IQVFNdQFbmzKJFup7d4acW1stAVMVgYA3Idh7ufSKRrCpQC5y/mIdrsPlCgmwwBYHBHURw5W7s6+I7BfDdXRKS8xVz8pU705ShOEg9BkOHi0Mp0seRiQNyTcMQbP/AGisS+CygkTELqTYrLpYWBATd6nvftuIfcPm0TvBJJAAEsqyUgXF/mI65DpfMSd8jwdKmNUKPMKQwUxu42YjGQQW2hVr9UZOkUpJ5mAT3UA2/wDsJh7KkuAMgEPkCxa3sIpXxtrqZaZIyhRX0ILzE+Vy3rCxWqSRRvYA/Z/pSZWuEwfPLloNzcTSST659oK+HeOytLJlada1GYXNKQ9AJWpIJWW6DIs2LOy+A0jwNWbEDwkpKRylgpiAOoIVfq8UTWSANZNwk+Ic784YDJGPpHXNak0yF6d0dM1HGULpTKYgq5izpLpHKM3JUPIAl+pWo4siQp5pCUEBRXezZDAE9SC+WGSBCbhEgSkpBcBCAhgfxMCpRaz4tsxES65Ysnma5LAuRh7DLfaOLSXTbRZlK+Zi989LPt5gQq0i6HqUAEu5J6kmKFreKTpc1SAoowSoKNSxLekkuxs1zfY4gDiHHJ0xJTMUS5Jf5diLgC4FTtaGjGicsq4Oj8b+KUaeSmYAeZAWhJsWqSAC9xnHYwPxf440oql+MtBWkhM1I+QuRkXBs4t0xHK9RqZiwhKlFdI5QeilVWJ2eMk6QlQBAblVYfrDRTSktyTyvsLePzZuonTJq1k85SkqJekE0uT2/OMhlM0oUTUW6xkWWalRFk2kU8xSSgAJI5gHBDEoc7G2/TvdxoRzikgABIbuoFy+W3iraLi6QCVVsWFLls5DWuCzsNrw+0+tEwqSlhSUszg5A6thQ6YjlyQd7oYzhmmmKSCT8qiS7gWdmcOrbzD5GRNdwEvWJxSoKKqQk0bFgHByMOc4ETzBNVNTXzS0uvLDlcBzYZpt0cxtpdZMmKJA5QqzqZ+V2djZyxs9zATnF6kzKjfWcKIIomkMrIFT5sWLG2xyzRvrNSlCkhCUrrJqILY81soXuH/KDCZjkpWm5ZLksDdx0288wFqNFLmMlRSQLgqDly5ISotdx2sPSGjJTaX8DV7Eum1CUsVSwFisBWQkFwGuwS1rj6wGsJmMUtOSlSXKQpkVAoqWCxQBdjZJpsTE+v0MxIC1ygoJDUJcg4AdnqDFwDm2IIRMUmwKi5NQqpHMdg7s9vJ4z8m7RjcJ8NDSpyiliVhQCweb8RA6kYIPcBxE/DVzEc+nmORZaU8ymObFLrGQOmbmB9DwhAqCCpCskKJKCLOzDkuBzOfSNfBEtS1LPhKyAAllIDNUXIId2OQO8GM13d/XuYM1GpTqP+6ZiEhIszhJckul3ANgGfHlCPiOlSqWsJL1DzIIyPoO8OUT0Ty5QFLDsaSpJDOHBZKvmTYl38hAWu4MqYCSkEpUBXLYLALgBRA5bnBBAa7O8d2HqHD1boScLWxzsDrGAEQ81/w7ORzJBmpZyUhyPMAl85STviFuo06kBJUGrBI62JSXGxt9RHfDNCe8Wczi1yb6XVKSDYKTuDkd43GoSThx0NnAuxgNKmvjyjCXiuoU7dN4qkCXMJSpC0qnopDqoASFhgmxFeM7Ydka/i2Qs/u5oQL5srOx22YxzVGvmpASmasJFwmo0jqwdo202vmo+VTb4H9o5FhpclpZLOmaTWyWN0qtsQ+x+1/WPZlBNlFmAAJHT/m3SOdJ43qLNNNuwv52v5xkvjWoGJqvVj9x2geFPtRO0dGWgf8ALO36+4jJYAs+On+I50OM6i/71Ubf9Z1GPEVgjuzuYDxT90bY6KhRCgUmlQwdx3eJ9JqVy3KSm7XIc2e3l/aKPwziGoJBXMISNyAx3Av5CGJ4/QjnZ2sB1vf2P1iMnOO3I6iuS1cS16VFSpykikJtsVEO3oANz/dJoOJomLKGASzH/cLuOlxjvFZVrpmpUVTDgOGxhg//AMiCdNolClyUqVghjm+/6tAeKKXme50LIXaRInSlzaGUUMlhUxqIPiUghiwZg7l7ncv/AEyjKmPTWpHjdFS1uR8wuLJSAf5CbiK/qFqVSKeZIpKwVAqYO5awvjziPQcUmylghZIJJXfJFndQPVif0OBofWk/oWWYNTIlqmKnkGpAPiMvlCiAxbfLd8vFN4lxheomTCUjnsAHwouwa57fm0XPUcTCypIUkIpCUi123USPoOsCpn6bhygpSCtakFSSmlSXdmpBBSnlN4thj3Y7dcD34I0SdJopkzUqoqLqKzT8opAuTu7Nl7C8cx1P7zU+PVZUyvBwVWLG+Id8d46vWEGZZIDJTgd2G/mYRayYEpNLkhkgNfDxpZHqqJKUvYvqtUmWPDSCspIR3qJYkpN33G1xcA2H4xxhEhI8QCq7XLMQCBV8pJJdgT54jmer4wWUUq5nHMHewzVkQHrNbP1BAmLUQSuZfuSVEeox2jLBJ87B8fbYP1HHApVL27kttvmI5/E0AFjUsJDW5XcP9PvASOFGuilXndsPnfB9oc8O+GAUlUwgFQICc0u4cv6GHlHFDdsluzNFOQtlMbUsTcMHcEev0gzSzCs8oNLtXsGew9Az9YIl8LTLHJUwDHepvPr7OYnSyUpQkANZhguXz2jllOPYNGitJKsZhOGsQL/fEZEpPVBLWalz0BNux94yJa2MUGTqEBwQQKaSzVEW3I7Q44LqEJWlO6iFMS7gfkyYrstYTm584MSkppKQXKHD7BzYe0ejOOrYQdy5lU9kqKUkqrTUSFAJAAbAwfQw3la1K0qloepaSpZzZRpAA2Nn9YrPD9cEgmYVJuySUqIJAFQyL3HvDTQ6yWCpctAS2L3OWy4AGRjG0c2SH8BH+jUshbig1uSTjmSLMdw/0gLhgmgkzJktRIC/lJLlL05FgQCzEl9rR5owpwtV6kGu7AEuUCklnYXLdYEMlStTJCgWLrSoGxSFHI60gMP5ojFLc1lg1U5ctJCUrYuKgxYMeudsA5ibUSpa2fo6ffb+La5fOIgqJQQ5POVbA2AWxe3/ADEKtWORRFtz/CFEMPI1D7QVllppj6jWcpMtLsVXucEgJPQB8fq7TaWfUEomoKUPQAXqD2CkMAxwbG7dniLSawljMS3yEbgguyn62g6at7OQwLHuzg98Qqbi9jIincGMhI/0s9Uu7hyVJFAZmYgublwCeuG1nKI8OYZpTNLAprKEGYrooOWcEsbY8o10+omEVuxJCQlaQQbspxuOYW7QRJMlaUsgAGoFKgRSsuSUpJLO5YOzE72FtWpW+TAcyfMNXKUTBY1BpdyQ4bqzt3zHms0I1AZaUFhYvfuxKQobG2WDtDKZNSkfxg3Cb3FVhcm9ntg+UQ6iUZhK6nBuwDKBubEE7jpmF8RXa2ZmrK5qvg4FhLXSWZluQblzUANjin8I6uAZ/wAJTUpWoqSwBKWPzAM3k9/bvFslpUkMxA7ErZmSHqfr5QUieSz8wNTbAMzYVuD9YouszR72ScIlEX8LzqmDEO1Q2FVLkfX1jfR/Da1HmNKWVcg/hXQzecXmczKw74FmG32z/LEYnC4PzJCSQ2CfvmH+eyNA8NFeT8LBK5fMCQHKSPmZYf0ZTe0Qaf4YJrCiQUkAYYl1Aju7D3i0iY97l267l7jbGPOMmzQAKsGxPQDmfDNZoT5zJ7h0REJ+G0snxHB8O4T1Fie5FQ9o9kcDkC5qBUeUl2IICmtixAftD86lJJzyFlDYWCvtES/EI5RRzM7fhZ6mOf8AEKupyd2NoQtHAgEtSSHYM7C9/Y794ARwGkpUUqWxwzDNgXLn0G+YtIWWta4HZzfy6e8aV1Ek3Y2F8s3qIX5rIu5tCAdFwwIamXSQ5Llx6F2bffeCUJLAFId+Uju4BIyD+L3iaRUwByRcA7n5g/TPvEtytwkZLO4FmYvuWKrRGU5PdjKCBBKUTdwA58tvePZ6U2CsgAN3U2+w/tEa9QoUMk71jsxY2/pHnBPhJUhVQdTt5AEv6sMmNb7mUQYynFV7OBttHi5Qu93y+bj7f2jzUaxIUEpA5mDZawjWXNKkF/4gB5C794Gp8mI1adJIN3uzmzGxt6j2jedoZYKltzGmrLcoPXzb0jZS1KcYpI/qsCw9D9Iye7uN1B3bD4Y9iYGt+5qFQ4PISukJFQdRDvlwxHSCk6FOwSMDAuOYn7H3gw6dNQLklRIcgFwR12iCdqwElRwD+vv9YLyyfcFE8s8oty+VyAWY/cecaVBRy1QV68pI/XnAmu4kAgF7MVZywYP6/lAUvjkjw0zFqNRDFI2d0lh0s9+vlBSb3oaxlpbEF7Cwtk4v239o3CEpZxzJ9rgi/vCjVfEkvwUKQ1Ux6g3yUkEg+YNoH4txFwUu75byd/eGWOTe4o1malSQXw4xs7sPp94yFcxE/UpAqSlHKoNzKsCPw7Fyb4PrGQ2mHdhKDLlKNwCWv9h+YixSETJqpQJCeRKAVC9yQmwNz6vCGStjkdMOPQGJJmrUTcsGHbAYW6tv/mO632AWn4gXLRp5UlBQpSVKJIsHzv1qG+w7QJwjRCd4gDppQVEDJAawfzEV6ShSlBASS7MGJd8EdfOOi/B8oiWpKpRTy4KSHCnuatyEAbWIcRGcnCJqsF0WgIQUqnMSAwLEOQWenb19YcaPTB5alquhLtbLEKDgkbjc3G8Vnia1ypqZi0Hw3KAACByEJZ8G/wBQ0WbTKEyRVLfJSD17hzuSbRz5NVJvhmQXpyBLy5UCLm5cDr9u0B8UIRpUHNTuHf5k8oDbAkexgSRwjUKWlRmUs6gm7hRdk56kejiGchIaTLUaiPmYMksm7OH/ADsYn5U7TsNGmnWmYkJIA8MIKgHszFvKx9CIKKFqCWSxASTawNgoMcWWWHqYD4QHZTEFSmU4YkIYA3GP7w3qUHUrINLAvfpYZtAk6YUtivp1wKkygmwWGPUUlTnzpIz/AILnrCWByVBQ6ciQSG7hx6CI5+hDcuyncEPyqUWvZiVkdhE8lJKk1SnUKlvsCEAdbZZmDtDOUeUKFGUlRAIBTQQdstftePFqCTS2ByhnFkh7/nEC0qIVkOhDjoTUDfzA9oyVKHirIUl3UCNwFJAZvMA/1QgQiYCAR0DgAlyB93t6xIA1Kai4AB7gvn6e0QmYAoqqZkFPssEFmyzj2jWdM50qdNIBKrP+IEejP6wOTUglYatWAzuBskWfbJMDTQPErA5gooVazEA3+h+kRT9UxRMExkOVMR8woNId+t4zT6ldLLcEVqP9JUH7jti8FJpWCgmWogFaksbnvk5fzT7xpM04KBX+IMR3UCCDc2NVojOrM2YpDsUKCjlighJF+pLj03iHXIP7xQLpuB5Ui3o31MGnYaC5a0JQS/NNALs5LhKW6/8AMSzDzBVXLzClsmql+wBH1hDqOIkGWThRSBYG7EED3f1EeJ4stRT1SolhYXU7frtDeFJ7gssU2YlKXuxZ97izje7CINbqEJmfOwCrgDIUCA58xntCCfxWYkqGQym7XF/PEK53EpqpaiRm9W7lg31gxwSZnItuq1ktNKiSkJze7DY5Jdr/AKMRaXiTqKpakqFQGbiq5dz3H22ika6bMU9WwZvIn/MQJBChkB2J2dvuw+kXXSquRdRdtRxGUJzrWXApJflIUpzYdCAOzxDq/iNArIuXYEM2xs2zCEP/AE5XISLKACe5U7fYmB+K6YoVLlJvUbAbkW84CwwbSYbY34hxMDwpiWIXdiHsCUu2NoNmceQmQFpuSQz25lJKvOzfWBFcGSlAQSVEAjzq/IEvCnXcLUhLtk/di3/1AUcc6RrYdrvifdIc98YtbzhTO4/NVVfLW79v1tAeokMxO94hKPb/ABHRDDjXCBbHGn+I5qVAkuAGbtA+v4wuYABYC7Dcs0BKSBc9IHCoZYoXaRrYXN1ilSwkmwDfV/7wEtWY9Usx4A4LRSKSMYhUSKmls9o8BHT/ABHkxL4H6MYw64dMlIR8ylKLFXMJaQWxcKKiL3YRkRyeGyyTR4k3AZilmySW62Atg5zGRDSnvY6sVcdQBMDJSAxskBIx0EB6AOQTcghveMjIePpMi/cMSE6iWwD0gOwdqFKao3ZwPQAYEWXWKJUtLlmBZyN+0ZGR50/WgrgRr1i0hdKm5KtmdiXbzEFy5hTpCtNlUKW4AHM6btiMjIq0jGvw5qVqUkKUTjNzepy+XsIO0coJnoSHZpimckO46nufePIyOfLtJ/Yy4FGsnKTqZaQSAZqQR1q1QSfoo/oCJJs5X+pAe3g1/wBRrD+0ZGRVLZfZi9hujTJMkAhwTMySd1Hcwv1qyJ0pibzEg3ODNAI9jGRkJj3lv/vIXyMNJzIWTfmWPQAkD6xglJTNmkBiZlJ7gAMIyMgS7mYk4OsnULSS4BUQD/8A0TD5MpNRDBikP3dagb+RjIyGzeoVGunkJaWlnAZgbtcjfsYh1yiyTvWU+hSokeVh7RkZC/qYwTw2SkGeQAP+0n0DsPIPEJTzLTtWzdiASIyMjd3/AL7B7AerkJUuQkiwWW9EqIv6CF3CEAqWSPxI+oBP3MZGRe3T+39sD5PddZ2/m/KNdOgf6aVbID9+YxkZFP0r7mNdRJT/APs2/wDG/wD8k/cRprZSRp7AfNKPqWf7n3j2Mgxe6/H9GHGoQPDlncLlB/Uj7Qq1aB/rdP8A7h9ZSn+wjIyJR7/ZhYzSXmrB2Fu0acWSBQkYBTb+oCMjIVG7MqXHyy0jalP1D/cmF6zyv/LHsZHdj9KJmkxLj0jWYGIbvGRkOY8nJDjyjaSkNGRkZ8GPSLGJeHSgqYhJDgqAMZGQkvSzFlmyU+IAQ4owb7997xkZGR5zbNLk/9k=" /></p>`
})
})
app.listen(3000)
Request and Response Objects: Revisited
Forms: getting data from the request
object
Consider the following basic login.ejs
file with a basic form:
<link rel="stylesheet" type="text/css" href="/stylesheets/styles.css">
<div class="login-page">
<div class="form">
<form action="/process_login" method="post" class="login-form">
<input type="text" placeholder="username" name="username" />
<input type="password" placeholder="password" name="password"/>
<button>login</button>
<p class="message">Not registered? <a href="#">Create an account</a></p>
</form>
</div>
</div>
The action
attribute on a form
tag determines where the form is going to be submitted when it is submitted (i.e., what endpoint relative to the host). On a front-end framework, you would never submit the form because you don't want to leave the HTML page (see this post about how the preventDefault
method is often used on events in the browser to stop forms from automatically submitting when the submit button is clicked). You would use preventDefault
when using a front-end framework like React; this gives you the chance to instead submit the form data asynchronously using JS to make an AJAX request using axios
or another HTTP client. Specifically, when using Express for server-side rendering instead of building APIs, you have a route that specifically handles the data submitted for certain types of HTTP requests whereas in something like React you will need to transmit the data submitted on the front-end using an HTTP client like axios
. But in our case right now, where we're using Express in the context of server-side rendering, we will use res.render
and Express will be in charge of rendering every single page by means of a view engine. So we are going to have Express move the form once it is submitted on to the /process_login
route, and it will be a post
request (the method
attribute on the form indicates what kind of request is going to be fired off once the form is submitted).
Everything described above should immediately remind us that we need some middleware in Express to handle what happens when we get a request, specifically a post
request, to the /process_login
route. For right now let's just do a simple test:
app.post('/process_login', (req, res, next) => {
console.log(req.body); // see what data is coming across the wire from the form
res.json({
message: 'You tried to login!'
})
})
NOTE: Redirects in post routes |
---|
It is worth noting here that the browser never sees what goes on at /process_login . Think about what typically happens when you try to log in to your Amazon account, Gmail, etc. Are you not redirected to a page that indicates success (e.g., a dashboard, your posts feed, etc.) or a page that indicates failure (e.g., maybe being redirected to the same page but now with an error message displayed)? The point is that once you submit the form with your credentials/information, this information goes to the server (our Express server!), and it is now up to us to figure out what to do with the user's request in terms of how we respond.If a request comes in from a user posting data by means of a form submission, then the response will almost always boil down to first doing something with the data behind the scenes in Express (e.g., pulling from the database and matching/validating user credentials, etc.) and then redirecting the user accordingly. In Express, the way to redirect a user is with res.redirect([status], path) , a method touched on later in these notes.Of course, it is also worth noting that the method on a form can be not only post but also put (to update/edit things like comments, blog posts, etc.), get (retrieve certain information), etc.In the case of server-side rendering (when you use a view engine(s) and the like), you will often handle form submissions from a user and redirect them internally within Express before landing on a route where you render a view to the user, something like login.ejs or dashboard.ejs or whatever view it might be. But you handle the logic internally and the user does not see the logic being hashed out in the browser but sees whatever view you end up responding with (if that's what you choose to ultimately respond with).In the case of single page applications (e.g., when using something like React), you will often use Express to build an API and you will make routes that respond with JSON. In this way, you can design your UI so that you have routing on the front-end that directs your user to different pages/routes in the browser (using React Router, for example), but you can respond with JSON from Express (probably from your database) based on what routes the user hits or what requests they issue on the front-end. As an example, if you're using React for your front-end and Node, Express, and Postgres for your backend, then you may use react-router and react-router-dom for a lot of your routing on the front-end. Routing on the front-end will often mean rendering different components based on what front-end routes are hit. It's within the components that you want rendered based on the front-end routing that you make asynchronous calls to your backend. As a simple example, suppose a coordinator went to /dashboard from your root domain in the browser. Then you may choose to render several components, where the main one might be something like <CoordinatorDashboard /> . Inside this component, you might use an HTTP client like axios to make an HTTP request (to the appropriate route with the appropriate method) to wherever your Express server is listening. Your Express server would then respond with JSON (from you database most likely) which you would then use to populate meaningful things in the <CoordinatorDashboard /> component. |
The data, as it is being passed from the form
, is coming from two input
boxes, with type
of text
and password
, being submitted by the user. In HTML, whatever the name
attribute is set as is what is going to be passed to whomever comes next (i.e., user-submitted information will come across the wire as key-value pairs where the key is determined by what we specify for name
on our input
and the value is whatever the user submits).
In our case, our /process_login
route is going to get the submitted data through body
on the request
object (thanks to our app.use(urlencoded({ extended: false }))
middleware) and the property names will correspond to whatever name
s were set on the input
tags with the property values being whatever the user entered.
For example, suppose our form
had the following input
tags:
<input type="text" placeholder="username" name="some" />
<input type="text" placeholder="user" name="thing" />
<input type="password" placeholder="password" name="else"/>
And suppose the user typed in values of An
, illustrative
, example
into the different input
fields, respectively. Once the form
was submitted, we we would see something like the following come across the wire on req.body
:
{
some: 'An',
thing: 'illustrative',
else: 'example'
}
Whatever the user submitted will come through the HTTP message and the form will come through as urlencoded
(hence the need of the Express middleware to parse the HTTP message corresponding to the user-submitted request and to tack on the data to the req.body
object). Note that before the urlencoded
request gets to app.post('/process_login', ...)
it is subjected to all of the middleware used at the application level, namely app.use(express.urlencoded({ extended: false }))
. This middleware parses the request
object coming from the user and will add to the request
object a body
property which will have the user-submitted data from the form on it.
What we actually want to accomplish here is we want to decide what to do with the user. Think about what happens when you log in to a site. Are you redirected to a certain page only for logged in users? Are you redirected to your dashboard? Somewhere else? The point is we can have a bunch of logic in our routes to handle the user effectively. And almost always your logic will depend on properties on the request
object. In the case of form-submitted data, we have access to information on the body
of the request
which should further inform what we want to do with the user. It's fairly common to destructure information off the body
of the request
like so (as an example of our login form):
const { username, password } = req.body;
Then you can do stuff like check the database to see if a user's credentials are valid (maybe you're using bcrypt
or some version of blowfish
or some sort of algorithm or OAuth). As an example, suppose we want to direct the user to the welcome page if they are valid. And we might want to save their username in a cookie--or you could use sessions (we want to do this to make it readily available).
Sessions and cookies are very similar. The difference is that cookie data is stored entirely on the browser and the browser will send it up to the server every time a request is made. Session data, on the other hand, is stored on the server and the browser is essentially given a key for that data. But sessions are not included with Express (you can use express-session
if you want to use that instead of cookies). But the ability to do things with cookies is built-in with Express so we'll just use that instead in this case. You could use local data too, but we're just going to stick with cookies for this.
Cookies: using res.cookie
, res.clearCookie
, and other cookie-related information
As always, it is best to look at the docs. We see the following for res.cookie(name, value [, options])
: Sets cookie name
to value
. The value
parameter may be a string or object converted to JSON. The options
parameter is an object that can have the following properties.
Property | Type | Description |
---|---|---|
domain | String | Domain name for the cookie. Defaults to the domain name of the app. |
encode | Function | A synchronous function used for cookie value encoding. Defaults to encodeURIComponent . |
expires | Date | Expiry date of the cookie in GMT. If not specified or set to 0, creates a session cookie. |
httpOnly | Boolean | Flags the cookie to be accessible only by the web server. |
maxAge | Number | Convenient option for setting the expiry time relative to the current time in milliseconds. |
path | String | Path for the cookie. Defaults to "/" . |
secure | Boolean | Marks the cookie to be used with HTTPS only. |
signed | Boolean | Indicates if the cookie should be signed. |
sameSite | Boolean or String | Value of the "SameSite" Set-Cookie attribute. More information here |
Note: All res.cookie()
does is set the HTTP Set-Cookie
header with the options provided. Any option not specified defaults to the value stated in RFC 6265.
See the res.cookie
docs for example usage (and also note you can set multiple cookies in a single response by calling res.cookie
multiple times).
For our uses right now, we'll just note that res.cookie
takes at least 2 arguments:
- The
name
of the cookie. - The
value
to set it to.
So every time whoever the response is sent to makes a request, they're going to send their cookie up so the server will have all of that data available to it. So the example of using the form to submit a username and password and subsequently getting the values off req.body
const { username, password } = req.body;
can now be used inside of our route in a conditional way to set a cookie:
app.post('/process_login', (req, res, next) => {
const { username, password } = req.body;
if (password === 'x') {
res.cookie('username', username);
res.redirect(303, '/welcome');
}
res.json(req.body);
})
Once the form is submitted, the application-level middleware app.use(express.urlencoded({ extended: false }))
parses the HTTP message and tacks the data onto a body
object which is appended to the request
object before our route middleware app.post('/process_login', ...)
deals with the request
. If the password supplied has a value of 'x'
, then we will stash the username
in a cookie so that going forward we can access that username value on any page and we don't need to remember it (the cookie is stored on the user's browser and thus we do not need to find out the cookie info for each new request ... we are simply using what we stored on the user's browser in the form of a cookie). Why does this matter? Well, if the user comes back or goes to a new path, then we will not have access to req.body
anymore. We get a totally new response and a totally new request. Remember that's part of HTTP. It's stateless. There's no dialogue going on. It's just the one-off process. You get one request and you get one response. And then we start completely over.
So that is what the cookie is for--cookies allow us to persist data from one request (e.g., username, preferences, etc.) to numerous requests by storing information on the user's browser. This information (in the form of cookies) is sent up to the server by the browser for every subsequent request, thus allowing us access to information stored in cookies that would otherwise have to be left behind due to the stateless nature of HTTP. See the comments at the end of this note for more about this.
Once we've stashed the username
in the cookie, we can call res.redirect
to send the user where we want them to go. From the docs for res.redirect([status,] path)
: Redirects to the URL derived from the specified path
, with specified status
, a positive integer that corresponds to an HTTP status code . If not specified, status
defaults to "302 Found". (See the docs for more detail and examples.)
The essential fact is that res.redirect
takes one argument (unless we give it the optional first one as a status code as specified above): where to send the browser. We shall, for the time being, simply have res.redirect('/welcome')
. So to recap:
app.post('/process_login', (req, res, next) => {
const { username, password } = req.body;
if (password === 'x') {
res.cookie('username', username);
res.redirect('/welcome');
}
})