Contact form
Vue Bootstrap 5 Contact form component
A typical subscription form used when subscribing to a newsletter, asking a question in a FAQ section, or leaving a testimonial / review for a product.
To learn more read Docs.
Prerequisites
Before starting the project make sure to install the following utilities:
- Node LTS environment (12.x.x recommended)
- Express- setting the server
- Express-validator - for validating the form
- Nodemailer - for sending mails
- Multer - for handling multiparts request (reading FormData)
- Dotenv - for loading environment variables
- Cors - for enabling CORS (Cross-origin resource sharing)
Within this tutorial we are using the Material Design for Bootstrap library, you can download it for free from here. Without the library, the form will still work — however it may look and behave differently. It's recommended to use this library along with the tutorial.
Form HTML
First, create basic contact form which will be our base for validating and sending data.
Copy and paste the following HTML and JavaScript code into your file (e.g. index.html
):
<template>
<section class="border p-4 d-flex justify-content-center mb-4">
<form id="form" class="text-center" style="width: 100%; max-width: 300px">
<h2>Contact us</h2>
<!-- Name input -->
<MDBInput label="Name" v-model="name" wrapperClass="mb-4" />
<!-- Email input -->
<MDBInput type="email" label="Email address" v-model="email" wrapperClass="mb-4" />
<!-- Subject input -->
<MDBInput label="Subject" v-model="subject" wrapperClass="mb-4" />
<!-- Message input -->
<MDBTextarea wrapperClass="mb-4" label="Message" v-model="message" />
<!-- Mail copy -->
<MDBCheckbox label="Send me copy" v-model="copy" />
<!-- Submit button -->
<MDBBtn color="primary" block class="my-4">Send</MDBBtn>
</form>
</section>
</template>
<script>
import {
MDBInput,
MDBTextarea,
MDBBtn,
MDBCheckbox
} from "mdb-vue-ui-kit";
import {
ref
} from "vue";
export default {
components: {
MDBInput,
MDBTextarea,
MDBBtn,
MDBCheckbox
},
setup() {
const name = ref("");
const email = ref("");
const subject = ref("");
const message = ref("");
const copy = ref(false);
return {
name,
email,
subject,
message,
copy,
};
},
}
</script>
Frontend validation
Our form now works fine. However currently if the user makes a mistake by clicking send without filling in the form first, this will result in sending an empty email. The other potential problem is that user might make a mistake in his email address so he would never get a response from us.
MDB Validation
MDBootstrap form components provide built-in validation which can be used to validate user data before sending email via PHP script. Read more about MDBootstrap form validation here.
In the example below all inputs were marked as required, and JavaScript code enabling custom MDB Form Validation was added.
<template>
<section class="border p-4 d-flex justify-content-center mb-4">
<form class="text-center needs-validation" style="width: 100%; max-width: 300px" novalidate
@submit.prevent="checkForm">
<h2>Contact us</h2>
<!-- Name input -->
<MDBInput label="Name" v-model="nameMDBValidation" wrapperClass="mb-4" required
invalidFeedback="Please provide your name." />
<!-- Email input -->
<MDBInput type="email" label="Email address" v-model="emailMDBValidation" wrapperClass="mb-4" required
invalidFeedback="Please provide your email." />
<!-- Subject input -->
<MDBInput label="Subject" v-model="subjectMDBValidation" wrapperClass="mb-4" required
invalidFeedback="Please provide mail subject." />
<!-- Message input -->
<MDBTextarea wrapperClass="mb-4" label="Message" v-model="messageMDBValidation" required
invalidFeedback="Please provide a message text." />
<!-- Mail copy -->
<MDBCheckbox label="Send me copy" v-model="copyMDBValidation" />
<!-- Submit button -->
<MDBBtn color="primary" block class="my-4" type="submit">Send</MDBBtn>
</form>
</section>
</template>
<script>
import {
MDBInput,
MDBTextarea,
MDBBtn,
} from "mdb-vue-ui-kit";
import {
ref
} from "vue";
export default {
components: {
MDBInput,
MDBTextarea,
MDBBtn,
},
setup() {
const nameMDBValidation = ref("");
const emailMDBValidation = ref("");
const subjectMDBValidation = ref("");
const messageMDBValidation = ref("");
const copyMDBValidation = ref(false);
const checkForm = (e) => {
e.target.classList.add("was-validated");
};
return {
nameMDBValidation,
emailMDBValidation,
subjectMDBValidation,
messageMDBValidation,
copyMDBValidation,
checkForm
};
},
}
</script>
Custom Validation
If you don't want to use MDB default validation and want to create your own functionality instead, you have to change the behavior of the form submission. So first change the existing code in our JavaScript.
<template>
<form class="text-center needs-validation" style="width: 100%; max-width: 300px" novalidate>
{ ... }
<MDBBtn color="primary" type="submit">Send</MDBBtn>
</form>
</template>
with the following code, which instead of directly submitting the form will call our validation function:
<template>
<section class="border p-4 d-flex justify-content-center mb-4">
<form id="form" class="text-center" style="width: 100%; max-width: 300px" @submit.prevent="validateForm">
<h2>Contact us</h2>
<!-- Name input -->
<MDBInput label="Name" v-model="nameCustomValidation" wrapperClass="mb-4" />
<!-- Email input -->
<MDBInput type="email" label="Email address" v-model="emailCustomValidation" wrapperClass="mb-4" />
<!-- Subject input -->
<MDBInput label="Subject" v-model="subjectCustomValidation" wrapperClass="mb-4" />
<!-- Message input -->
<MDBTextarea wrapperClass="mb-4" label="Message" v-model="messageCustomValidation" />
<!-- Mail copy -->
<MDBCheckbox label="Send me copy" v-model="copyCustomValidation" />
<!-- Submit button -->
<MDBBtn color="primary" block class="my-4" type="submit">Send</MDBBtn>
<!-- Status message -->
<div ref="statusRef"></div>
</form>
</section>
</template>
<script>
import {
MDBInput,
MDBTextarea,
MDBBtn,
} from "mdb-vue-ui-kit";
import {
ref
} from "vue";
export default {
components: {
MDBInput,
MDBTextarea,
MDBBtn,
},
setup() {
const nameCustomValidation = ref("");
const emailCustomValidation = ref("");
const subjectCustomValidation = ref("");
const messageCustomValidation = ref("");
const copyCustomValidation = ref(false);
const statusRef = ref(null);
const checkValidation = () => {
let isDataValid = true;
let statusMessage = "";
if (nameCustomValidation.value === "") {
statusMessage += '<p class="note note-danger"><strong>Name</strong> cannot be empty</p>';
isDataValid = false;
}
if (emailCustomValidation.value === "") {
statusMessage += '<p class="note note-danger"><strong>Email</strong> cannot be empty</p>';
isDataValid = false;
} else {
const re =
/^(([^<>()[]\.,;:s@"]+(.[^<p>()[]\.,;:s@"]+)*)|(".+"))@(([[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}])|(([a-zA-Z-0-9]+.)+[a-zA-Z]{2,}))$/;
if (!re.test(emailCustomValidation.value)) {
statusMessage += '<p class="note note-danger"><strong>Email</strong> is invalid</p>'
';
isDataValid = false;
}
}
if (subjectCustomValidation.value === "") {
statusMessage +=
'<p class="note note-danger"><strong>Subject</strong> cannot be empty</p>';
isDataValid = false;
}
if (messageCustomValidation.value === "") {
statusMessage +=
'<p class="note note-danger"><strong>Message</strong> cannot be empty</p>';
isDataValid = false;
}
return {
isDataValid,
statusMessage
};
};
const validateForm = (e) => {
const {
isDataValid,
statusMessage
} = checkValidation();
if (!isDataValid) {
statusRef.value.innerHTML = statusMessage;
return;
}
statusRef.value.innerHTML = '<p class="note note-success">Sending...</p>'
';
e.target.submit();
};
return {
nameCustomValidation,
emailCustomValidation,
subjectCustomValidation,
messageCustomValidation,
statusRef,
validateForm
};
},
}
</script>
Create NodeJS server file
Create a new file called server.js
inside your application's root directory.
If you haven't installed the express, nodemailer, body-parser and other necessary packages from the npm repository so far, do so immediately using the following command in your application terminal:
// terminal
npm install express nodemailer express-validator multer dotenv cors
Having all set up, let's create our server. Paste following code into your file:
// JS
const express = require("express");
const multer = require("multer");
const cors = require("cors");
const app = express();
const upload = multer();
const port = process.env.PORT || 3000;
app.use(cors());
app.use(upload.none());
app.listen(port, () => {
console.log('Express started on port: ', port);
});
This will import all necessary dependencies, initialize application as express instance, allows Cross-Origin Resource Sharing (CORS) for sending information between frontend and backend development servers and finally start the server. const upload = multer() and app.use(upload.none()) creates middlewarae for handling multiparts requests, which will allow to send contact form data using FormData object.
In this example we use app.use(upload.none())
to show how
multipart
request can be handled (with file upload turned off), however it is not recommended to use it global
scope.
In your production app use e.g. upload.any()
for specific endpoints in which you expect to
handle multipart requests.
Create file structure for mail sending server
Having the server set up you are ready to create a file structure for mail sending route and middleware. For the purposes of our course, it is not necessary, but in case you want to expand the functionality of our example, it is worth knowing what the file structure could looks like in applications using the NodeJS environment and Express server.
Create and read environment variables file
Firstly, create .env
file inside your root directory. This file will
contain all your private data used inside your NodeJS project. It's content for our purposes would
look like this:
// JS
NODE_ENV=development
PORT=3000
MAIL_USERNAME=your_smtp_email_address
MAIL_PASSWORD=your_smtp_email_password
MAIL_HOST=your_smtp_host
MAIL_PORT=your_smtp_port
MAIL_TO=email_address_to_which_you_want_mails_to_be_sent
Example .env file for Gmail SMTP
// JS
NODE_ENV=development
PORT=3000
MAIL_USERNAME=your_email_address_here
MAIL_PASSWORD=your_email_password
MAIL_HOST=smtp.gmail.com
MAIL_PORT=465
MAIL_TO=email_address_to_which_you_want_mails_to_be_sent
Please note that we are using SMTP for Gmail here. If you would like to use another
provider your HOST, PORT and PROVIDER
would be different. Also if you would like to send test mails in development mode you may want to be
interested in fake SMTP servers, e.g. Mailtrap.
Also for production purposes it is recommended to use one of many SMTP providers.
Never publish your environment variable files. Include
all your .env
files inside .gitignore
file when you publish your code.
Now add /config/index.
file that will read all your environment variables.
// JS
require('dotenv').config();
module.exports = {
PORT: process.env.PORT || 3000,
NODE_ENV,
MAIL_HOST: process.env.MAIL_HOST,
MAIL_PORT: process.env.MAIL_PORT,
MAIL_USERNAME: process.env.MAIL_USERNAME,
MAIL_PASSWORD: process.env.MAIL_PASSWORD,
MAIL_TO: process.env.MAIL_TO
};
... and change your server.js
file so the config can expose your variables
to the server:
// JS
const express = require("express");
const multer = require("multer");
const cors = require("cors");
const config = require("./config");
const routes = require("./routes");
const upload = multer();
const app = express();
app.use(cors());
app.use(upload.none());
app.use("/api", routes);
app.listen(config.PORT, () => {
console.log(`Server is running on port ${config.PORT}.`);
});
Also notice that we added app.use("/api", routes);
which will use our
router from the next section.
Create router and contact form routes
Create /routes
folder and inside it create two files. First index.js that
will control all your project routes:
// JS
const router = require("express").Router({ mergeParams: true });
const contactRoutes = require("./contact.routes");
router.use("/contact", contactRoutes);
module.exports = router;
... and contact.routes.js
file that will contain all your contact form
related routes.
// JS
// express server specific imports
const router = require("express").Router({ mergeParams: true });
// import for contact form routes controller
const { ContactController } = require("../controllers/contact.controller");
router.post("/", (req, res) => {
const controller = new ContactController(req, res);
controller.sendContactForm();
});
module.exports = router;
Thanks to express.Router({ mergeParams: true })
we can access the params
from the parent router inside the child router.
In our case creating router.post('/', ...)
is equal to router.post('/contact',
...)
. This structure is very helpful in deep nested routes structure.
... and contact.routes.js
file that will contain all your contact form
related routes.
Contact controller
Controllers are callbacks passed to the router methods which process request to the route and generate the response.
Create /controllers
folder inside which you will create
contact.controllers.js
file. Controller for /contact
route will look as
follows:
// JS
const nodemailer = require("nodemailer");
const { MAIL_HOST, MAIL_PORT, MAIL_PROVIDER, MAIL_USERNAME, MAIL_PASSWORD, MAIL_TO } =
require('../config');
const transporter = nodemailer.createTransport({
host: MAIL_HOST,
port: MAIL_PORT,
auth: {
user: MAIL_USERNAME,
pass: MAIL_PASSWORD,
},
});
class ContactController {
constructor(request, response) {
this.request = request;
this.response = response;
}
sendContactForm() {
const { name, email, subject, message, mailCopy } = this.request.body;
const mailOptions = {
to: [MAIL_TO], // Enter here the email address on which you want to send emails from your
customers
from: name,
subject,
text: message,
};
if (mailCopy) {
mailOptions.to.push(email);
}
transporter
.sendMail(mailOptions)
.then(() => {
return this.response
.status(200)
.json({ msg: "Email sent successfully" });
})
.catch((error) => {
return this.response
.status(400)
.json({ errors: [{ msg: `Something went wrong: ${error}` }] });
});
}
}
module.exports = ContactController;
Controller is based on a class instance for better scalability of the application. You could also use function based controller as well.
Line 5 - const transporter = nodemailer.createTransport({ })
is used to
create a function that takes the object containing the configuration of the transporter as a
parameter.
It is in this configuration that you define the host from which emails are sent port, authorization,
and many other settings that are to be sent.
Line 14 - create class instance of the controller that, in this example will take request and response from the server as its constructor arguments.
Line 20 - declares sendContactForm
method for handling POST
method on /contact
route.
Lines 22-33 - defines the mailOptions
object that is sent to the sendMail()
method of transporter.
It is in this object that we define, to who the email is to be sent, from who, what is its subject,
text, and to who the response in the email
is to be addressed. In this object are many more options that you can use. You will find them here.
Lines 35-48 - Code responsible for sending the mail with the parameters defined inside
mailOptions
. Will send either success or failure response message to the frontend.
Create validating middleware
Middlewares are functions that have access to the server request and response objects. In our example we will create middleware function that will validate contact form data.
Create /middleware
folder and inside it create validate.js
file.
// JS
const { body, validationResult } = require("express-validator");
module.exports = [
body("name").notEmpty().withMessage("You must supply a name"),
body("email").isEmail().normalizeEmail().withMessage("Email is not valid"),
body("subject").notEmpty().withMessage("You must supply a subject"),
body("message").notEmpty().withMessage("You must supply a message"),
body("mailCopy").toBoolean(),
function (req, res, next) {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
next();
},
];
We are using here express-validator
package that will help us validate
request.body
and if something is wrong provide server response with proper error message.
If everything in the request is ok, next()
function that will allow server to call next
function (controller) is called.
Now update contact.routes.js
file with our validator. Note that middleware
function is added between the route and controller callback.
// JS
// express server specific imports
const router = require("express").Router({ mergeParams: true });
// import validation middleware
const validate = require("../middleware/validate");
// import for contact form routes controller
const { ContactController } = require("../controllers/contact.controller");
// add validator to the route callback
router.post("/", validate, (req, res) => {
const controller = new ContactController(req, res);
controller.sendContactForm();
});
module.exports = router;
Run server
In your package.json
file add script that will run your server:
// package.json
"start": "node server.js",
"start:dev" : "nodemon server.js"
You can also add start:dev
script for development purposes that will
restart your server anytime you make a change to the code. For that add nodemon
package
to your application.
// Terminal
npm install nodemon --save-dev
Connect frontend form with backend server
To connect frontend contact form with backend server we will use AJAX.
<script>
import {
MDBInput,
MDBTextarea,
MDBBtn,
} from "mdb-vue-ui-kit";
import {
ref
} from "vue";
export default {
components: {
MDBInput,
MDBTextarea,
MDBBtn,
},
setup() {
const nameCustomValidation = ref("");
const emailCustomValidation = ref("");
const subjectCustomValidation = ref("");
const messageCustomValidation = ref("");
const copyCustomValidation = ref(false);
const statusRef = ref(null);
const checkValidation = () => {
let isDataValid = true;
let statusMessage = "";
if (nameCustomValidation.value === "") {
statusMessage +=
'<p class="note note-danger"><strong>Name</strong> cannot be empty</p>';
isDataValid = false;
}
if (emailCustomValidation.value === "") {
statusMessage +=
'<p class="note note-danger"><strong>Email</strong> cannot be empty</p>';
isDataValid = false;
} else {
const re =
/^(([^<>()[]\.,;:s@"]+(.[^<p>()[]\.,;:s@"]+)*)|(".+"))@(([[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}])|(([a-zA-Z-0-9]+.)+[a-zA-Z]{2,}))$/;
if (!re.test(emailCustomValidation.value)) {
statusMessage += '<p class="note note-danger"><strong>Email</strong> is invalid</p>'
';
isDataValid = false;
}
}
if (subjectCustomValidation.value === "") {
statusMessage +=
'<p class="note note-danger"><strong>Subject</strong> cannot be empty</p>';
isDataValid = false;
}
if (messageCustomValidation.value === "") {
statusMessage +=
'<p class="note note-danger"><strong>Message</strong> cannot be empty</p>';
isDataValid = false;
}
return {
isDataValid,
statusMessage
};
};
const validateForm = (e) => {
const formData = new FormData(e.target);
const {
isDataValid,
statusMessage
} = validateForm();
if (!isDataValid) {
statusRef.value.innerHTML = statusMessage;
return;
}
fetch('/api/contact', {
method: 'POST',
body: formData,
mode: 'cors'
})
.then((response) => {
response.json();
})
then((response) => {
// handle errors
if (response.errors) {
response.errors.forEach(({
msg
}) => {
statusRef.value.innerHTML += `<p class="note note-danger">${msg}</p>`
});
return;
}
// If mail was sent successfully, reset all elements with attribute 'name'
nameCustomValidation.value = ''
emailCustomValidation.value = ''
subjectCustomValidation.value = ''
messageCustomValidation.value = ''
copyCustomValidation.valule = false
statusRef.value.innerHTML = `<p class="note note-success">${response.msg}</p>`;
})
.catch((err) => {
statusRef.value.innerHTML += `<p class="note note-danger">${err}</p>`
})
.finally(() => {
setTimeout(() => {
statusRef.value.innerHTML = '';
}, 2000)
})
};
return {
nameCustomValidation,
emailCustomValidation,
subjectCustomValidation,
messageCustomValidation,
statusRef,
validateForm
};
},
}
</script>
Now it's time for you to run your backend server and frontend application and start sending your contact forms.
Information provided above are just an example of implementation of sending contact forms using NodeJS server. If you have any questions, please do not hesitate to post a question on our support forum.