In modern web development, creating interactive and user-friendly forms is crucial for engaging and retaining users. SvelteKit, with its efficient and component-based architecture, offers an excellent foundation. When combined with SuperForm for form handling, Zod for schema validation, Svelte-Sonner for notifications, and Nodemailer for email integration, it becomes a powerhouse for creating sophisticated web applications. This blog post will guide you through integrating these technologies to enhance user experience in your SvelteKit applications.
Prerequisites
Install the necessary packages:
npm i nodemailer nodemailer-brevo-transport svelte-sonner sveltekit-superforms zod
Creating the Form Component
In your Svelte-Kit
project, navigate to the src/routes
directory and create a new file named +page.svelte
. This file will contain our form component.
+page.svelte
file
At the top of your +page.svelte
file, import
the required modules and set up the form with sveltekit-superforms
and Zod
for validation:
We will also import svelte-sonner
for showing the toast
message about user action
<script>
import { Toaster, toast } from 'svelte-sonner';
import { z } from 'zod';
import { superForm } from 'sveltekit-superforms/client';
import { opinionSchema } from '../../lib/formFunctions/FormSchema';
export let data;
const {
form,
enhance,
message,
capture,
restore,
errors,
constraints,
delayed,
timeout,
submitting
} = superForm(data?.form, {
resetForm: false,
onSubmit() {
toast('Your form is submitting...');
}
});
</script>
<Toaster richColors closeButton />
HTML Markup
Below the script tag, define the HTML structure for your form, utilizing Svelte's reactivity to handle form submission and display messages:
<form action="?/create" method="POST" use:enhance>
<div class="flex flex-col space-y-3">
<input
class="input input-primary"
placeholder="Type your name"
name="name_title"
type="text"
bind:value={$form.name_title}
{...$constraints.name_title}
/>
<div class="text-red-500">
{#if $errors?.name_title}
{$errors.name_title}
{/if}
</div>
<input
class="input input-primary"
placeholder="Type your email"
name="email"
type="email"
bind:value={$form.email}
{...$constraints.email}
/>
<div class="text-red-500">
{#if $errors?.email}
{$errors.email}
{/if}
</div>
<textarea
class="textarea textarea-primary"
placeholder="Type your message"
name="detail"
type="text"
bind:value={$form.detail}
{...$constraints.detail}
/>
<div class="text-red-500">
{#if $errors?.detail}
{$errors.detail}
{/if}
</div>
</div>
<button
disabled={$delayed}
class="btn {$delayed ? 'animate-pulse text-white ring' : 'btn-primary'} mt-5 w-full"
>
{$delayed ? 'Sending Mail...' : 'Submit'}
</button>
</form>
Handling Form Submission on the Server
Now, let's set up the server-side handling of the form submission. Create a +page.server.js file in the same directory as your form component:
The Blueprint
import { superValidate, message } from 'sveltekit-superforms/server';
import { opinionSchema } from '../../lib/formFunctions/FormSchema';
import { mailPost } from '../../lib/mail/sendMail.js';
export const actions = {
create: async ({ request }) => {
// Form validation and email sending logic goes here
}
};
The complete code
import { superValidate, message } from 'sveltekit-superforms/server';
import { opinionSchema } from '../../lib/formFunctions/FormSchema';
import { mailPost } from '../../lib/mail/sendMail.js';
export const load = async (event) => {
const form = await superValidate(event, opinionSchema);
return {
form
};
};
export const actions = {
create: async ({ request }) => {
const formData = await superValidate(request, opinionSchema);
// await new Promise((resolve) => setTimeout(resolve, 5000));
console.log('POST HERE', formData.data);
// Extracting information from form
const user_name = formData.data.name_title;
const user_email = formData.data.email;
const user_detail = formData.data.detail;
console.log('user_name, user_email, user_detail: ', user_name, user_email, user_detail);
if (!formData.valid) {
// Again, return { form } and things will just work.
return message(formData, {
text: 'Something is wrong with your request, please correct and try again',
status: 400
});
} else {
const response = await mailPost({
body: {
user_name,
user_email,
user_detail
}
});
// Check if response is successful
if (response.status === 200) {
return message(formData, {
text: 'Your opinion has been received. Thank you!',
status: 200
});
} else {
console.log('response: ', response);
return message(formData, { text: response.body, status: response.status });
}
}
}
};
Setting Up Email with Nodemailer
Navigate to the lib/mail directory and create a sendMail.js file. Here, set up Nodemailer with your preferred transport (in this case, nodemailer-brevo-transport) and define the mailPost function to send an email with the form data:
The Blueprint
import nodemailer from 'nodemailer';
import brevoTransport from 'nodemailer-brevo-transport';
const transporter = nodemailer.createTransport(new brevoTransport({
// Transport configuration
}));
export async function mailPost(request) {
// Email sending logic goes here
}
Make sure to handle errors gracefully and send a response back to the client indicating the success or failure of the email operation.
The complete code
import nodemailer from 'nodemailer';
import brevoTransport from 'nodemailer-brevo-transport';
// IMPORT ENV VARIABLE
let brevoAPI = import.meta.env.VITE_BREVO_API;
// console.log(brevoAPI);
const transporter = nodemailer.createTransport(
new brevoTransport({
apiKey: brevoAPI
})
);
export async function mailPost(request) {
console.log('from sendmail js: ', request);
const { user_name, user_email, user_detail } = request.body;
const mailOptions = {
from: user_email,
to: '[email protected]',
subject: `New message from (${user_name})`,
html: user_detail,
text: user_detail
};
try {
await transporter.sendMail(mailOptions);
return {
status: 200,
body: 'Email sent successfully'
};
} catch (error) {
console.error(error);
return {
status: 500,
body: 'Failed to send email, Please try again'
};
}
}