In this guide, we'll walk through the process of building a multi-step form using Laravel Volt, Folio, and Neon Postgres.
Laravel Volt provides reactivity for dynamic form interactions, Folio offers file-based routing for a clean project structure, and Neon Postgres serves as our scalable database solution.
Our example app will be a job application form with multiple steps, including personal information, education, and work experience.
Let's start by creating a new Laravel project and setting up the necessary components.
Create a new Laravel project:
Install Laravel Folio for file-based routing:
Install the Volt Livewire adapter for Laravel, this will also install the Livewire package:
After installing Volt, you can install the Volt service provider:
Configuring the Database Connection
Update your .env file with your Neon Postgres credentials:
Replace your-neon-hostname.neon.tech, your_database_name, your_username, and your_password with your Neon Postgres connection details.
Database Design
Let's create the database migrations for our job application form. We'll use separate tables for each section and leverage Postgres JSON columns for flexible data storage for additional information.
First, let's create the migration for the applicants table using the following artisan command:
Note that the create_applicants_table migration name follows the Laravel convention of create_{table_name}_table, where {table_name} is the name of the table you're creating. That way, Laravel can automatically determine the table name from the migration name, and also it will be easier to identify the purpose of the migration file by its name for other developers.
This command generates a new migration file in the database/migrations directory. Open the newly created file and update its content as follows:
This migration creates the applicants table with fields for first_name, last_name, and email. The email field is set as unique to prevent duplicate applications. We've also included a jsonb column called additional_info for storing any extra data that doesn't fit into the predefined columns. This flexibility is one of the advantages of using Postgres with Laravel.
Next, let's create the migration for the educations table:
Update the newly created migration file with the following content:
This migration creates the educations table. It includes a foreign key applicant_id that references the id column in the applicants table. The onDelete('cascade') ensures that if an applicant is deleted, their education records are also removed. We've included fields for the institution, degree, and start/end dates. Again, we have an additional_info jsonb column for flexibility.
Finally, let's create the migration for the work experiences table:
Update this migration file with the following content:
This migration creates the work_experiences table. Similar to the educations table, it has a foreign key relationship with the applicants table. It includes fields for the company, position, start/end dates, and responsibilities. The responsibilities field is of type text to allow for longer descriptions. We've also included an additional_info jsonb column here.
Now that we've created all our migrations, we can run them to create the tables in our database:
This command will execute all the migrations we've just created, setting up the database schema for our job application form.
One thing to note is that we've used the jsonb column type for storing additional information in each table. This allows us to store flexible data structures without needing to define a fixed schema. Postgres' JSONB data type is ideal for this use case.
For your Laravel migrations, you should not use the Neon Postgres Pooler. The Pooler is designed to manage connections for long-running processes, such as web servers, and is not necessary for short-lived processes like migrations.
Creating Models
Next, let's create models for our Applicant, Education, and WorkExperience tables. Models in Laravel are used to interact with database tables and represent the data in your application in an object-oriented way.
Laravel provides an easy way to generate models using the artisan command. To create the Applicant model run:
This command creates a new file app/Models/Applicant.php. Open this file and update it with the following content:
Now, create the Education model:
Update the newly created file at app/Models/Education.php with the following content:
Finally, create the WorkExperience model:
And update the app/Models/WorkExperience.php file with the following content:
Let's quickly note down the most important parts in these model definitions:
We've used the $fillable property to specify which attributes can be mass-assigned. This is a security feature to prevent unintended mass assignment vulnerabilities.
We've defined relationships between models. An Applicant has many Education and WorkExperience records, while Education and WorkExperience belong to an Applicant.
We've used the $casts property to automatically cast certain attributes to specific types. For example, we're casting the additional_info field to an array, which works well with Postgres' JSONB column type.
The start_date and end_date fields are cast to date objects, which allows for easy date manipulation in PHP.
These models will allow us to easily interact with our database tables using Laravel's Eloquent ORM. They provide a convenient way to retrieve, create, update, and delete records, as well as define relationships between different tables.
Creating a layout for the multi-step form
Before we create the form components, let's set up a layout for our multi-step form. We'll create a main layout file that includes the necessary CSS and JavaScript assets including the Livewire scripts.
Create a new Blade layout file at resources/views/layouts/app.blade.php:
In this layout file:
We've included the necessary meta tags for character encoding, viewport settings and the page title.
We've used the @vite directive to include the CSS and JavaScript assets. This directive is provided by the Laravel Vite package, which integrates Laravel with the Vite build tool for modern frontend development.
We've included the Livewire styles and scripts. Livewire is a full-stack framework for Laravel that allows you to build dynamic interfaces without writing JavaScript.
To compile the frontend assets, you'll need to run the following commands:
Implementing File-based Routing with Folio
Laravel Folio was introduced in 2023, and it offers a new approach to routing in Laravel applications.
It simplifies routing by allowing you to create routes simply by adding Blade templates to a specific directory. This file-based routing system makes your project structure cleaner and more intuitive.
It is not a replacement for Laravel's built-in routing system but rather a complementary feature that simplifies routing for certain types of applications.
First, let's set up the directory structure for our multi-step form. Create the following directory structure in your resources/views/pages folder:
With Folio, each of these Blade files automatically becomes a route. For example:
pages/index.blade.php will be accessible at the root URL /
pages/apply/personal-info.blade.php will be accessible at /apply/personal-info
To create a Folio page, you can use the php artisan folio:page command. For example, to create a page for the personal information step:
The above will create a blade file for the in resources/views/pages/apply/personal-info.blade.php:
You can list all available Folio routes using the following Artisan command:
You can create similar pages for the education, work experience, and review steps:
We will update these files with the form components later in the guide.
The main thing to remember here is that with Folio, you don't need to manually define routes in a separate routes file. The mere presence of a Blade file in the pages directory automatically creates a corresponding route.
Building the Multi-Step Form with Volt
Volt is a powerful addition to Laravel Livewire that allows you to build reactive components without writing JavaScript. Unlike traditional Livewire components, Volt lets you define your component's state and validation rules directly in the view file, eliminating the need for a separate component class.
Let's create Volt components for each step of our multi-step form.
Personal Information Form
First, create the personal information form component:
That will create a file at resources/views/livewire/personal-info-form.blade.php. Update the file with the following content:
Quick explanation of the code above:
We define the component's state using the state function, which initializes the form fields.
The rules function sets up validation rules for each field.
The saveAndContinue function handles form submission. It validates the form, creates a new Applicant record, stores the applicant_id in the session, and redirects to the next step.
The form fields are bound to the component's state using wire:model.
Validation errors are displayed using @error.
In the same way, you can create components for the education, work experience, and review steps.
Next, let's create the work experience form component:
Update resources/views/livewire/work-experience-form.blade.php similar to the previous components:
Review Form
Finally, create the review form component:
Update resources/views/livewire/review-form.blade.php as we did for the other components:
These Volt components handle the state management, validation, and submission logic for each step of the multi-step form. That way Volt simplifies the process of creating interactive components by allowing you to define both the logic and the template in a single file.
To use these components in your Folio pages and make the routes named, you can include them like this. Named routes allow you to easily reference routes by name throughout your application. We also need to extend a layout for each page to ensure a consistent structure.
First, in each file, you will define a named route using the name function and extend the layout.
For the resources/views/pages/apply/personal-info.blade.php file:
We need to do the same for the other pages:
For the resources/views/pages/apply/education.blade.php file:
For the resources/views/pages/apply/work-experience.blade.php file:
And for the resources/views/pages/apply/review.blade.php file:
Confirmation Page
Finally, create a confirmation page for the application submission:
Update the resources/views/pages/apply/confirmation.blade.php file:
This page displays a success message after the application is submitted and provides a link to return to the homepage.
Testing the Multi-Step Form
To manually verify that everything works as expected, follow these steps:
If you haven't already, start the Laravel development server:
Open your browser and navigate to http://localhost:8000/apply/personal-info.
Fill out the personal information form and submit it. You should be redirected to the education form.
Fill out the education form and submit it. You should be redirected to the work experience form.
Fill out the work experience form and submit it. You should be redirected to the review page.
On the review page, verify that all the information you entered is displayed correctly.
Submit the application and verify that you see a success message.
To check if the data was persisted correctly:
Open a database client (like pgAdmin for Postgres) and connect to your Neon database.
Check the applicants, educations, and work_experiences tables. You should see your submitted data.
Verify that the applicant_id in the educations and work_experiences tables matches the id in the applicants table for your submission.
Try refreshing the page or closing and reopening your browser, then navigate back to http://localhost:8000/apply/review. You should still see your submitted data, demonstrating that the data persists across sessions.
Testing
Besides manual testing, you can also write automated tests to make sure your multi-step form works correctly. Laravel provides a testing suite that allows you to write unit, feature, and browser tests.
Create feature tests for your multi-step form to ensure each step works correctly. Here's an example for the personal info step:
This test checks if:
The form can be submitted with valid data.
The data is correctly stored in the database.
The applicant_id is stored in the session.
The user is redirected to the next step after submission.
You can create similar tests for the education and work experience steps.
In this guide, we've built a multi-step form using Laravel Volt, Folio, and Neon Postgres. We've covered form validation, data storage, and routing, demonstrating how these tools can be used together to create a dynamic and interactive form.
To further improve this project, consider adding features like:
File uploads for resumes
Email notifications to applicants
An admin interface to review applications
One thing to keep in mind is always to validate and sanitize user inputs, optimize your database queries, and thoroughly test your application before deploying to production.
Join our Discord Server to ask questions or see what others are doing with Neon. Users on paid plans can open a support ticket from the console. For more details, see Getting Support.