Tuesday, 17 December 2024

Web Tutorial: NodeJS Christmas Mailer (Part 1/3)

Tis the season to be jolly, readers! In the spirit of learning, we'll be exploring a bit of NodeJS today. I'm a bit of a beginner here myself, so bear with me!

What we'll do here, is create a web form that will send a HTML email. Sounds simple, and it is! However, that comes with a few considerations. So buckle up; this sleigh is going on a ride.

You should already have a Node environment, and set up the folder in which you'll be working. To begin, we install Express because it will take care of the tedious and repetitive scaffolding.
install --save express


Now create this file. This will be the file which we run whenever we need to restart the script. We need to run the require() function with "express" as an argument, then assign the returned value (which is another function) to the variable express.

app.js
var express = require("express");


Then we run the newly-created function express() and assign the value to app.

app.js
var express = require("express");

var app = express();


Next, we'll install Handlebars as a templating engine. It's not absolutely necessary, of course, but knowing how to use a templating engine is a good basic skill. Run this in the command line.
install --save express-handlebars


Again, we need to require() handlebars. Assign the returned value to the variable handlebars.

app.js
var express = require("express");

var app = express();

var handlebars = require("express-handlebars");


Then chain this to use the method create(). In it, we pass an object with one single property - defaultLayout with a value of "main".

app.js
var express = require("express");

var app = express();

var handlebars = require("express-handlebars").create({defaultLayout:"main"});


Then, by using app's engine() method, we set the app to use the Handlebars engine (represented by the engine property of the handlebars object) and map the ".handlebars" extension to that engine.

app.js
var express = require("express");

var app = express();

var handlebars = require("express-handlebars").create({defaultLayout:"main"});
app.engine("handlebars", handlebars.engine);


Now let's set some stuff. Use the set() method of the app object, to set the view engine to handlebars.

app.js
var express = require("express");

var app = express();

var handlebars = require("express-handlebars").create({defaultLayout:"main"});
app.engine("handlebars", handlebars.engine);

app.set("view engine", "handlebars");


We also want to set the port. In this case, we use the core process object's PORT property (within the sub-object env) or 3000 if it doesn't exist (as can be the case in a different environment).

app.js
var express = require("express");

var app = express();

var handlebars = require("express-handlebars").create({defaultLayout:"main"});
app.engine("handlebars", handlebars.engine);

app.set("view engine", "handlebars");
app.set("port", process.env.PORT || 3000);


After that, we'll want to do routes. Here's our first route, and it's the most basic one. It's a GET request and it's the home address. Just a slash, with nothing after it. The second argument in the get() method is a callback. The callback uses the parameters req and res to represent the request and response.

app.js
app.set("view engine", "handlebars");
app.set("port", process.env.PORT || 3000);

app.get("/", (req, res)=> {

});


In the callback, we use the response's render() method, and pass in "form" as the argument. This means that for the home page, we want to render the form page.

app.js
app.get("/", (req, res)=> {
  res.render("form");
});


Finally, we set the app to listen at the port we set earlier. We don't have to add any more statements to the callback, but it's useful for if we need to do any troubleshooting.

app.js
app.get("/", (req, res)=> {
  res.render("form");
});

app.listen(app.get("port"), ()=> {
  
});


Remember that the default layout is main and the home page is form? Our next steps will bear this in mind. In Handlebars, these files are in the views directory by default. And layouts are stored in the layouts directory of the views directory. Let's just start with form. Create form.handlebars in views. We'll start simple, with a h1 tag and a paragraph.

views/form.handlebars
<h1>Welcome to Teochew Thunder's Xmas Mailer.</h1>
<p>Email a friend!</p>


Now let's do the layout. Create the layouts directory within views, and then create main.handlebars in that sub-directory. It's a bare-bones HTML boilerplate.

views/layouts/main.handlebars
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Xmas Mailer</title>
  </head>
  <body>

  </body>
</html>


We will add the template string that brings body into the body tag. body is a keyword in the templating engine that represents the HTML content. And in this case, the HTML content will be inside main.handlebars.

views/layouts/main.handlebars
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Xmas Mailer</title>
  </head>
  <body>
    {{{ body }}}
  </body>
</html>


You'll see this in your browser at port 3000!


Time to add some styling. Generally there's a better way to do this, but I'm just going to put all the styling in the main layout.

views/layouts/main.handlebars
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Xmas Mailer</title>

    <style>
      body {
        font-family: verdana, sans-serif;
        font-size: 14px;
        background-color: rgba(255, 200, 0, 0.5);
        color: rgba(255, 100, 0, 0.8);
      }

      h1, p {
        text-align: center;
      }
    </style>

  </head>
  <body>
    {{{ body }}}
  </body>
