Lesson 5: Contact Form

1 Create the HTML form

Up until now we’ve only did GET requests: when we load a page, we ask the server to send us a page and its assets.

In order for a user to send information to the server, we need a POST form.

You need a <form> element with 2 attributes:

  • the method post
  • the action /contact

We’re going to use 3 types of form elements:

  • the text input
  • the select dropdown
  • the multiline textarea
<form class="form" method="post" action="/contact">
  <div class="field">
    <label class="label">Name</label>
    <input class="input" type="text" name="name" value="{{formBody.name}}">
  </div>

  <div class="field">
    <label class="label">Email</label>
    <input class="input" type="email" name="email" value="{{formBody.email}}">
  </div>

  <div class="field">
    <label class="label">Subject</label>
    <div class="select">
      <select name="subject">
        <option value="press">Press inquiry</option>
        <option value="work">Work opportunity</option>
        <option value="hi">Just to say hi!</option>
      </select>
    </div>
  </div>

  <div class="field">
    <label class="label">Message</label>
    <textarea class="textarea" name="message">{{formBody.message}}</textarea>
  </div>

  <div class="field no-label">
    <button class="button">Send</button>
  </div>
</form>
Copy to clipboardcontact.handlebars

2 Add body parser

In order to make the form data parsable, we need this for when we submit forms.

It will help us extract the info we want.

var bodyParser = require('body-parser');

app.use(bodyParser.urlencoded({ extended: false }));
Copy to clipboardapp.js

3 Handle the form submission

When we submit the form by clicking “Send”, we’re sending a POST request to the server.

In the request object req we can find all the information sent. The req.body will contact all the values based on the name attribute of each form element.

Because we have <textarea name="message"> we can retrive its value with req.body.message.

For now, we are only going to create a formBody object and log it, and pass the value back to the template, so that we don’t lose the already submitted data.

// Handle the contact form submission
app.post('/contact', function(req, res) {
  var formBody = {
    'name': req.body.name,
    'email': req.body.email,
    'subject': req.body.subject,
    'message': req.body.message,
  };

  console.log(formBody);

  res.render('contact', {
    formBody,
  });
});
Copy to clipboardapp.js

4 Check if the fields are empty

As we don’t want to receive an email if the name, email address, or message is empty, let’s check the values of each.

If any of them is empty, we are going to display an error message, explaining:

  • why there was an error
  • how to fix that error

We’re going to pass more data to the template:

  • an error boolean
  • a message string
  • a boolean for each missing field

Make sure to return the response, so that we don’t render the template twice.

var missingName = (formBody.name === '');
var missingEmail = (formBody.email === '');
var missingMessage = (formBody.message === '');

if (missingName || missingEmail || missingMessage) {
  return res.render('contact', {
    error: true,
    message: 'Some fields are missing.',
    formBody: formBody,
    missingName: missingName,
    missingEmail: missingEmail,
    missingMessage: missingMessage,
  });
}
Copy to clipboardapp.js

5 Display the errors in the template

The {{#if}}...{{/if}} block allows to display HTML only if the boolean is true.

We add a error block which displays the message, only if error is true.

After each field, we show a missing block, only if that field is empty.

{{#if error}}
  <p class="error">{{message}}</p>
{{/if}}

<form>
  <!-- Name field -->

  {{#if missingName}}
    <p class="missing">The name is required.</p>
  {{/if}}

  <!-- Email field -->

  {{#if missingEmail}}
    <p class="missing">The email is required.</p>
  {{/if}}

  <!-- Textarea -->

  {{#if missingMessage}}
    <p class="missing">The message is required.</p>
  {{/if}}

  <!-- Send button -->
</form>
Copy to clipboardcontact.handlebars

6 Handle the success if all fields are present

If all fields are present, we want to display a success message.

We can also empty the fields by not passing back the formBody object.

res.render('contact', {
  success: true,
  message: 'Your message has been successfully sent!',
});
Copy to clipboardapp.js

7 Show a success message

The same way we handle the error message, let’s add a success message.

{{#if success}}
  <p class="success">{{message}}</p>
{{/if}}
Copy to clipboardcontact.handlebars

8 Send an email with mailgun

By signing up to mailgun.com, you can send emails directly from your code.

var mailgun = require('mailgun-js')({ apiKey: '', domain: '' });
Copy to clipboardapp.js

9 Send an email

When all the fields are present, we need to create an emailOptions object with all the required data:

  • the sender from
  • the recipient to
  • the subject
  • the content of the email

As sending the email is not guaranteed, we need to handle that error case as well. We need to make sure to pass the formBody data, so that the user doesn’t lose the fields they’ve already filled.

// Email options
var emailOptions = {
  from: formBody.name + ' <' + formBody.email + '>',
  to: '[email protected]',
  subject: 'Website contact form - ' + formBody.subject,
  text: formBody.message,
};

// Try send the email
mailgun.messages().send(emailOptions, function (error, response) {
  if (error) {
    res.render('contact', {
      error: true,
      message: 'The message was not sent. Please try again.',
      formBody: formBody,
    });
  } else {
    res.render('contact', {
      success: true,
      message: 'Your message has been successfully sent!',
    });
  }
});
Copy to clipboardapp.js