Skip to content

Latest commit

 

History

History
661 lines (568 loc) · 20.7 KB

buildingsite.md

File metadata and controls

661 lines (568 loc) · 20.7 KB

Building a site using Node.js and Express

<- Go Back

  • 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
  • Initialize the project with:

    npm init -y
    
  • Now 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

  • Configure NPM start script

    "start": "nodemon"
  • 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
    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

  • 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 create a superheroes folder and download the following images

    /node-site-example
    |- public
        |- img
            |- blackwidow.jpg
            |- captainmarvel.jpg
            |- captanamerica.jpg
            |- daredevil.jpg
            |- hulk.jpg
            |- ironman.jpg
            |- spiderman.jpg
            |- thor.jpg
            |- wolverine.jpg
    
  • 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: "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 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;
    }
  • 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: "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!

  • 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: "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 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: "captanamerica.jpg" },
      { id: 9, name: "WOLVERINE", image: "wolverine.jpg" },
    ];
    
    app.get("/", (req, res) => {
      res.render("index", { superheroes: superheroes });
    });
    
    app.get("/superheros/", (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("/superheroes/: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 assign 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="superhero 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="superhero 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: "captanamerica.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
    

Let's Checkout MongoDB