</html>


Here we are, styling applied.


We'll need a few more! Add the "thank you" route. It's a GET, and renders the thankyou page.

app.js
app.get("/", (req, res)=> {
  res.render("form");
});

app.get("/thankyou", (req, res)=> {
  res.render("thankyou");
});


app.listen(app.get("port"), ()=> {

});


Create this file in the views directory. It has a h1 tag, two paragraphs and a link.

views/thankyou.handlebars
<h1>Thank you!</h1>
<p>Your mail has been sent.</p>
<p><a href="./">Send another.</a></p>


Now try this URL! If you click on the link, you should go back to the main page.


We'll create routes to handle errors as well. After the two routes we've defined so far, add a middleware function. It's the use() method of the app object. Again, it has req and res, and also next. But we'll only need res.

app.js
app.get("/thankyou", (req, res)=> {
  res.render("thankyou");
});

app.use((req, res, next)=> {

});


app.listen(app.get("port"), ()=> {

});


We set the status to 404 here. Bear in mind that this code only executes if none of the other routes are triggered.

app.js
app.get("/thankyou", (req, res)=> {
  res.render("thankyou");
});

app.use((req, res, next)=> {
  res.status(404);
});

app.listen(app.get("port"), ()=> {

});


Then after that, we use the render() method to display the 404 page.

app.js
app.get("/thankyou", (req, res)=> {
  res.render("thankyou");
});

app.use((req, res, next)=> {
  res.status(404);
  res.render("404");
});

app.listen(app.get("port"), ()=> {

});


We will, of course, want to create that page under the views directory.

view/404.handlebars
<h1>404</h1>

<p>Not found!</p>


Now try this. Enter any string after the base URL, other than "thankyou". You should get this.


Now for the rest of the form!

