DEV Community

Cover image for Building SvelteKit forms with Superforms
Matt Angelosanto for LogRocket

Posted on • Originally published at blog.logrocket.com

Building SvelteKit forms with Superforms

Written by Chiamaka Umeh✏️

Due to the ever-evolving nature of software development, creating and managing forms remains a crucial yet often difficult task to achieve. Whether for basic contact forms or intricate data input interfaces, form validation is essential to ensure accurate and secure submissions. This step becomes even more complex as an application grows in size.

Imagine developing an app with multiple forms, each demanding unique data formats, required fields, and distinct validation rules. Quickly, the validation logic can become hard to manage. Ensuring consistency in error messages, data handling, and user experience becomes a difficult task.

Superforms, a SvelteKit library, helps with this. This library offers a comprehensive solution to the challenges posed by form validation complexity. Leveraging a Zod validation schema as the single source of truth, Superforms centralizes validation rules, ensuring consistency across the entire application. This approach eliminates code duplication and offers a solution for server and client validation.

In this article, we will learn how to set up and use Superforms in our Svelte applications. We’ll cover the following:

Features of the Superforms library

The Superforms library seamlessly validates data on both the server and client using Zod, with output that seamlessly integrates into the client. The library also offers auto-centering and auto-focusing on erroneous form fields, which enhances user-friendly interactions.

Superforms has the ability to detect tainted/dirty forms, safeguarding against data loss when users navigate away from unsaved forms. The library transcends FormData limitations for complex data structures, transparently dispatching forms as devalued JSON.

Additionally, Superforms generates default form values based on validation schemas. It supports intricate nested data structures, snapshots, and accommodates multiple forms on a single page. It works seamlessly on both server-rendered and single-page applications.

Superforms offers proxy objects for hassle-free data conversion to and from string representations. Introducing real-time, client-side validators, it provides instant user feedback for enhanced usability. Its extensive event integration provides complete control over validation data, and ActionResult, offering the option to halt updates at any stage.

Building a form with Superforms

Below is a simple unvalidated form for receiving user information. In this section, we will install Superforms and validate our user form with it:

//src/routes/+page.svelte

<script lang="ts">

</script>

  <div class='form-wrapper'>
        <form method="POST">
            <label for="name">First Name</label>
            <input
                type="text"
                name="name"
            />

            <label for="name">Last Name</label>
            <input
                type="text"
                name="name"
            />

            <label for="email">E-mail</label>
            <input
                type="email"
                name="email"
            />

            <label for="email">Employee Number</label>
            <input
                type="number"
                name="employeeNumber"
            />

            <div><button>Submit</button></div>
        </form>
  </div>

  <style>
    .form-wrapper{
            display: flex;
            justify-content: center;
    }
    button {
            background-color: #008001;
            border-radius: 5px;
            border: none;
            padding: 10px;
          }
      button:hover {
            background-color: #AAD922;
      }
  </style>
Enter fullscreen mode Exit fullscreen mode

If you are adding Superforms to an existing project, install it using either of the following commands:

pnpm i -D sveltekit-superforms zod
Enter fullscreen mode Exit fullscreen mode
npm i -D sveltekit-superforms zod
Enter fullscreen mode Exit fullscreen mode

If you are creating a new SvelteKit project, install it with this command before installing Superforms: npm create svelte@latest.

Server-side validation

This section explores how to validate a form on the server side of a SvelteKit application. The first thing we do is create a schema using Zod, which we installed earlier. The schema represents the form data:

//src/routes/+page.server.ts

import { z } from "zod"

const userSchema = z.object({
  firstName: z.string().min(1),
  lastName: z.string().min(1),
  email: z.string().email(),
  employeeNumber: z.number().min(1)
})
Enter fullscreen mode Exit fullscreen mode

Next, we’ll initialize the form in the load function using Superforms' server API, superValidate. superValidate takes in the schema we described earlier:

//src/routes/+page.server.ts

import { z } from "zod"
import { superValidate } from 'sveltekit-superforms/server';

const userSchema = z.object({
  firstName: z.string().min(1),
  lastName: z.string().min(1),
  email: z.string().email(),
  employeeNumber: z.number().min(1)
})

export const load = (async () => {
  // Server API:
  const form = await superValidate(userSchema);

  // Always return { form } in load and form actions.
  return { form };
});
Enter fullscreen mode Exit fullscreen mode

With this, we are now forwarding the validation data to the client. Our next step involves retrieving this data using the client-side API called superForm in the Svelte page with export let data;. After that, we destructure form from superForm.

See below for the code:

//src/routes/+page.svelte

<script lang="ts">
import { superForm } from "sveltekit-superforms/client"
export let data:

const { form } = superForm(data.form)

</script>
Enter fullscreen mode Exit fullscreen mode

form is a store containing the properties of our userSchema schema. We use the prefix $ before form to access the form store. Next, we bind the values of each input field to its corresponding form property like so: bind:value={$form.firstName}. We do this for all the fields.

Debugging Superforms

