Astro: Building Contact Form

Introduction

Two way communication is foundation to any long lasting relationship. Through a blog like this one the author is able to communicate his side, but it is equally important to listen what the reader have to say. A Feedback form or a general contact form is a good way to hear the other side.

Below I have outlined step by step approach on how you go about it.

Step 1: Enable the Page to Render on Server (Server Side Rendering - SSR)

Building this contact form in Astro is bit atypical because Astro is designed for speed and performance by prioritizing static site generation (SSG) and server-side rendering (SSR). Now if your site is content-heavy where most content is static, but you need the flexibility to render some pages on the server like the contact form you need to exempt the page from pre-rendering.

To keep the default site setting as static add output: 'hybrid' to Astro config file astro.config.mjs

import { defineConfig } from 'astro/config'
export default defineConfig({
output: 'hybrid'
})

But to allow server side rendering for contact form add the following line to the top of your contact page contact.astro

export const prerender = false

In Astro v5.0, the output: ‘hybrid’ and output: ‘static’ configurations have been merged into a single output: ‘static’ configuration, allowing for both static and server-rendered pages, with server rendering enabled by default by opting out of prerendering with export const prerender = false.

Now your page is ready for server side rendering.

Step 2: Build HTML Form to Capture User Input
Step 2.1 - Add HTML Form

Open your contact.astro page to add your standard HTML form to submit data to the server. Your frontmatter script will handle the data on the server.

<form>
<label for="fullname">Full Name</label>
<input type="text" id="fullname" name="fullname" placeholder="Your Full Name" />
<label for="email">Email Adrdess</label>
<input type="email" id="email" name="email" placeholder="Your Email Address" />
<label for="message">Message Here</label>
<textarea id="message" name="message" cols="30" rows="15"></textarea>
<input type="submit" value="Send Message" />
</form>

Step 2.2 - Add Validation Attributes

Next we will add validation attributes to provide basic client-side validation that works even if JavaScript is disabled. You can find detailed documentaion of these client-side validation attributes here. For our form we would be using the following validations:

  • required prevents form submission until the field is filled.
  • minlength sets a minimum required length for the input text.
  • type="email" also introduces validation that will only accept a valid email format.
<form>
<label for="fullname">Full Name</label>
<input type="text" id="fullname" name="fullname" placeholder="Your Full Name" required/>
<label for="email">Email Adrdess</label>
<input type="email" id="email" name="email" placeholder="Your Email Address" required/>
<label for="message">Message Here</label>
<textarea id="message" name="message" cols="30" rows="15" minlength="50" required></textarea>
<input type="submit" value="Send Message" />
</form>

Step 2.3 - Change Form Submission Method to ‘post’

The form submission will cause the browser to request the page again. Change the form’s data transfer method to POST to send the form data as part of the Request body, rather than as URL parameters.

<form method="post">
<label for="fullname">Full Name</label>
<input type="text" id="fullname" name="fullname" placeholder="Your Full Name" required/>
<label for="email">Email Adrdess</label>
<input type="email" id="email" name="email" placeholder="Your Email Address" required/>
<label for="message">Message Here</label>
<textarea id="message" name="message" cols="30" rows="15" minlength="50" required></textarea>
<input type="submit" value="Send Message" />
</form>

Step 2.4 - Access Form Data on Server Side

In the frontmatter of the page we can access the form data using Astro.request.formData(). Wrap this in a try ... catch block to handle cases when the POST request wasn’t sent by a form and the formData is invalid.

---
if (Astro.request.method === "POST") {
try {
const data = await Astro.request.formData();
const fullname = data.get("fullname");
const email = data.get("email");
const message = data.get("message");
const date = new Date();
// Do something with the data
} catch (error) {
if (error instanceof Error) {
console.error(error.message);
}
}
}
---
<form method="post">
<label for="fullname">Full Name</label>
<input type="text" id="fullname" name="fullname" placeholder="Your Full Name" required/>
<label for="email">Email Adrdess</label>
<input type="email" id="email" name="email" placeholder="Your Email Address" required/>
<label for="message">Message Here</label>
<textarea id="message" name="message" cols="30" rows="15" minlength="50" required></textarea>
<input type="submit" value="Send Message" />
</form>

Step 2.5 - Validate Form Data on Server

This should include the same validation done on the client to prevent malicious submissions to your endpoint and to support the rare legacy browser that doesn’t have form validation. It can also include validation that can’t be done on the client. For example, this example checks if the email is already in the database.

Error messages can be sent back to the client by storing them in an errors object and accessing it in the template.

---
const errors = { fullname: "", email: "", message: "" };
if (Astro.request.method === "POST") {
try {
const data = await Astro.request.formData();
const fullname = data.get("fullname");
const email = data.get("email");
const message = data.get("message");
const date = new Date();
// server side validation of the form.
if (typeof fullname !== "string" || fullname.length < 1) {
errors.fullname += "Please enter your full name. ";
}
if (typeof email !== "string" || !isValidEmail(email)) {
errors.email += "Email is not valid. ";
}
if (typeof message !== "string" || message.length < 50) {
errors.message += "Message must be at least 50 characters. ";
}
// Do something with the data
} catch (error) {
if (error instanceof Error) {
console.error(error.message);
}
}
}
---
<form method="post">
<label for="fullname">Full Name</label>
{errors.fullname && <p>{errors.fullname}</p>}
<input type="text" id="fullname" name="fullname" placeholder="Your Full Name" required/>
<label for="email">Email Adrdess</label>
{errors.email && <p>{errors.email}</p>}
<input type="email" id="email" name="email" placeholder="Your Email Address" required/>
<label for="message">Message Here</label>
{errors.message && <p>{errors.message}</p>}
<textarea id="message" name="message" cols="30" rows="15" minlength="50" required></textarea>
<input type="submit" value="Send Message" />
</form>

Thats it. We have are contact data successfully sent to and received by the server. Next we need to “Do Something with the Data”.

Step 3: Do Something with the Data.

To store data you need a database. Fortunately Astro comes with a handy way of saving your data if you do not enterprise grade asks from your data storage.