In Chapter 2, you learned how to create a simple web server using only Node. In this chapter, we will re-create that server using Express. This will provide a jumping-off point for the rest of the content of this book and introduce you to the basics of Express.
Scaffolding is not a new idea, but many people (myself included) were introduced to the concept by Ruby. The idea is simple: most projects require a certain amount of so-called boilerplate code, and who wants to re-create that code every time you begin a new project? A simple way is to create a rough skeleton of a project, and every time you need a new project, you just copy this skeleton, or template.
Ruby on Rails took this concept one step further by providing a program that would automatically generate scaffolding for you. The advantage of this approach is that it could generate a more sophisticated framework than just selecting from a collection of templates.
Express has taken a page from Ruby on Rails and provided a utility to generate scaffolding to start your Express project.
While the Express scaffolding utility is useful, I think it’s valuable to learn how to set up Express from scratch. In addition to learning more, you have more control over what gets installed and the structure of your project. Also, the Express scaffolding utility is geared toward server-side HTML generation and is less relevant for APIs and single-page applications.
While we won’t be using the scaffolding utility, I encourage you to take a
look at it once you’ve finished the book: by then you’ll be armed with
everything you need to know to evaluate whether the scaffolding it
generates is useful for you. For more information, see the
express-generator
documentation.
Throughout this book, we’ll be using a running example: a fictional website for Meadowlark Travel, a company offering services for people visiting the great state of Oregon. If you’re more interested in creating an API, have no fear: the Meadowlark Travel website will expose an API in addition to serving a functional website.
Start by creating a new directory: this will be the root directory for your project. In this book, whenever we refer to the project directory, app directory, or project root, we’re referring to this directory.
You’ll probably want to keep your web app files separate from all the other files that usually accompany a project, such as meeting notes, documentation, etc. For that reason, I recommend making your project root a subdirectory of your project directory. For example, for the Meadowlark Travel website, I might keep the project in ~/projects/meadowlark, and the project root in ~/projects/meadowlark/site.
npm manages project dependencies—as well as metadata about the project—in a
file called package.json. The easiest way to create this file is to
run npm init
: it will ask you a series of questions and generate a
package.json file to get you started (for the “entry point” question,
use meadowlark.js for the name of your project).
Every time you run npm, you may get warnings about a missing description or repository field. It’s safe to ignore these warnings, but if you want to eliminate them, edit the package.json file and provide values for the fields npm is complaining about. For more information about the fields in this file, see the npm package.json documentation.
The first step will be installing Express. Run the following npm command:
npm install express
Running npm install
will install the named package(s) in the
node_modules directory and
update the package.json file. Since the node_modules directory can
be regenerated at any time with npm, we will not save it in our repository.
To ensure we don’t accidentally add it to our repository, we create a file
called .gitignore:
# ignore packages installed by npm node_modules # put any other files you don't want to check in here, such as .DS_Store # (OSX), *.bak, etc.
Now create a file called meadowlark.js. This will be our project’s entry point. Throughout the book, we will simply be referring to this file as the app file (ch03/00-meadowlark.js in the companion repo):
const
express
=
require
(
'express'
)
const
app
=
express
()
const
port
=
process
.
env
.
PORT
||
3000
// custom 404 page
app
.
use
((
req
,
res
)
=>
{
res
.
type
(
'text/plain'
)
res
.
status
(
404
)
res
.
send
(
'404 - Not Found'
)
})
// custom 500 page
app
.
use
((
err
,
req
,
res
,
next
)
=>
{
console
.
error
(
err
.
message
)
res
.
type
(
'text/plain'
)
res
.
status
(
500
)
res
.
send
(
'500 - Server Error'
)
})
app
.
listen
(
port
,
()
=>
console
.
log
(
`Express started on http://localhost:
${
port
}
; `
+
`press Ctrl-C to terminate.`
))
Many tutorials, as well as the Express scaffolding generator,
encourage you to name your primary file app.js (or sometimes
index.js or server.js). Unless you’re
using a hosting service or deployment system that requires your main
application file to have a specific name, I don’t feel there’s a compelling
reason to do this, and I prefer to name the primary file after the project.
Anyone who’s ever stared at a bunch of editor tabs that all say
“index.html” will immediately see the wisdom of this. npm init
will
default to index.js; if you use a different name for your application
file, make sure to update the
main
property in package.json.
You now have a minimal Express server. You can start the server (node
meadowlark.js
) and navigate to http://localhost:3000. The result
will be disappointing: you haven’t provided Express with any routes, so it
will simply give you a generic 404 message indicating that the page doesn’t
exist.
Note how we choose the port that we want our application to
run on: const port = process.env.PORT || 3000
. This allows us to
override the port by setting an environment variable before you start the
server. If your app isn’t running on port
3000 when you run this example, check to see whether your PORT
environment
variable is set.
Let’s add some routes for the home page and an About page. Before the 404 handler, we’ll add two new routes (ch03/01-meadowlark.js in the companion repo):
app
.
get
(
'/'
,
(
req
,
res
)
=>
{
res
.
type
(
'text/plain'
)
res
.
send
(
'Meadowlark Travel'
);
})
app
.
get
(
'/about'
,
(
req
,
res
)
=>
{
res
.
type
(
'text/plain'
)
res
.
send
(
'About Meadowlark Travel'
)
})
// custom 404 page
app
.
use
((
req
,
res
)
=>
{
res
.
type
(
'text/plain'
)
res
.
status
(
404
)
res
.
send
(
'404 - Not Found'
)
})
app.get
is the method by which we’re adding routes. In the Express documentation, you
will see app.METHOD
. This doesn’t mean that there’s literally a method
called METHOD
; it’s just a placeholder for your (lowercased) HTTP verbs
(get
and post
being the most common). This method
takes two parameters: a path and a function.
The path is what defines the route. Note that app.METHOD
does the heavy
lifting for you: by default, it doesn’t care about the case or trailing
slash, and it doesn’t consider the querystring when performing the match.
So the route for the About page will work for /about, /About,
/about/, /about?foo=bar, /about/?foo=bar, etc.
The function you provide will get invoked when the route is matched. The parameters passed to that function are the request and response objects, which we’ll learn more about in Chapter 6. For now, we’re just returning plain text with a status code of 200 (Express defaults to a status code of 200—you don’t have to specify it explicitly).
I highly recommend getting a browser plug-in that shows you the status code of the HTTP request as well as any redirects that took place. It will make it easier to spot redirect issues in your code or incorrect status codes, which are often overlooked. For Chrome, Ayima’s Redirect Path works wonderfully. In most browsers, you can see the status code in the Network section of the developer tools.
Instead of using Node’s low-level res.end
, we’re switching to using
Express’s extension, res.send
. We are also replacing Node’s
res.writeHead
with res.set
and res.status
. Express is also providing us a
convenience method, res.type
, which sets the Content-Type
header. While it’s still
possible to use res.writeHead
and res.end
, it isn’t necessary or
recommended.
Note that our custom 404 and 500 pages must be handled slightly
differently. Instead of using app.get
, we are using
app.use
. app.use
is the method by which Express adds
middleware. We’ll be covering
middleware in more depth in Chapter 10, but for now, you can think
of this as a catchall handler for anything that didn’t get matched by a
route. This brings us to an important point: in Express, the order in
which routes and middleware are added is significant. If we put the 404 handler above the
routes, the home page and About page would stop working; instead, those
URLs would result in a 404. Right now, our routes are pretty simple, but
they also support wildcards, which can lead to problems with
ordering. For example, what if we
wanted to add subpages to About, such as /about/contact and
/about/directions? The following will not work as expected:
app
.
get
(
'/about*'
,
(
req
,
res
)
=>
{
// send content....
})
app
.
get
(
'/about/contact'
,
(
req
,
res
)
=>
{
// send content....
})
app
.
get
(
'/about/directions'
,
(
req
,
res
)
=>
{
// send content....
})
In this example, the /about/contact
and /about/directions
handlers will
never be matched because the first handler uses a wildcard in its path:
/about*
.
Express can distinguish between the 404 and 500 handlers by the number of arguments their callback functions take. Error routes will be covered in depth in Chapter 10 and Chapter 12.
Now you can start the server again and see that there’s a functioning home page and About page.
So far, we haven’t done anything that couldn’t be done just as easily
without Express, but already Express is providing us some functionality
that isn’t immediately obvious. Remember in the previous chapter how we
had to normalize req.url
to determine what resource was being requested?
We had to manually strip off the querystring and the trailing slash and
convert to lowercase. Express’s router is now handling those details for
us automatically. While it may not seem like a large thing now, it’s only
scratching the surface of what Express’s router is capable of.
If you’re familiar with the “model-view-controller” paradigm, then the concept of a view will be no stranger to you. Essentially, a view is what gets delivered to the user. In the case of a website, that usually means HTML, though you could also deliver a PNG or a PDF or anything that can be rendered by the client. For our purposes, we will consider views to be HTML.
A view differs from a static resource (like an image or CSS file) in that a view doesn’t necessarily have to be static: the HTML can be constructed on the fly to provide a customized page for each request.
Express supports many different view engines that provide different levels of abstraction. Express gives some preference to a view engine called Pug (which is no surprise, because it is also the brainchild of TJ Holowaychuk). The approach Pug takes is minimal: what you write doesn’t resemble HTML at all, which certainly represents a lot less typing (no more angle brackets or closing tags). The Pug engine then takes that and converts it to HTML.
Pug was originally called Jade, and the name changed with the release of version 2 because of a trademark issue.
Pug is appealing, but that level of abstraction comes at a cost. If you’re a frontend developer, you have to understand HTML and understand it well, even if you’re actually writing your views in Pug. Most frontend developers I know are uncomfortable with the idea of their primary markup language being abstracted away. For this reason, I am recommending the use of another, less abstract templating framework called Handlebars.
Handlebars (which is based on the popular language-independent templating language Mustache) doesn’t attempt to abstract away HTML for you: you write HTML with special tags that allow Handlebars to inject content.
In the years following the original release of this book, React has taken the world by storm…which abstracts HTML away from frontend developers! Viewed through that lens, my prediction that frontend developers didn’t want HTML abstracted away hasn’t stood the test of time. However, JSX (the JavaScript language extension that most React developers use) is (almost) identical to writing HTML, so I wasn’t entirely wrong.
To provide Handlebars support, we’ll use Eric Ferraiuolo’s
express-handlebars
package. In your project directory, execute the following:
npm install express-handlebars
Then in meadowlark.js, modify the first few lines (ch03/02-meadowlark.js in the companion repo):
const
express
=
require
(
'express'
)
const
expressHandlebars
=
require
(
'express-handlebars'
)
const
app
=
express
()
// configure Handlebars view engine
app
.
engine
(
'handlebars'
,
expressHandlebars
({
defaultLayout
:
'main'
,
}))
app
.
set
(
'view engine'
,
'handlebars'
)
This creates a view engine and configures Express to use it by default. Now create a directory called views that has a subdirectory called layouts. If you’re an experienced web developer, you’re probably already comfortable with the concepts of layouts (sometimes called master pages). When you build a website, there’s a certain amount of HTML that’s the same—or very close to the same—on every page. It not only becomes tedious to rewrite all that repetitive code for every page, but also creates a potential maintenance nightmare: if you want to change something on every page, you have to change all the files. Layouts free you from this, providing a common framework for all the pages on your site.
So let’s create a template for our site. Create a file called views/layouts/main.handlebars:
<!doctype html>
<html>
<head>
<title>
Meadowlark Travel</title>
</head>
<body>
{{{body}}}</body>
</html>
The only thing that you probably haven’t seen before is this:
{{{body}}}
. This expression will be replaced with the HTML for
each view. When we created the Handlebars instance, note we specified the
default layout (defaultLayout: 'main'
). That means that unless you
specify otherwise, this is the layout that will be used for any
view.
Now let’s create view pages for our home page, views/home.handlebars:
<h1>
Welcome to Meadowlark Travel</h1>
Then our About page, views/about.handlebars:
<h1>
About Meadowlark Travel</h1>
Then our Not Found page, views/404.handlebars:
<h1>
404 - Not Found</h1>
And finally our Server Error page, views/500.handlebars:
<h1>
500 - Server Error</h1>
You probably want your editor to associate .handlebars and
.hbs (another common extension for Handlebars files) with HTML to
enable syntax highlighting and
other editor features. For vim, you can add the line au BufNewFile,BufRead *.handlebars
set filetype=html
to your ~/.vimrc file. For other editors, consult
your documentation.
Now that we have some views set up, we have to replace our old routes with new ones that use these views (ch03/02-meadowlark.js in the companion repo):
app
.
get
(
'/'
,
(
req
,
res
)
=>
res
.
render
(
'home'
))
app
.
get
(
'/about'
,
(
req
,
res
)
=>
res
.
render
(
'about'
))
// custom 404 page
app
.
use
((
req
,
res
)
=>
{
res
.
status
(
404
)
res
.
render
(
'404'
)
})
// custom 500 page
app
.
use
((
err
,
req
,
res
,
next
)
=>
{
console
.
error
(
err
.
message
)
res
.
status
(
500
)
res
.
render
(
'500'
)
})
Note that we no longer have to specify the content type or status code: the
view engine will return a content type of text/html
and a status code
of 200 by default. In the catchall handler, which provides our custom 404 page,
and the 500 handler, we have to set the status code explicitly.
If you start your server and check out the home or About page, you’ll see that the views have been rendered. If you examine the source, you’ll see that the boilerplate HTML from views/layouts/main.handlebars is there.
Even though every time you visit the home page, you get the same HTML, these routes are considered dynamic content, because we could make a different decision each time the route gets called (which we’ll see plenty of later in this book). However, content that really never changes, in other words, static content, is common and important, so we’ll consider static content next.
Express relies on middleware to handle static files and views. Middleware is a concept that will be covered in more detail in Chapter 10. For now, it’s sufficient to know that middleware provides modularization, making it easier to handle requests.
The static
middleware allows you to designate one or more directories as
containing static resources that are simply to be delivered to the client
without any special handling. This is
where you would put things such as images, CSS files, and client-side
JavaScript files.
In your project directory, create a subdirectory called public (we call
it public because anything in this directory will be served to the
client without question). Then, before you declare any routes, you’ll add
the static
middleware (ch03/02-meadowlark.js in the companion repo):
app
.
use
(
express
.
static
(
__dirname
+
'/public'
))
The static
middleware has the same effect as creating a route for each
static file you want to deliver that renders a file and returns it to the
client. So let’s
create an img subdirectory inside public and put our logo.png
file in there.
Now we can simply reference /img/logo.png (note, we do not specify
public
; that directory is invisible to the client), and the static
middleware will serve that file, setting the content type
appropriately. Now let’s
modify our layout so that our logo appears on every page:
<body>
<header>
<img
src=
"/img/logo.png"
alt=
"Meadowlark Travel Logo"
>
</header>
{{{body}}}</body>
Remember that middleware is processed in order, and static middleware—which is usually declared first or at least very early—will override other routes. For example, if you put an index.html file in the public directory (try it!), you’ll find that the contents of that file get served instead of the route you configured! So if you’re getting confusing results, check your static files and make sure there’s nothing unexpected matching the route.
Views aren’t simply a complicated way to deliver static HTML (though they can certainly do that as well). The real power of views is that they can contain dynamic information.
Let’s say that on the About page, we want to deliver a “virtual fortune cookie.” In our meadowlark.js file, we define an array of fortune cookies:
const
fortunes
=
[
"Conquer your fears or they will conquer you."
,
"Rivers need springs."
,
"Do not fear what you don't know."
,
"You will have a pleasant surprise."
,
"Whenever possible, keep it simple."
,
]
Modify the view (/views/about.handlebars) to display a fortune:
<h1>
About Meadowlark Travel</h1>
{{#if fortune}}<p>
Your fortune for the day:</p>
<blockquote>
{{fortune}}</blockquote>
{{/if}}
Now modify the route /about to deliver the random fortune cookie:
app
.
get
(
'/about'
,
(
req
,
res
)
=>
{
const
randomFortune
=
fortunes
[
Math
.
floor
(
Math
.
random
()
*
fortunes
.
length
)]
res
.
render
(
'about'
,
{
fortune
:
randomFortune
})
})
Now if you restart the server and load the /about page, you’ll see a random fortune, and you’ll get a new one every time you reload the page. Templating is incredibly useful, and we will be covering it in depth in Chapter 7.
3.138.118.250