We've learned quite a few concepts. But it's time to create the form so that we can use it in the next part of this tutorial. In this file, add the HTML form. It will submit to the process route (which we'll create in the next part of this web tutorial) and the method will be POST.

views/form.handlebars
<h1>Welcome to Teochew Thunder's Xmas Mailer.</h1>
<p>Email a friend!</p>

<form action="/process" method="POST">

</form>


We have here a label tag. Within it is a span tag styled using the CSS class labelText. The label text is "LAYOUT".
views/form.handlebars
<form action="/process" method="POST">
  <label>
    <span class="labelText">LAYOUT</span>
  </label>

</form>


We've got a few more labels. There are break tags in between.

views/form.handlebars
<form action="/process" method="POST">
  <label>
    <span class="labelText">LAYOUT</span>
  </label>

  <br />

  <label>
    <span class="labelText">EMAIL</span>
  </label>

  <br />

  <label>
    <span class="labelText">MESSAGE</span>
  </label>

</form>


In here, we allow the user to select between a red, green or blue layout by means of a drop-down list, id and name ddlLayout. We'll want to set the for attribute in the label for this, too.
views/form.handlebars
<label for="ddlLayout">
  <span class="labelText">LAYOUT</span>
  <select id="ddlLayout" name="ddlLayout">
    <option value="red">RED</option>
    <option value="green">GREEN</option>
    <option value="blue">BLUE</option>
  </select>

</label>


For email, I'm going to use a text input with type attribute set to "email". This will be a required field. The drop-down list we made earlier is also mandatory, but there's a default value in it, so no need to do anything more.

views/form.handlebars
<label for="txtEmail">
  <span class="labelText">EMAIL</span>
  <input id="txtEmail" name="txtEmail" type="email" placeholder="john12345@gmail.com" required />
</label>


For the message, I use a textarea tag. It is also mandatory.

views/form.handlebars
<label for="txtMessage">
  <span class="labelText">MESSAGE</span>
  <textarea id="txtMessage" name="txtMessage" required></textarea>
</label>


Let's go ahead and add a Submit button.

views/form.handlebars
<form action="/process" method="POST">
  <label for="ddlLayout">
    <span class="labelText">LAYOUT</span>
    <select id="ddlLayout" name="ddlLayout">
      <option value="red">RED</option>
      <option value="green">GREEN</option>
      <option value="blue">BLUE</option>
    </select>
  </label>

  <br />

  <label for="txtEmail">
    <span class="labelText">EMAIL</span>
    <input id="txtEmail" name="txtEmail" type="email" placeholder="john12345@gmail.com" required />
  </label>

  <br />

  <label for="txtMessage">
    <span class="labelText">MESSAGE</span>
    <textarea id="txtMessage" name="txtMessage" required></textarea>
  </label>

  <br />

  <button>Go</button>
</form>


Looks... meh. This is going to need more styling.


In this file, add the styling in the style tag. We want all forms (or actually, the one and only form) to have a height, width, and be in the middle of the page, and have a background color.

views/layouts/main.handlebars
<style>
  body {
    font-family: verdana, sans-serif;
    font-size: 14px;
    background-color: rgba(255, 200, 0, 0.5);
    color: rgba(255, 100, 0, 0.8);
  }

  h1, p {
    text-align: center;
  }

  form {
    width: 22em;
    height: 12em;
    padding: 1em;
    display: block;
    margin: 0 auto 0 auto;
    background-color: rgba(255, 200, 0, 0.5);
  }

</style>


The label tags are set to display as block-level elements, with a bit of a margin at the top, and font is boldened. The span tags that are styled using labelText. They're also rendered as block-level elements so that we can float them left, give them a margin to the right, and a width.

views/layouts/main.handlebars
<style>
  body {
    font-family: verdana, sans-serif;
    font-size: 14px;
    background-color: rgba(255, 200, 0, 0.5);
    color: rgba(255, 100, 0, 0.8);
  }

  h1, p {
    text-align: center;
  }

  form {
    width: 22em;
    height: 12em;
    padding: 1em;
    display: block;
    margin: 0 auto 0 auto;
    background-color: rgba(255, 200, 0, 0.5);
  }

  label {
    display: inline-block;
    margin-top: 0.5em;
    font-weight: bold;
  }

  .labelText {
    display: inline-block;
    width: 5em;
    margin-right: 1em;
    float: left;
  }

</style>


Now, we'll make sure all the textboxes and drop-down lists have fixed widths and heights, round corners and a border.

views/layouts/main.handlebars
<style>
  body {
    font-family: verdana, sans-serif;
    font-size: 14px;
    background-color: rgba(255, 200, 0, 0.5);
    color: rgba(255, 100, 0, 0.8);
  }

  h1, p {
    text-align: center;
  }

  form {
    width: 22em;
    height: 12em;
    padding: 1em;
    display: block;
    margin: 0 auto 0 auto;
    background-color: rgba(255, 200, 0, 0.5);
  }

  label {
    display: inline-block;
    margin-top: 0.5em;
    font-weight: bold;
  }

  .labelText {
    display: inline-block;
    width: 5em;
    margin-right: 1em;
    float: left;
  }

  input, select, textarea {
    border-radius: 5px;
    width: 15em;
    height: 1.5em;
    border: 2px solid rgb(100, 100, 100);
  }

</style>


But textarea tags will have a bigger height.

views/layouts/main.handlebars
<style>
  body {
    font-family: verdana, sans-serif;
    font-size: 14px;
    background-color: rgba(255, 200, 0, 0.5);
    color: rgba(255, 100, 0, 0.8);
  }

  h1, p {
    text-align: center;
  }

  form {
    width: 22em;
    height: 12em;
    padding: 1em;
    display: block;
    margin: 0 auto 0 auto;
    background-color: rgba(255, 200, 0, 0.5);
  }

  label {
    display: inline-block;
    margin-top: 0.5em;
    font-weight: bold;
  }

  .labelText {
    display: inline-block;
    width: 5em;
    margin-right: 1em;
    float: left;
  }

  input, select, textarea {
    border-radius: 5px;
    width: 15em;
    height: 1.5em;
    border: 2px solid rgb(100, 100, 100);
  }

  textarea {
    height: 5em;
  }

</style>


And lastly, we style the button.

views/layouts/main.handlebars
<style>
  body {
    font-family: verdana, sans-serif;
    font-size: 14px;
    background-color: rgba(255, 200, 0, 0.5);
    color: rgba(255, 100, 0, 0.8);
  }

  h1, p {
    text-align: center;
  }

  form {
    width: 22em;
    height: 12em;
    padding: 1em;
    display: block;
    margin: 0 auto 0 auto;
    background-color: rgba(255, 200, 0, 0.5);
  }

  label {
    display: inline-block;
    margin-top: 0.5em;
    font-weight: bold;
  }

  .labelText {
    display: inline-block;
    width: 5em;
    margin-right: 1em;
    float: left;
  }

  input, select, textarea {
    border-radius: 5px;
    width: 15em;
    height: 1.5em;
    border: 2px solid rgb(100, 100, 100);
  }

  textarea {
    height: 5em;
  }

  button {
    display: inline-block;
    width: 3em;
    float: right;
  }

</style>


And now it looks acceptable.


Next

Form handling and security

No comments:

Post a Comment