Skip to content

Latest commit

 

History

History
634 lines (576 loc) · 20.5 KB

buildingsite.md

File metadata and controls

634 lines (576 loc) · 20.5 KB

Building a site using Node.js and Express

  • 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
  • You can donwload the project code from the github repo
  • First lets create a node-site-example folder and change directory into it
mkdir node-site-example
cd node-site-example
  • 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
npm init -y
  • Configure NPM start script in your package.json, it should look like this (the dependencies versions can be different:
{
  "name": "node-site-example",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "dependencies": {
    "express": "^4.17.1",
    "nodemon": "^2.0.7",
    "pug": "^3.0.2"
  },
  "devDependencies": {},
  "scripts": {
    "start": "nodemon"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
  • Nodemon will look for an index.js file to start the server and watch for changes

  • Create an index.js file and add a express server

  • index.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

> [email protected] 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
  • 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 to

  • Create a views folder

  • Add index.pug to the views folder

  • Add this code to index.pug

  • 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 practice
  • Configure Express to use pug
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 this

  • index.js

const 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}`);
});
  • It's time to add the static public folder and configure express to use it
  • Create a public folder
  • Configure Express to use the public folder
  • 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.js
  • Add some CSS to the site
  • styles.css
* {
  padding: 0;
  margin: 0;
}

body {
  color: black;
  font-family: Arial, Helvetica, sans-serif;
  font-size: 16px;
}
  • Add some JS to the site
  • 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 folder
  • Copy and paste all the content from index.pug template into the layout.pug one and add the blocks styles, scripts and content (use the reference below)
  • Also we need to add a styles, scripts & content block so we can change the content from the different templates
  • 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 content
  • Now 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
extends ./layout.pug

block content
  h1 Wellcome to Node.js, Express and Pug
  p This project is just to practice
  • Check 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
  • Inside the public/img download the following images from here
/node-site-example
|- public
    |- img
        |- blackwidow.jpeg
        |- captainmarvel.jpeg
        |- captainamerica.jpeg
        |- daredevil.jpeg
        |- hulk.jpeg
        |- ironman.jpeg
        |- spiderman.jpeg
        |- thor.jpeg
        |- wolverine.jpeg
  • Now 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 superheroe description page
  • Start by creating a superheroes array in the root route handler
  • 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: 'captainamerica.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 content
  • Each object has a superheroe 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 objects
  • Now lets show the superheroes on our home page
  • Using each we can iterate the superheroes collection
each superheroe in superheroes
  div.superheroe-container
    img(src='/img/' + superheroe.image)
    h3= superheroe.name
  • Update index.pug to match this code:
extends ./layout.pug

block content
  h1 Superheroes
  p This site shows superheroes information
  each superheroe in superheroes
    div.superheroe-container
      img(src='/img/' + superheroe.image)
      h3= superheroe.name
  • Now 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
  • styles.css
.superheroe-container {
  display: inline-block;
  width: 200px;
  text-align: center;
  margin-right: 10px;
  margin-bottom: 40px;
}  

.superheroe-container img {
  max-height: 300px;
} 
  • 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 template
extends ./layout.pug

block content
  h1 Superheroes
  p This site shows superheroes information
  each superheroe in superheroes
    div.superheroe-container
      a(href="/superheroes/")
        img(src='/img/' + superheroe.image)
        h3= superheroe.name
  • We 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
  • index.js
app.get('/superheros/', (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: 'captainamerica.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!
  • Create the superhero.pug template inside the views folder
  • Add the following code
  • superheroe.pug
extends ./layout.pug

block content
  img(src='/img/' + superheroe.image)
  h3= superheroe.name
  • Great we are using the layout template that we created but we don't have the superheroe 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 superheroe, 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: 'captainamerica.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 scoe level so both routes can use it?
  • 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: 'captainamerica.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}`);
});
  • 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
  • index.pug
a(href="/superheroes/" + superheroe.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
  • index.js
app.get('/superheros/:id', (req, res) => {
  const selectedId = req.params.id;

  let selectedSuperhero = superheroes.filter(superhero => {
    return superhero.id === +selectedId;
  });

  selectedSuperhero = selectedSuperhero[0];
  
  res.render('superhero', { superheroe: selectedSuperhero });
});
  • Using '/superheros/:id' we define that this route contains a parameter that we need to get
  • This request parameter is called id and will come after the superheros route
  • To 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 variable
  • The only remainding thing to do is render the template using the selectedSuperhero data
  • Now we can call any this url http://localhost:3000/superheros/2 changing the id from 1 to 9
  • Our home page still has blue and violet links so update the css so it looks better
  • styles.css
.superheroe-container a {
  color: black;
  text-decoration: none;
}
  • Great we're able to show the superheroes home and detail page

  • 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 code

  • create.pug

div.create-container
  form(action="/superheros",  method="post")
    input(type="text", placeholder="suerhero name", required="required", name="superhero")
    button Create
  • This code will transform in this HTML
<div class="create-container">
  <form action="/superheros" 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 /superheros
  • As we have an input with the superhero name we'll be able to retrieve the value from the server using this same name
  • The 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
  • index.pug
extends ./layout.pug

block content
  h1 Superheroes
  p This site shows superheroes information
  include ./create.pug

  each superheroe in superheroes
    div.superheroe-container
      a(href="/superheros/" + superheroe.id)
        img(src='/img/' + superheroe.image)
        h3= superheroe.name
  • And now add some styles so the form looks better
  • 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;
}
  • 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 file
  • index.js
const bodyParser = require('body-parser');
  • Add body-parser as middleware
  • 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 /superheros

  • 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:

  • index.js

app.post('/superheros', 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('/superheros')
  • Then we added body-parser to this call using the urlencodedParser middleware
  • Body 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 call toUpperCase() so it's consistent with the rest of the superheroes names
  • As 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
  • Also you can download this lukecage.jpg and put it inside the img folder
  • Now that we have the superhero we can add it to the collection
superheroes.push(newSuperHero);
  • 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 / handler
  • As 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: 'captainamerica.jpg' },
  { id: 9, name: 'WOLVERINE', image: 'wolverine.jpg' },
];

app.get('/', (req, res) => {
  res.render('index', { superheroes: superheroes });
});

app.get('/superheros/:id', (req, res) => {
  const selectedId = req.params.id;

  let selectedSuperhero = superheroes.filter(superhero => {
    return superhero.id === +selectedId;
  });

  selectedSuperhero = selectedSuperhero[0];
  
  res.render('superhero', { superheroe: selectedSuperhero });
});

app.post('/superheros', 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