Before we move on to posting the form data to the server, let me quickly demonstrate a cool component provided by Superforms for dev debugging called SuperDebug. This component takes form as a prop and can be used to show what is happening with the form in real time. It also has a status code that displays error when the form validation fails, and success in the absence of any errors. It should only be used during development.

This is how to import and use SuperDebug:

import SuperDebug from "sveltekit-superforms/client/SuperDebug.svelte"
Enter fullscreen mode Exit fullscreen mode
<SuperDebug data={form}/>
Enter fullscreen mode Exit fullscreen mode

It shows the object at the top of the page so you see the changes happening to the form: Changes Happening To The Form

Posting data back to the server

To post form data back to the server, we’ll create a form action. Also, we'll continue to use the superValidate function, but this time we'll fill it with FormData, which holds the form data.

There are a few ways to do this. The first is by using the event object, which also has the request:

export const actions = {
      default: async (event) => {
            const form = await superValidate(event, userSchema);
            console.log(form);
            return { form };
      }
};
Enter fullscreen mode Exit fullscreen mode

The second way is by using the request parameter, which includes FormData:

export const actions = {
      default: async ({ request }) => {
            const form = await superValidate(request, userSchema);
            console.log(form);
            return { form };
      }
};
Enter fullscreen mode Exit fullscreen mode

Now let’s fill out and submit the form, and then inspect the console to see the form data being sent to the server from the client: Form Data Being Sent From The Server To The Client

{
  id: 'h7b6xj',
  valid: true,
  posted: true,
  errors: {},
  data: {
    firstName: 'John ',
    lastName: 'Doe',
    email: 'johndoe@gmail.com',
    employeeNumber: '09719293'
  },
  constraints: {
    firstName: { minlength: 1, required: true },
    lastName: { minlength: 1, required: true },
    email: { required: true },
    employeeNumber: { minlength: 1, required: true }
  }
}
Enter fullscreen mode Exit fullscreen mode

The code above is what you get back from superValidate. It also gives you everything you need to further customize the form:

  • id: This is used to identify the schema, which is useful when dealing with multiple forms on a page
  • valid: This informs you if the form validation was successful or not. This information is useful for both server-side and event-related actions
  • posted: This tells us whether the data was sent through the form action or not
  • data: This is an object containing the posted data. With this property, you can easily check if the user data is valid or not. If not, it can be sent back to the client using the “fail” approach
  • errors: This object contains all the validation errors maintaining the same structure as the data object
  • message: This field is set to pass a status message for further clarity
  • constraints: This is also an object that stores normal HTML validation constraints, which can be applied to input fields thereby adding another layer of validation

Now we can add logic to return an error with a code of 400:

if (!form.valid) {
    return fail(400, {
        form
    })
}
Enter fullscreen mode Exit fullscreen mode

Now if we try to skip a field in the form, we will find that the error object is no longer empty:

{
  id: 'h7b6xj',
  valid: false,
  posted: true,
  errors: {
    lastName: [ 'String must contain at least 1 character(s)' ],
    email: [ 'Invalid email' ],
    employeeNumber: [ 'String must contain at least 1 character(s)' ]
  },
  data: { firstName: 'ww', lastName: '', email: '', employeeNumber: '' },
  constraints: {
    firstName: { minlength: 1, required: true },
    lastName: { minlength: 1, required: true },
    email: { required: true },
    employeeNumber: { minlength: 1, required: true }
  }
}
Enter fullscreen mode Exit fullscreen mode

Notice this status code is no longer showing 200 but 400: The New Status Code With the updated error object, we can now import the error in the client and show the users:

const { form, errors } = superForm(data.form)
Enter fullscreen mode Exit fullscreen mode

Then, we can use it to show the appropriate error under each input like so:

