Intro to SvelteKit: Form Actions

Daniel Boadzie
CodeX
Published in
9 min readJan 25, 2023

In my previous article, we discussed an introduction to SvelteKit, the meta framework for Svelte with support for server-rendering, code-splitting, and other features that make it a powerful front-end development tool. We also discussed how SvelteKit makes it easy to create components, handle routing, and integrate with external APIs. In this article, we will explore how to work with forms using SvelteKit, including creating forms, handling form data, validating form input, and submitting forms. We will also discuss best practices for working with forms in SvelteKit and how to troubleshoot any issues that arise.

Setup

Before we begin working with forms, let’s use pnpm to create a new SvelteKit project:

pnpm create svelte@latest sveltekit-forms

The code above will prompt you to choose from a few options and generate a basic SvelteKit project based on your selection. The generated project should look something like this:

Once you have selected the options, you will need to install the dependencies for the project, which can be done by running the following command from the project’s directory:

# cd sveltekit-forms
pnpm install

Incorporating Tailwind CSS for styling

In addition, we can also install Tailwindcss, Daisyui and the Tailwindcss typography plugin to enhance the styling and design capabilities of our project, because why not. Fortunately, there is a useful tool called “svelte-add” that is specifically designed for this purpose.

npx svelte-add@latest tailwindcss --daisyui --typography

To install these new dependencies, you will need to use the following command:

pnpm install

Now open the project in your preferred editor; mine is vscode.

How to create a form action in SvelteKit

Forms are an essential part of any web application or website. Form actions are the mechanisms used to handle the data input from the user. SvelteKit is an open-source framework that provides users with a highly customizable and straightforward way to create forms, and the form actions associated with them. In this article, we’ll explore what form actions are and how SvelteKit makes them easier to create, utilize, and manage. We’ll also discuss some of the best practices for using form actions in SvelteKit and review examples of SvelteKit form actions in action.

Let’s start by creating a simple form on our homepage that will capture a user’s name, email, and message. This form will be used for a contact form.

<section class="container mx-auto px-4 py-4">
<div class="flex flex-col justify-center items-center min-h-[40rem]">
<h1 class="text-4xl text-slate-500">Sveltekit Forms</h1>

<div class="w-3/6 mt-4 rounded-sm p-8 bg-slate-100 ">
<form class="my-4 " action="?/contactus" method= "POST">
<div>
<label class="label text-slate-500 mr-4" for="name">Name </label>
<input type="text" name="name" placeholder="Type here" class="ring-1 ring-slate-200 focus:outline-none rounded-sm input w-full" />
</div>
<div>
<label class="label text-slate-500 mr-4" for="name">Email </label>
<input type="email" name="email" placeholder="Type here" class="ring-1 ring-slate-200 focus:outline-none rounded-sm input w-full" />
</div>
<div>
<label class="label text-slate-500 mr-4" for="name">Message </label>
<textarea class="textarea ring-1 ring-slate-200 focus:outline-none rounded-sm w-full" placeholder="Message"></textarea>
</div>
</form>
</div>
</div>
</section>

In the code above, we used TailwindCSS and DaisyUI to create a contact page with a form to collect user data. It is important to note that all form elements have a name attribute with values that will help us identify the form elements. This will be especially useful if we need to process the user's inputs on the server. We also added a button that will submit the user's input when clicked and make the method POST. Sveltekit forms are configured to support only POST actions, so they cannot be used for any other type of action.

Now that we have our form, Let’s send the form data to our backend using SvelteKit form actions. SvelteKit provides a convenient way to capture user inputs from forms. We need to create a +page.server.js file that exports an async function called actions. This function has a named action that will use the request object to get the form data. The data is then stored in a formData object which we can destructure to get the values or use the get() method on the data object. We can then use the values however we want. For instance, send it into a database or manipulate it in some way. We can also send the formData to the page component.

NB: It is important to note that sensitive user information should not be sent to the front-end

export const actions = {
contactus: async ({ request }) => {
const formData = Object.fromEntries(await request.formData());
// const name = res.get('name');
// const email = res.get('email');
// const message = res.get('message');
// console.log(formdata);
return {
success: true,
formData
};
}
};

If no name is specified for the action, SvelteKit will automatically set it as the default action, which will be invoked when the user presses the submit button.

Displaying the form data

Let’s display the user’s data in our component. To do that, add a script to the component and export the form object. This object contains the form data, which we will use in our page only if the user hits the submit button.

<script>
export let form;
</script>

....

