In this chapter, we’ll learn the important details of the request and response objects—which are very much the beginning and end of everything that happens in an Express application. When you’re building a web server with Express, most of what you’ll be doing starts with a request object and ends with a response object.
These two objects originate in Node and are extended by Express. Before we delve into what these objects offer us, let’s establish a little background on how a client (a browser, usually) requests a page from a server and how that page is returned.
We see URLs all the time, but we don’t often stop to think about their component parts. Let’s consider three URLs and examine their component parts:
The protocol determines how the request will be transmitted. We will be dealing exclusively with http and https. Other common protocols include file and ftp.
The host identifies the server. Servers on your computer (localhost) or a local network may be identified simply be one word or by a numeric IP address. On the internet, the host will end in a top-level domain (TLD) like .com or .net. Additionally, there may be subdomains, which prefix the hostname. www is a common subdomain, though it can be anything. Subdomains are optional.
Each server has a collection of numbered ports. Some port numbers are special, like 80 and 443. If you omit the port, port 80 is assumed for HTTP and 443 for HTTPS. In general, if you aren’t using port 80 or 443, you should use a port number greater than 1023.1 It’s common to use easy-to-remember port numbers like 3000, 8080, and 8088. Only one server can be associated with a given port, and even though there are plenty of numbers to choose from, you may have to change the port number if you’re using a commonly used port number.
The path is generally the first part of the URL that your app cares about (it is possible to make decisions based on protocol, host, and port, but it’s not good practice). The path should be used to uniquely identify pages or other resources in your app.
The querystring is an optional collection of name/value
pairs. The
querystring starts with a question mark (?), and name/value pairs are
separated by ampersands (&). Both names and values should be URL
encoded. JavaScript provides a built-in function to do that:
encodeURIComponent
. For example, spaces will be replaced with plus signs
(+). Other special characters will be replaced with numeric character
references. Sometimes the querystring will be referred to as the search
string or simply the search.
The fragment (or hash) is not passed to the server at all; it is
strictly for use by the browser. Some single-page applications use the
fragment to control application navigation. Originally, the fragment’s
sole purpose was to cause the browser to display a specific part of the
document, marked by an anchor tag (for example: <a id="chapter06">
).
The HTTP protocol defines a collection of request methods (often referred
to as HTTP verbs) that a client uses to communicate with a server.
Far and away, the most common methods are GET
and
POST
.
When you type a URL into a browser (or click a link), the browser issues an
HTTP GET
request to the server. The important information passed to the
server is the URL path and querystring. The combination of method, path,
and querystring is what your app uses to determine how to respond.
For a website, most of your pages will respond to GET
requests. POST
requests are usually reserved for sending information back to the server
(form processing, for example). It’s quite common for
POST
requests to respond with the same HTML as the corresponding GET
request after the server has processed any information included in the
request (like a form). Browsers will primarily use the GET
and POST
methods when communicating with your server. The Ajax requests your
application makes, however, may use any HTTP verb. For example, there’s an
HTTP method called DELETE
that is useful for, well,
an API call that deletes things.
With Node and Express, you are fully in charge of what methods you respond to. In Express, you’ll usually be writing handlers for specific methods.
The URL isn’t the only thing that’s passed to the server when you navigate
to a page. Your browser is sending a lot
of “invisible” information every time you visit a website. I’m not talking
about spooky personal information (though if your browser is infected by
malware, that can happen). The browser will tell the server what language
it prefers to receive the page in (for example, if you download Chrome in
Spain, it will request the Spanish version of pages you visit, if they
exist). It will also send information about the user agent (the browser,
operating system, and hardware) and other bits of information. All this
information is sent as a request header, which is made available to you
through the request object’s headers
property. If you’re curious to see the information your browser
is sending, you can create a simple Express route to display that
information (ch06/00-echo-headers.js in the companion repo):
app
.
get
(
'/headers'
,
(
req
,
res
)
=>
{
res
.
type
(
'text/plain'
)
const
headers
=
Object
.
entries
(
req
.
headers
)
.
map
(([
key
,
value
])
=>
`
${
key
}
:
${
value
}
`
)
res
.
send
(
headers
.
join
(
' '
))
})
Just as your browser sends hidden information to the server in the form of
request headers, when the server responds, it also sends information back
that is not necessarily rendered or displayed by the browser. The
information typically included in response headers is metadata and server
information. We’ve already seen the Content-Type
header,
which tells the browser what kind of content is
being transmitted (HTML, an image, CSS, JavaScript, etc.). Note that the browser will respect the
Content-Type
header regardless of what the URL path is. So you could
serve HTML from a path of /image.jpg
or an image from a path of /text.html. (There’s no legitimate reason to
do this; it’s just important to understand that paths are abstract, and the
browser uses Content-Type
to determine how to render content.) In
addition to Content-Type
, headers can indicate whether the response is
compressed and what kind of encoding it’s using. Response headers can also
contain hints for the browser about how long it can cache the resource.
This is an important consideration for optimizing your website, and we’ll
be discussing that in detail in Chapter 17.
It is also common
for response headers to contain some information about the server,
indicating what type of server it is and sometimes even details about the
operating system. The downside about returning server information is that
it gives hackers a starting point to compromise your site. Extremely
security-conscious servers often omit this information or even provide
false information. Disabling Express’s
default X-Powered-By
header is easy (ch06/01-disable-x-powered-by.js in
the companion repo):
app
.
disable
(
'x-powered-by'
)
If you want to see the response headers, they can be found in your browser’s developer tools. To see the response headers in Chrome, for example:
Open the JavaScript console.
Click the Network tab.
Reload the page.
Pick the HTML from the list of requests (it will be the first one).
Click the Headers tab; you will see all response headers.
The Content-Type
header is critically important; without it, the client
would have to painfully guess how to render the content. The format of
the Content-Type
header is an internet media type, which consists of a
type, subtype, and optional parameters. For example, text/html;
charset=UTF-8
specifies a type of “text,” a subtype of “html,” and a
character encoding of UTF-8. The Internet Assigned Numbers Authority
maintains an
official list
of internet media types. Often, you will hear “content type,” “Internet
media type,” and “MIME type” used interchangeably. MIME (Multipurpose Internet Mail
Extensions) was a precursor of internet media types and, for the most part,
is equivalent.
In addition to the request headers, a request can have a body (just like
the body of a response is the actual content that’s being
returned). Normal
GET
requests don’t have bodies, but POST
requests usually do. The most common media type for
POST
bodies is application/x-www-form-urlencoded
, which is simply
encoded name/value pairs separated by ampersands (essentially the same
format as a querystring). If the POST
needs to support file uploads, the
media type is multipart/form-data
, which is a more complicated format.
Lastly, Ajax requests can use application/json
for the body. We’ll learn
more about request bodies in Chapter 8.
The request object (which is passed as the first parameter of a request
handler, meaning you can name it whatever you want; it is common to name it
req
or request
) starts its life as an instance of
http.IncomingMessage
, a core Node object. Express adds
further functionality. Let’s look at the most useful properties and
methods of the request object (all of these methods are added by Express,
except for req.headers
and req.url
, which originate in Node):
req.params
An array containing the named route parameters. We’ll learn more about this in Chapter 14.
req.query
An object containing querystring parameters (sometimes called
GET
parameters) as name/value pairs.
req.body
An object containing POST
parameters. It is so named
because POST
parameters are passed in the body of the request, not in
the URL as querystring parameters are. To make req.body
available, you’ll
need middleware that can parse the body content type, which we will learn
about in Chapter 10.
req.route
Information about the currently matched route. This is primarily useful for route debugging.
req.cookies
/req.signedCookies
Objects containing cookie values passed from the client. See Chapter 9.
req.headers
The request headers received from the client. This is an object whose
keys are the header names and whose values are the header values. Note that
this comes from the underlying http.IncomingMessage
object, so you won’t
find it listed in the Express documentation.
req.accepts(types)
A convenience method to determine whether the
client accepts a given type or types (optional types
can be a single MIME
type, such as application/json
, a comma-delimited list, or an array).
This method is of primary interest to those writing public APIs; it is
assumed that browsers will always accept HTML by default.
req.ip
req.path
The request path (without protocol, host, port, or querystring).
req.hostname
A convenience method that returns the hostname reported by the client. This information can be spoofed and should not be used for security purposes.
req.xhr
A convenience property that returns true
if the request
originated from an Ajax call.
req.protocol
The protocol used in making this request (for our
purposes, it will be either http
or https
).
req.secure
A convenience property that returns true
if the connection
is secure. This is equivalent to req.protocol === 'https'
.
req.url
/req.originalUrl
A bit of a misnomer, these properties return
the path and querystring (they do not include protocol, host, or port).
req.url
can be rewritten for internal routing purposes, but req.originalUrl
is designed
to remain the original request and querystring.
The response object (which is passed as the second parameter of a request
handler, meaning you can name it whatever you want; it is common to name it
res
, resp
, or response
) starts its life as an instance of
http.ServerResponse
, a core Node object. Express adds
further functionality. Let’s look at the most useful properties and
methods of the response object (all of these are added by Express):
res.status(code)
Sets the HTTP status code. Express defaults to 200
(OK), so you will have to use this method to return a status of 404 (Not
Found) or 500 (Server Error), or any other status code you want to use.
For redirects (status codes 301, 302, 303, and 307), there is a method
redirect
, which is preferable. Note that res.status
returns the
response object, meaning you can chain calls: res.status(404).send('Not found')
.
res.set(name, value)
Sets a response header. This is not something you will normally be doing manually. You can also set multiple headers at once by passing a single object argument whose keys are the header names and whose values are the header values.
res.cookie(name, value, [options])
, res.clearCookie(name, [options])
Sets or clears cookies that will be stored on the client. This requires some middleware support; see Chapter 9.
res.redirect([status], url)
Redirects the browser. The default redirect code is 302 (Found). In general, you should minimize redirection unless you are permanently moving a page, in which case you should use the code 301 (Moved Permanently).
res.send(body)
Sends a response to the client. Express defaults to a content type of
text/html
, so if you want to change it to text/plain
(for example),
you’ll have to call res.type('text/plain’)
before calling res.send
.
If body
is an object or an array, the response is sent as JSON
(with the content type being set appropriately), though if you want to send
JSON, I recommend doing so explicitly by calling res.json
instead.
res.json(json)
res.jsonp(json)
res.end()
Ends the connection without sending a response. To learn more about the
differences between res.send
, res.json
, and res.end
, see this article
by Tamas Piros.
res.type(type)
A convenience method to set the Content-Type
header. This is essentially
equivalent to res.set('Content-Type ', type)
, except that it will also
attempt to map file extensions to an internet media type if you provide a
string without a slash in it. For example, res.type('txt ')
will result
in a Content-Type
of text/plain
. There are areas where this
functionality could be useful (for example, automatically serving disparate
multimedia files), but in general, you should avoid it in favor of
explicitly setting the correct internet media type.
res.format(object)
This method allows you to send different content depending on the
Accept
request header. This is of primary use in APIs, and we will
discuss this more in Chapter 15. Here’s a simple
example: res.format({'text/plain': 'hi there', 'text/html': '<b>hi
there</b>'})
.
res.attachment([filename])
, res.download(path, [filename], [callback])
Both of these methods set a response header called Content-Disposition
to attachment
; this will prompt the browser to download the content
instead of displaying it in a browser. You may specify filename
as a
hint to the browser. With res.download
, you can specify the file to
download, whereas res.attachment
just sets the header; you still have to
send content to the client.
res.sendFile(path, [options], [callback])
This method will read a file specified by path
and send its contents to
the client. There should be little need for this method; it’s easier to
use the static
middleware and put files you want available to the client
in the public directory. However, if you want to have a different
resource served from the same URL depending on some condition, this method
could come in handy.
res.links(links)
Sets the Links
response header. This is a
specialized header that has little use in most applications.
res.locals
, res.render(view, [locals], callback)
res.locals
is an object containing default
context for rendering views. res.render
will render a view using the
configured templating engine (the locals
parameter to res.render
shouldn’t be confused
with res.locals
: it will override the context in res.locals
, but
context not overridden will still be available). Note that res.render
will default to a response code of 200; use res.status
to specify a
different response code. Rendering views will be covered in depth in
Chapter 7.
Because of JavaScript’s prototypal inheritance, knowing exactly what you’re dealing with can sometimes be challenging. Node provides you with objects that Express extends, and packages that you add may also extend those. Figuring out exactly what’s available to you can be challenging sometimes. In general, I recommend working backward: if you’re looking for some functionality, first check the Express API documentation. The Express API is pretty complete, and chances are, you’ll find what you’re looking for there.
If you need information that isn’t documented, sometimes you have to dive into the Express source. I encourage you to do this! You’ll probably find that it’s a lot less intimidating than you might think. Here’s a quick roadmap to where you’ll find things in the Express source:
The main Express interface. If you want to understand how middleware is linked in or how views are rendered, this is the place to look.
A relatively short file that primarily provides the
createApplication
function (the default export of this file), which
creates an Express application instance.
Extends Node’s http.IncomingMessage
object to provide a robust request
object. For information about
all the request object properties and methods, this is where to look.
Extends Node’s http.ServerResponse
object to provide the response
object. For information about response object properties and methods, this
is where to look.
Provides basic routing support. While routing is central to your app, this file is less than 230 lines long; you’ll find that it’s quite simple and elegant.
As you dig into the Express source code, you’ll probably want to refer to
the Node documentation, especially the section
on the HTTP
module.
This chapter has provided an overview of the request and response objects, which are the meat and potatoes of an Express application. However, the chances are that you will be using a small subset of this functionality most of the time. So let’s break it down by functionality you’ll be using most frequently.
When you’re rendering content, you’ll be using res.render
most often,
which renders views within layouts, providing maximum value. Occasionally, you may want to write a quick test page, so you might use
res.send
if you just want a test page. You may use req.query
to
get querystring values, req.session
to get session values, or
req.cookie
/req.signedCookies
to get
cookies. Example 6-1 to Example 6-8 demonstrate common
content rendering tasks.
// basic usage
app
.
get
(
'/about'
,
(
req
,
res
)
=>
{
res
.
render
(
'about'
)
})
app
.
get
(
'/error'
,
(
req
,
res
)
=>
{
res
.
status
(
500
)
res
.
render
(
'error'
)
})
// or on one line...
app
.
get
(
'/error'
,
(
req
,
res
)
=>
res
.
status
(
500
).
render
(
'error'
))
app
.
get
(
'/greeting'
,
(
req
,
res
)
=>
{
res
.
render
(
'greeting'
,
{
message
:
'Hello esteemed programmer!'
,
style
:
req
.
query
.
style
,
userid
:
req
.
cookies
.
userid
,
username
:
req
.
session
.
username
})
})
// the following layout doesn't have a layout file, so
// views/no-layout.handlebars must include all necessary HTML
app
.
get
(
'/no-layout'
,
(
req
,
res
)
=>
res
.
render
(
'no-layout'
,
{
layout
:
null
})
)
// the layout file views/layouts/custom.handlebars will be used
app
.
get
(
'/custom-layout'
,
(
req
,
res
)
=>
res
.
render
(
'custom-layout'
,
{
layout
:
'custom'
})
)
app
.
get
(
'/text'
,
(
req
,
res
)
=>
{
res
.
type
(
'text/plain'
)
res
.
send
(
'this is a test'
)
})
// this should appear AFTER all of your routes
// note that even if you don't need the "next" function, it must be
// included for Express to recognize this as an error handler
app
.
use
((
err
,
req
,
res
,
next
)
=>
{
console
.
error
(
'** SERVER ERROR: '
+
err
.
message
)
res
.
status
(
500
).
render
(
'08-error'
,
{
message
:
"you shouldn't have clicked that!"
})
})
// this should appear AFTER all of your routes
app
.
use
((
req
,
res
)
=>
res
.
status
(
404
).
render
(
'404'
)
)
When you’re processing forms, the information from the forms will usually
be in req.body
(or occasionally in req.query
). You may use
req.xhr
to determine whether the request was an Ajax request or a browser
request (this will be covered in depth in Chapter 8). See Example 6-9 and Example 6-10. For the
following examples, you’ll need to have body parser middleware linked in:
const
bodyParser
=
require
(
'body-parser'
)
app
.
use
(
bodyParser
.
urlencoded
({
extended
:
false
}))
We’ll learn more about body parser middleware in Chapter 8.
app
.
post
(
'/process-contact'
,
(
req
,
res
)
=>
{
console
.
log
(
`received contact from
${
req
.
body
.
name
}
<
${
req
.
body
.
}
>`
)
res
.
redirect
(
303
,
'10-thank-you'
)
})
app
.
post
(
'/process-contact'
,
(
req
,
res
)
=>
{
try
{
// here's where we would try to save contact to database or other
// persistence mechanism...for now, we'll just simulate an error
if
(
req
.
body
.
simulateError
)
throw
new
Error
(
"error saving contact!"
)
console
.
log
(
`contact from
${
req
.
body
.
name
}
<
${
req
.
body
.
}
>`
)
res
.
format
({
'text/html'
:
()
=>
res
.
redirect
(
303
,
'/thank-you'
),
'application/json'
:
()
=>
res
.
json
({
success
:
true
}),
})
}
catch
(
err
)
{
// here's where we would handle any persistence failures
console
.
error
(
`error processing contact from
${
req
.
body
.
name
}
`
+
`<
${
req
.
body
.
}
>`
)
res
.
format
({
'text/html'
:
()
=>
res
.
redirect
(
303
,
'/contact-error'
),
'application/json'
:
()
=>
res
.
status
(
500
).
json
({
error
:
'error saving contact information'
}),
})
}
})
When you’re providing an API, much like processing forms, the parameters
will usually be in req.query
, though you can also use
req.body
. What’s different about APIs is that you’ll usually be returning
JSON, XML, or even plain text, instead of HTML, and you’ll often be using
less common HTTP methods like PUT
, POST
, and DELETE
. Providing an
API will be covered in Chapter 15.
Example 6-11 and Example 6-12 use the following “products”
array (which would normally be retrieved from a database):
const
tours
=
[
{
id
:
0
,
name
:
'Hood River'
,
price
:
99.99
},
{
id
:
1
,
name
:
'Oregon Coast'
,
price
:
149.95
},
]
The term endpoint is often used to describe a single function in an API.
app
.
get
(
'/api/tours'
,
(
req
,
res
)
=>
res
.
json
(
tours
))
Example 6-12 uses the res.format
method in Express to respond
according to the preferences of the client.
app
.
get
(
'/api/tours'
,
(
req
,
res
)
=>
{
const
toursXml
=
'<?xml version="1.0"?><tours>'
+
tours
.
map
(
p
=>
`<tour price="
${
p
.
price
}
" id="
${
p
.
id
}
">
${
p
.
name
}
</tour>`
).
join
(
''
)
+
'</tours>'
const
toursText
=
tours
.
map
(
p
=>
`
${
p
.
id
}
:
${
p
.
name
}
(
${
p
.
price
}
)`
).
join
(
' '
)
res
.
format
({
'application/json'
:
()
=>
res
.
json
(
tours
),
'application/xml'
:
()
=>
res
.
type
(
'application/xml'
).
send
(
toursXml
),
'text/xml'
:
()
=>
res
.
type
(
'text/xml'
).
send
(
toursXml
),
'text/plain'
:
()
=>
res
.
type
(
'text/plain'
).
send
(
toursXml
),
})
})
In Example 6-13, the PUT endpoint updates a product and returns
JSON. Parameters are passed in the request body (the :id
in the route
string tells Express to add an id
property to req.params
).
app
.
put
(
'/api/tour/:id'
,
(
req
,
res
)
=>
{
const
p
=
tours
.
find
(
p
=>
p
.
id
===
parseInt
(
req
.
params
.
id
))
if
(
!
p
)
return
res
.
status
(
404
).
json
({
error
:
'No such tour exists'
})
if
(
req
.
body
.
name
)
p
.
name
=
req
.
body
.
name
if
(
req
.
body
.
price
)
p
.
price
=
req
.
body
.
price
res
.
json
({
success
:
true
})
})
Finally, Example 6-14 shows a DELETE endpoint.
app
.
delete
(
'/api/tour/:id'
,
(
req
,
res
)
=>
{
const
idx
=
tours
.
findIndex
(
tour
=>
tour
.
id
===
parseInt
(
req
.
params
.
id
))
if
(
idx
<
0
)
return
res
.
json
({
error
:
'No such tour exists.'
})
tours
.
splice
(
idx
,
1
)
res
.
json
({
success
:
true
})
})
I hope the micro-examples in this chapter gave you a feel for the kind of functionality that is common in an Express application. These examples are intended to be a quick reference you can revisit in the future.
In the next chapter, we’ll dig deeper into templating, which we touched on in the rendering examples in this chapter.
1 Ports 0–1023 are “well-known ports” reserved for common services.
18.119.159.150