{#if $errors.firstName} {$errors.firstName} {/if}
Enter fullscreen mode Exit fullscreen mode

This is the updated code for //src/routes/+page.svelte at this point:

//src/routes/+page.svelte

<script lang="ts">
    import { superForm } from "sveltekit-superforms/client"
    import SuperDebug from "sveltekit-superforms/client/SuperDebug.svelte"

    export let data;

    const { form, errors } = superForm(data.form)

</script>

<SuperDebug data={form}/>

<div class='form-wrapper'>
    <form method="POST">
        <div>
                <label for="name">First Name</label>
                <input
                        type="text"
                        name="firstName"
                        bind:value={$form.firstName}
                />
                {#if $errors.firstName}
                        <p class='error-para'>{$errors.firstName}</p>
                {/if}
        </div>

        <div>
                <label for="name">Last Name</label>
                <input
                        type="text"
                        name="lastName"
                        bind:value={$form.lastName}
                />
                {#if $errors.lastName}
                        <p class='error-para'>{$errors.lastName}</p>
                {/if}
        </div>

        <div>
                <label for="email">E-mail</label>
                <input
                        type="email"
                        name="email"
                        bind:value={$form.email}
                />
                {#if $errors.email}
                        <p class='error-para'>{$errors.email}</p>
                {/if}
        </div>

        <div>
                <label for="email">Employee Number</label>
                <input
                        type="number"
                        name="employeeNumber"
                        bind:value={$form.employeeNumber}
                />
                {#if $errors.employeeNumber}
                        <p class='error-para'>{$errors.employeeNumber}</p>
                {/if}
        </div>
        <div><button>Submit</button></div>
    </form>
</div>

<style>
    .form-wrapper{
            display: flex;
            justify-content: center;
    }
    button {
        background-color: #008001;
        border-radius: 5px;
        border: none;
        padding: 10px;
    }
    button:hover {
        background-color: #AAD922;
    }
    .error-para{
        color: red;
        font-size: 12px;
    }
</style>
Enter fullscreen mode Exit fullscreen mode

Errors In Our Form

Using the use:enhance feature

When we use the "enhance" feature from superForm, it brings enhanced client-side interactivity to forms in a SvelteKit application. When you use the enhance feature, various client-side features become available, such as events, timers, and auto-error focus. These enhancements enhance the user experience by providing real-time feedback and smoother interactions.

Notably, the use:enhance action doesn't take any arguments. Instead, events are employed to interact with the default SvelteKit use:enhance parameters and other functionalities. For a deeper understanding of enhance and how events work in this context, check out the official Superforms documentation.

It's essential to acknowledge that without using use:enhance, the form remains static, lacking the enhanced client-side behavior. Only the constraints and resetForm functionalities will operate in this scenario.

Client-side validation

In this section, we'll explore how you can utilize either a Zod schema or a Superforms validation object to achieve thorough real-time validation directly on the client side.

Constraints

To make use of the existing browser constraints, simply import the $constraints store from superForm and use it in the appropriate fields. Import it with the following line of code:

const {form, errors, enhance, constraints} = superForm(data.form)
Enter fullscreen mode Exit fullscreen mode

And then apply them to each input like this:

{...$constraints.firstName}
Enter fullscreen mode Exit fullscreen mode

This is the result below: Applying Constraints To Our Form The browser's built-in validation might feel a bit limited. For instance, it might be challenging to manage where and how error messages appear. Instead, we have the option to configure certain settings for personalized real-time validation with Superforms. This is how to do that:

const { form, enhance, constraints, validate } = superForm(data.form, {
  validators: AnyZodObject | {
    field: (value) => string | string[] | null | undefined;
  },
  validationMethod: 'auto' | 'oninput' | 'onblur' | 'submit-only' = 'auto',
  defaultValidator: 'keep' | 'clear' = 'keep',
  customValidity: boolean = false
})
Enter fullscreen mode Exit fullscreen mode

The most convenient and advisable way to perform a client-side validation is by configuring the “validators” option with the Zod schema we use on the server side. But we could also use a Superforms validation object. The object corresponds to the form’s keys and has a function that takes the field value and input. The function returns a string or an array of strings when there is an error with the fields and returns null or undefined if the fields are valid.

Below is an example of client-side validation with both a Superforms validation object and the previously defined userSchema Zod schema:

//src/routes/+page.svelte

const userSchema = z.object({
          firstName: z.string().min(1),
      lastName: z.string().min(1),
      email: z.string().email(),
      employeeNumber: z.string().min(1)
});
// validate the form with our previously defined userSchema zod schema:
const { form, errors, enhance } = superForm(data.form, {
  validators: userSchema;
});

// validate the length of the firstName field with Superforms validation object:

const { form, errors, enhance, constraints } = superForm(data.form, {
    validators: {
      firstName: (firstName) => firstName.length < 3 ? 'Name must be at least 3 characters' : null,
    }
});

Enter fullscreen mode Exit fullscreen mode

The image below demonstrates client validation with our previously defined userSchema Zod schema: Client Validation With Our Previously Defined UserSchema Zod Schema The image below demonstrates client validation using the Superforms validation object: Client Validation With Our Previously Defined UserSchema Zod Schema As you can see, any one of the choices works. There are many more customizations that can be done on the client side to validate forms.

Conclusion

Superforms simplifies form validation in SvelteKit applications, streamlining complexity, enhancing user experience, and delivering powerful client-side interactions. Throughout this article, we've seen and demonstrated how Superforms helps to make the form validation process a lot simpler, thereby making it an important tool for developers. The code used in the tutorial can be found here.


Get set up with LogRocket's modern error tracking in minutes:

  1. Visit https://logrocket.com/signup/ to get an app ID.
  2. Install LogRocket via NPM or script tag. LogRocket.init() must be called client-side, not server-side.

NPM:

$ npm i --save logrocket 

// Code:

import LogRocket from 'logrocket'; 
LogRocket.init('app/id');
Enter fullscreen mode Exit fullscreen mode

Script Tag:

Add to your HTML:

<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
<script>window.LogRocket && window.LogRocket.init('app/id');</script>
Enter fullscreen mode Exit fullscreen mode

3.(Optional) Install plugins for deeper integrations with your stack:

  • Redux middleware
  • ngrx middleware
  • Vuex plugin

Get started now

Top comments (1)

Collapse
 
marcitpro profile image
Marcitpro

Intresting post