{#if form?.success }
<div class="p-4 bg-slate-100 w-3/6 mt-4">
<p>Name: {form?.formData.name}</p>
<p>Message: {form?.formData.message}</p>
</div>
{/if}
.....

Your app should now look like this when you submit after adding some values:

Form Validation

Although our form functions properly, it lacks proper validation. Currently, the form can be submitted without any input, which can lead to the submission of empty forms to the server. This is not acceptable and requires attention.

Form validation is the process of ensuring that the data entered into a form is valid and correct. It is typically used in web development to ensure data is entered correctly into web forms before it is submitted to the server. Form validation can be done in the browser, on the server, or both. It typically involves checking the form fields for correct data types, valid ranges, required fields, and other criteria. It can also involve checking for valid formatting such as email addresses, phone numbers, and URLs. Form validation also helps to protect against malicious input such as SQL injections.

In this example, we will implement server-side validation to ensure that the data received from the client is correct before it is processed by the server. We will also provide clear and appropriate error feedback to the client. To achieve this, we will utilize the Zod library for form validation. To get started, we first need to install Zod by running the following command:

pnpm add zod

To ensure proper validation of our form, we will integrate Zod validation. This can be achieved by creating a Zod object and specifying the required validation rules for each field within the form. Then, we will modify the form's action to include this Zod validation.

import { z } from 'zod';

const contactSchema = z.object({
name: z
.string({ required_error: 'Name is required' })
.min(2)
.max(64, { message: 'Name must be less than 64 characters' })
.trim(),
email: z
.string({ required_error: 'Email is required' })
.trim()
.max(64, { message: 'Name must be less than 64 characters' })
.email({ message: 'Email must be a valid email address' }),
message: z
.string({ required_error: 'Message is required' })
.min(5, { message: 'Message must be at least 5 characters' })
.trim()
});

Zod offers a wide range of field-specific validation options, including validation for strings, numbers, and more. The library’s official documentation provides in-depth information on the various validation options available and how to use them effectively.

Now let’s add the validation schema we created to our form data:

export const actions = {
contactUs: async ({ request }) => {
const formData = Object.fromEntries(await request.formData());
// const name = formdata.name;
// const email = formdata.email;
// const message = formdata.message;
try {
const result = contactSchema.parse(formData);
console.log('success!');
console.log(result);
} catch (err) {
const { fieldErrors: errors } = err.flatten();
const { name, email, message } = formData;
return {
name,
email,
message,
errors
};
}
}
};

The code above uses the contactSchema.parse(formdata) to parse the form data by using a predefined contactSchema object which is expected to have validation rules for the form fields.

If the parsing is successful, it will print “success!” and the data in the console. But if it fails, the catch block will execute and the error object will be flattened with the help of err.flatten() and the field errors will be extracted using fieldErrors: errors

The function then returns an object with keys formdata values, and errors which can be used by the client-side to handle the validation errors.

To display client-side validation errors, we will need to incorporate them into the form elements within the +page.svelte file.

...
<div>
<label class="label text-slate-500 mr-4" for="name">Name </label>
<input type="text" name="name" placeholder="Type here" class="ring-1 ring-slate-200 focus:outline-none rounded-sm input w-full" />
{#if form?.errors?.name }
<span class="py-2 px-1 text-red-400">{form?.errors?.name[0]}</span>
{/if}
</div>
...

If you run the app now, you will see the following if you submit the form with erorrs:

To enhance the user experience, it would be beneficial to retain the user’s input values in the form fields upon encountering validation errors, rather than requiring them to re-enter all of their information. This can be achieved by adding a value attribute with a condition that checks for and returns the user's input value for each form field. An example will look like this:

...
<input type="text" name="name" placeholder="Type here" class="ring-1 ring-slate-200 focus:outline-none rounded-sm input w-full" value={form?.name ?? ""} />
...

The value attribute is used to define the value of the input field, this attribute takes an expression that is evaluated to return the value of the input field. In this case, it's using the ?? operator to check if the form.name exists and if it does, it returns that value, otherwise it returns an empty string.

The ?? is nullish coalescing operator, it returns the value of the left hand side operand if it's not null or undefined, otherwise it returns the right hand side operand. It is used here to avoid accessing a property of an undefined object and throw an error.

Now our form interaction should look like this:

Progressive Enhancement

The form created in this article can be submitted even with JavaScript disabled. However, to enhance the user experience for those who have modern browsers with JavaScript support, we can make the form submission process more efficient and smooth. This can be achieved by adding progressive enhancement to handle the form submission, providing instant feedback to the user, and reducing the need for a page refresh.

Thankfully, SvelteKit offers a convenient use:enhance action that allows for easy progressive enhancement without the need for additional configuration. While more advanced progressive enhancement techniques, such as custom submit functions, can also be implemented, in this article, we will use the simple use:enhance action to enhance the form submission process. This will ensure that users with modern browsers will have a smoother and more efficient form submission experience without the need for a full page refresh.

To implement the use:enhance action on the form, include the following code in the +page.svelte component:

<script>
import {enhance} from "$app/forms"
...
</script>

<form class="my-4 " action="?/contactUs" method= "POST" use:enhance>
...

By doing this, when the form is submitted, the page will not reload, providing a smoother and more efficient experience for the user.

Conclusion

In conclusion, SvelteKit offers a convenient and efficient solution for handling forms and their actions. By utilizing the built-in request object and libraries like Zod, we can easily access and validate form data on the server-side. By incorporating progressive enhancement, retaining user input values, and displaying clear validation errors, we can enhance the user experience and improve the overall form submission process. This article provided a basic introduction to implementing form actions in SvelteKit, and with the knowledge gained, we can now confidently create and implement forms in our SvelteKit applications.

The code for this article can be found here.

--

--

Daniel Boadzie
CodeX
Writer for

Data scientist | AI Engineer |Software Engineering|Trainer|Svelte Entusiast. Find out more about my me here https://www.linkedin.com/in/boadzie/