Building a site using Node.js and Express
Getting start
Now that we know how to create a project with NPM
Add Express as dependency and create a web server
We can also create some routes to handle our requests
And configure our statics assets
Also, we can create templates using Pug and render them from Express server using the render method
It's time to put everything in action
First lets create a node-site-example folder and change directory into it
mkdir node-site-example
cd node-site-example
Installing packages
After creating the folder lets install pug, express and nodemon
npm install express pug nodemon
Nodemon is a Node.js module that will watch our files to see if we save them and reload the server for us
Configuring scripts
Configure NPM start script
"start": "nodemon"
Nodemon will look for an index.js file to start the server and watch for changes
Using express server
Create an
index.js
file and add a express serverindex.js
const express = require("express");
const app = express();
const port = 3000;
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});And start the server
npm start
> node-site-example@1.0.0 start /node-site-example
> nodemon
[nodemon] 1.17.4
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: *.*
[nodemon] starting `node index.js`
Server running on port 3000
Project configuration
Now we have our server running and we need to configure routes, static assets and the template engine
We can see that nodemon is looking for all our files
[nodemon] watching: *.*
Also we can restart the server writing
rs
on the terminal that is running nodemon on in case we need toCreate a
views
folderAdd
index.pug
to the views folderAdd this code to index.pug
doctype html
head
title Simple site using Node.js, Express and Pug
body
h1 Wellcome to Node.js, Express and Pug
p This project is just to practiceConfigure Express to use pug in the
index.js
app.set('view engine', 'pug');
Finaly add a root get route handler to render the home content using index.html
app.get('/', (req, res) => {
res.render('index', {});
});Your
index.js
file must look like thisconst express = require("express");
const app = express();
const port = 3000;
app.set("view engine", "pug");
app.get("/", (req, res) => {
res.render("index", {});
});
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
Using public folder
It's time to add the static public folder and configure express to use it
Create a
public
folderConfigure Express to use the public folder in the
index.js
app.use(express.static('public'));
We could add all the static assets together as siblings but it's better to organize our code
Create the following folder structure
/node-site-example
|- public
|- css
|- styles.css
|- img
|- js
|- scripts.jsAdd some CSS to the site in the
styles.css
* {
padding: 0;
margin: 0;
}
body {
color: black;
font-family: Arial, Helvetica, sans-serif;
font-size: 16px;
}Add some JS to the site in the
scripts.js
window.onload = function() {
console.log("Loaded site");
};Now that we have the css and js files ready we need to add it to the template
index.pug
link(rel='stylesheet', href="/css/styles.css")
script(src="/js/scripts.js")We use link for the CSS file and script for the JavaScript file
If you refresh the browser now the font should look different and the size too
Also is nice that we removed all padding and margins from the elements
As we'll have to create more than one page we need to create a layout
Create the
layout.pug
file inside the view folderCopy and paste all the content from index.pug template into the layout.pug one
Also we need to add a styles, scripts & content block so we can change the content from the different templates in the
layout.pug
doctype html
html
head
title Simple site using Node.js, Express and Pug
link(rel='stylesheet', href="/css/styles.css")
block styles
script(src="/js/scripts.js")
block scripts
body
block contentNow the index.pug file will only contain the code that is relative only to that file
We need to extend the layout and create the block content in the
index.pug
extends ./layout.pug
block content
h1 Wellcome to Node.js, Express and Pug
p This project is just to practiceCheck that the site it still works as expected
If we don't have any errors we must see the same content but now using the layout
Adding more content
Inside the public/img create a
superheroes
folder and download the following images from here/node-site-example
|- public
|- img
|- blackwidow.jpg
|- captainmarvel.jpg
|- captanamerica.jpg
|- daredevil.jpg
|- hulk.jpg
|- ironman.jpg
|- spiderman.jpg
|- thor.jpg
|- wolverine.jpgNow we have the superheroes images in our static assets folder
We want to create a homepage with some superheores picture and name
Then we can create a superhero description page
Start by creating a superheroes array in the root route handler in the
index.js
app.get("/", (req, res) => {
const superheroes = [
{ name: "SPIDER-MAN", image: "spiderman.jpg" },
{ name: "CAPTAIN MARVEL", image: "captainmarvel.jpg" },
{ name: "HULK", image: "hulk.jpg" },
{ name: "THOR", image: "thor.jpg" },
{ name: "IRON MAN", image: "ironman.jpg" },
{ name: "DAREDEVIL", image: "daredevil.jpg" },
{ name: "BLACK WIDOW", image: "blackwidow.jpg" },
{ name: "CAPTAIN AMERICA", image: "captanamerica.jpg" },
{ name: "WOLVERINE", image: "wolverine.jpg" }
];
res.render("index", { superheroes: superheroes });
});We can see that we have a
superheroes array
that has JavaScript objects as contentEach object has a superhero name and image
Then we pass this superheroes array as superheroes object property
This means that at the template level we'll have a
superheroes
variable that represents this objectsNow lets show the superheroes on our home page
Using each we can iterate the superheroes collection in the
index.pug
each superhero in superheroes
div.superhero-container
img(src='/img/superheroes/' + superhero.image)
h3= superhero.nameUpdate index.pug to match this code:
extends ./layout.pug
block content
h1 Superheroes
p This site shows superheroes information
each superhero in superheroes
div.superhero-container
img(src='/img/superheroes/' + superhero.image)
h3= superhero.nameNow our site has all the superheroes pictures and name but it would be nice to change the design a little
Add the following class to your
styles.css
file.superhero-container {
display: inline-block;
width: 200px;
text-align: center;
margin-right: 10px;
margin-bottom: 40px;
}
.superhero-container img {
width: auto;
max-width: 100%;
}
Adding routes
It would be nice to be able to click the image or the superhero name and see a detail page
To create this feature we need to do a couple of changes
First we need to change the
index.pug
templateextends ./layout.pug
block content
h1 Superheroes
p This site shows superheroes information
each superhero in superheroes
div.superhero-container
a(href="/superheroes/")
img(src='/img/superheroes/' + superhero.image)
h3= superhero.nameWe added a link element that relates this page with
/superheroes/
So far so good but we still don't have the superheroes route configured
Lets add a new route config in the
index.js
app.get("/superheroes/", (req, res) => {
const superheroes = [
{ name: "SPIDER-MAN", image: "spiderman.jpg" },
{ name: "CAPTAIN MARVEL", image: "captainmarvel.jpg" },
{ name: "HULK", image: "hulk.jpg" },
{ name: "THOR", image: "thor.jpg" },
{ name: "IRON MAN", image: "ironman.jpg" },
{ name: "DAREDEVIL", image: "daredevil.jpg" },
{ name: "BLACK WIDOW", image: "blackwidow.jpg" },
{ name: "CAPTAIN AMERICA", image: "captanamerica.jpg" },
{ name: "WOLVERINE", image: "wolverine.jpg" }
];
res.render("superhero", { superheroes: superheroes });
});Great now we have the route but it looks like we have the superheroes repeated
Also we only need to show one superhero at the time
And.. we need to create the superhero template
Uff.. so many things we better start soon!
Creating more templates
Create the
superhero.pug
template inside the views folderAdd the following code in the
superhero.pug
extends ./layout.pug
block content
img(src='/img/superheroes/' + superhero.image)
h3= superhero.name
Getting the data by id
Great we are using the layout template that we created but we don't have the superhero data
How can we deal with this situation?
So we know that we can use the router to pass data to the template
But we need to know the selected superhero, right?
We can use the superhero name to select the selected superhero
Or we can use an id
To use the id will have to update the superheroes array objects
const superheroes = [
{ id: 1, name: "SPIDER-MAN", image: "spiderman.jpg" },
{ id: 2, name: "CAPTAIN MARVEL", image: "captainmarvel.jpg" },
{ id: 3, name: "HULK", image: "hulk.jpg" },
{ id: 4, name: "THOR", image: "thor.jpg" },
{ id: 5, name: "IRON MAN", image: "ironman.jpg" },
{ id: 6, name: "DAREDEVIL", image: "daredevil.jpg" },
{ id: 7, name: "BLACK WIDOW", image: "blackwidow.jpg" },
{ id: 8, name: "CAPTAIN AMERICA", image: "captanamerica.jpg" },
{ id: 9, name: "WOLVERINE", image: "wolverine.jpg" }
];Great now we have ids on our superheroes objects
I don't know about you but I think it's still pretty bad to have this duplicated array
Also we'll need the ids to create the links
What about if we move this array to a higher score level so both routes can use it? Check the updated
index.js
const express = require("express");
const app = express();
const port = 3000;
app.set("view engine", "pug");
app.use(express.static("public"));
const superheroes = [
{ id: 1, name: "SPIDER-MAN", image: "spiderman.jpg" },
{ id: 2, name: "CAPTAIN MARVEL", image: "captainmarvel.jpg" },
{ id: 3, name: "HULK", image: "hulk.jpg" },
{ id: 4, name: "THOR", image: "thor.jpg" },
{ id: 5, name: "IRON MAN", image: "ironman.jpg" },
{ id: 6, name: "DAREDEVIL", image: "daredevil.jpg" },
{ id: 7, name: "BLACK WIDOW", image: "blackwidow.jpg" },
{ id: 8, name: "CAPTAIN AMERICA", image: "captanamerica.jpg" },
{ id: 9, name: "WOLVERINE", image: "wolverine.jpg" }
];
app.get("/", (req, res) => {
res.render("index", { superheroes: superheroes });
});
app.get("/superheroes/", (req, res) => {
res.render("superhero", { superheroes: superheroes });
});
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
Refining the code
This is looking much better now
We still have a problem on how to know the selected superhero
Having the ids to identify them is great but we still need to update our code
First modify the links to use the superhero id in the
index.pug
a(href="/superheroes/" + superhero.id)
If the user clicks on this link it will redirect to a url that looks like this: http://localhost:3000/superheroes/2
So it looks like the user will select a superhero and we'll go to the /superheroes/ page and we have the id
Now we need to update our express route so we can get the id param and get the superhero data in the
index.js
app.get("/superheroes/:id", (req, res) => {
const selectedId = req.params.id;
let selectedSuperhero = superheroes.filter(superhero => {
return superhero.id === +selectedId;
});
selectedSuperhero = selectedSuperhero[0];
res.render("superhero", { superhero: selectedSuperhero });
});Using
'/superheroes/:id'
we define that this route contains a parameter that we need to getThis request parameter is called id and will come after the
superheroes
routeTo get this value we use
req.params.id
We could name this parameter with any name
Then we filter the superheroes array by id
And assighn the selected superhero to the
selectedSuperhero
variableThe only remainding thing to do is render the template using the selectedSuperhero data
Now we can call any this url
http://localhost:3000/superheroes/2
changing the id from 1 to 9Our home page still has blue and violet links so update the css so it looks better
Improving the CSS
styles.css
.superhero-container a {
color: black;
text-decoration: none;
}Great we're able to show the superheroes home and detail page
Adding new data
It would be really nice to be able to create a new superhero too, right?
To create any new resource we need to create a form
We'll create a new template and add a form
Create the
create.pug
file in the views folder and add the following codediv.create-container
form(action="/superheroes", method="post")
input(type="text", placeholder="suerhero name", required="required", name="superhero")
button CreateThis code will transform in this HTML
<div class="create-container">
<form action="/superheroes" method="post">
<input type="text" placeholder="suerhero name" required="required" name="superhero" />
<button>Create</button>
</form>
</div>So we can see that it's just a form that when gets submited it will submit the values to
/superheroes
As we have an input with the
superhero name
we'll be able to retrieve the value from the server using this same nameThe form is set to use the HTTP post method so we'll need to create a route handle to handle this request
It's nice to have the template ready but we still need to add it to the index.pug file
We can do this using Pug
include
Add the following line to your
index.pug
file:include ./create.pug
extends ./layout.pug
block content
h1 Superheroes
p This site shows superheroes information
include ./create.pug
each superhero in superheroes
div.superhero-container
a(href="/superheroes/" + superhero.id)
img(src='/img/superheroes/' + superhero.image)
h3= superhero.nameAnd now add some styles so the form looks better in the
styles.css
.create-container {
padding: 10px;
}
.create-container input[type="text"] {
font-size: 16px;
padding: 5px;
}
.create-container button {
font-size: 16px;
padding: 5px;
margin-left: 10px;
}
Handling POST requests
Now that our views are ready lets create the route handler
To hande post requests with express we need to install body-parser module
From the body-parser site we get this definition:
Parse incoming request bodies in a middleware before your handlers, available under the req.body property
This means that we need to configure body-parser as middleware and it will append the submited values to the request object body property
Install body-parser as dependency
npm i body-parser
After installing it we can require it from our
index.js
fileconst bodyParser = require("body-parser");
Add body-parser as middleware in the
index.js
const urlencodedParser = bodyParser.urlencoded({ extended: false });
After configured body-parser as middleware we can create our route handler
This route handler will listen post requests on /superheroes
It will recibe the superhero name as request body parameter
Once we have the superhero name we'll have to create a new superhero object and append it to the superheroes list
Then send a response to the client
The new route handler must have the following code in your
index.js
app.post("/superheroes", urlencodedParser, (req, res) => {
const newId = superheroes[superheroes.length - 1].id + 1;
const newSuperHero = {
id: newId,
name: req.body.superhero.toUpperCase(),
image: "lukecage.jpg"
};
superheroes.push(newSuperHero);
res.redirect("/");
});First we create a post route handler to
app.post('/superheroes')
Then we added body-parser to this call using the
urlencodedParser
middlewareBody parser allows us to configure it for all routes or some of them
In this case we only need it for this route and that's why we use
urlencodedParser
When we get a request body-parser will add the values to the request object body
This means that in our route this it's going to be
req.body
In this example the req.body will look something like:
{
superhero: "Value from the form";
}To get the submited superhero value we use
req.body.superhero
and as it's a string we just calltoUpperCase()
so it's consistent with the rest of the superheroes namesAs we need an ID for the new super hero we can do something like:
const newId = superheroes[superheroes.length - 1].id + 1;
We'll get the last superhero id and increment one
This option is valid as it's not code that we'll put in production
We can get this value from a database once we insert the new value
After getting the new id we can create a new superhero object
const newSuperHero = {
id: newId,
name: req.body.superhero.toUpperCase(),
image: "lukecage.jpg"
};We use the same object structure as the rest of the superheroes objects
Use newId as id
And the name we get it from the request body
As we don't have an image for now let's add some value
Now that we have the superhero we can add it to the collection
superheroes.push(newSuperHero);
Responding requests
And then we send the response.. oh wait.. what do we respond?
In all our previous routes we send something back as response but in this case we created a new route handler
We could return the same render response that we send on
/
If we do this the user will see that we're showing a different url but with the same content
So what we can do it's redirect the request to the
/
handlerAs we added a new superhero to the collection it will get all the superheroes with the one we just created
Then it will render the template and send the response back to the user
The user won't notice all the things that just happened
So our
index.js
file looks like this:const express = require("express");
const app = express();
const port = 3000;
const bodyParser = require("body-parser");
const urlencodedParser = bodyParser.urlencoded({ extended: false });
app.set("view engine", "pug");
app.use(express.static("public"));
const superheroes = [
{ id: 1, name: "SPIDER-MAN", image: "spiderman.jpg" },
{ id: 2, name: "CAPTAIN MARVEL", image: "captainmarvel.jpg" },
{ id: 3, name: "HULK", image: "hulk.jpg" },
{ id: 4, name: "THOR", image: "thor.jpg" },
{ id: 5, name: "IRON MAN", image: "ironman.jpg" },
{ id: 6, name: "DAREDEVIL", image: "daredevil.jpg" },
{ id: 7, name: "BLACK WIDOW", image: "blackwidow.jpg" },
{ id: 8, name: "CAPTAIN AMERICA", image: "captanamerica.jpg" },
{ id: 9, name: "WOLVERINE", image: "wolverine.jpg" }
];
app.get("/", (req, res) => {
res.render("index", { superheroes: superheroes });
});
app.get("/superheroes/:id", (req, res) => {
const selectedId = req.params.id;
let selectedSuperhero = superheroes.filter(superhero => {
return superhero.id === +selectedId;
});
selectedSuperhero = selectedSuperhero[0];
res.render("superhero", { superhero: selectedSuperhero });
});
app.post("/superheroes", urlencodedParser, (req, res) => {
const newId = superheroes[superheroes.length - 1].id + 1;
const newSuperHero = {
id: newId,
name: req.body.superhero.toUpperCase(),
image: "lukecage.jpg"
};
superheroes.push(newSuperHero);
res.redirect("/");
});
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});It's so good to see how our project keeps on groing and that we can add more features to it
I'm sure that you're thinking about how to upload a picture
To support this we need to refactor our code to use multer instead of body-parser
Install multer as dependency
npm i multer