{"id":11884,"date":"2023-08-03T12:22:56","date_gmt":"2023-08-03T12:22:56","guid":{"rendered":"https:\/\/codevoweb.com\/?p=11884"},"modified":"2023-08-03T15:52:55","modified_gmt":"2023-08-03T15:52:55","slug":"fullstack-sveltekit-application-with-form-actions","status":"publish","type":"post","link":"https:\/\/codevoweb.com\/fullstack-sveltekit-application-with-form-actions\/","title":{"rendered":"Building a FullStack SvelteKit Application with Form Actions"},"content":{"rendered":"\n<p>In this article, you will learn how to build a full-stack feedback application in SvelteKit using <a href=\"https:\/\/kit.svelte.dev\/docs\/form-actions\" target=\"_blank\" rel=\"noreferrer noopener\">Form Actions<\/a> and <a href=\"https:\/\/www.prisma.io\/\" target=\"_blank\" rel=\"noreferrer noopener\">Prisma ORM<\/a>. Using Form Actions will enable us to directly mutate data in the database from the frontend application, which means we do not have to go through an API layer.<\/p>\n\n\n\n<p>We will progressively enhance the forms to ensure that they function even when JavaScript is disabled in the browser or unavailable. We have already created this same application in SvelteKit in the article titled &#8216;<a href=\"\/build-a-frontend-app-in-sveltekit-using-a-restful-api\/\">Build a Frontend App in SvelteKit using a RESTful API<\/a>&#8216;, but this one requires an API layer to modify data on the server. <\/p>\n\n\n\n<p>Moreover, if you&#8217;re interested, there are Next.js and Rust versions of this feedback application available for exploration. The Rust implementation is found in the article titled &#8216;<a href=\"\/build-full-stack-app-with-rust-yew-and-actix-web\/\">Build a Full Stack App with Rust, Yew.rs, and Actix Web<\/a>&#8216;, while the Next.js version can be found in the article titled &#8216;<a href=\"\/build-a-simple-frontend-app-in-nextjs13-app-directory\/\">Build a Simple Frontend App in Next.js 13 App Directory<\/a>&#8216;.<\/p>\n\n\n\n<p>Before we delve into the tutorial, let&#8217;s give credit to Brad Traversy, who originally created this <a href=\"https:\/\/youtu.be\/3TVy6GdtNuQ?t=319\" target=\"_blank\" rel=\"noreferrer noopener\">application in Svelte<\/a> with local storage for feedback item storage. However, in our implementation, we&#8217;ll elevate it to a higher level by incorporating a PostgreSQL database and Prisma ORM. Without further delay, let&#8217;s dive into the tutorial and start building this exciting application!<\/p>\n\n\n\n<span id=\"ezoic-pub-video-placeholder-107\"><\/span>\n\n\n\n<p>More Practice (Optional):<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"\/how-to-build-a-simple-api-in-sveltekit\/\">How to Build a Simple API in SvelteKit<\/a><\/li>\n\n\n\n<li><a href=\"\/build-a-frontend-app-in-sveltekit-using-a-restful-api\/\">Build a Frontend App in SvelteKit using a RESTful API<\/a><\/li>\n\n\n\n<li><a href=\"\/learn-nextjs-server-actions-and-mutations-with-examples\/\">Learn Next.js Server Actions and Mutations with Examples<\/a><\/li>\n\n\n\n<li><a href=\"\/integrate-fastapi-framework-with-nextjs-and-deploy\/\">Integrate FastAPI Framework with Next.js and Deploy<\/a><\/li>\n\n\n\n<li><a href=\"\/how-to-integrate-flask-framework-with-nextjs\/\">Integrate Python Flask Framework with Next.js and Deploy<\/a><\/li>\n\n\n\n<li><a href=\"\/jwt-authentication-in-nextjs-13-api-route-handlers\/\">JWT Authentication in Next.js 13 API Route Handlers<\/a><\/li>\n\n\n\n<li><a href=\"\/user-registration-login-in-nextjs-13-app-directory\/\">User Registration and Login in Next.js 13 App Directory<\/a><\/li>\n\n\n\n<li><a href=\"\/build-a-simple-api-in-next-js-13-app-directory\/\">Build a Simple API in Next.js 13 App Directory<\/a><\/li>\n\n\n\n<li><a href=\"\/build-a-simple-frontend-app-in-nextjs13-app-directory\/\">Build a Simple Frontend App in Next.js 13 App Directory<\/a><\/li>\n\n\n\n<li><a href=\"\/nextjs-add-google-and-github-oauth2-using-nextauth-js\/\">Next.js &#8211; Add Google and GitHub OAuth2 using NextAuth.js<\/a><\/li>\n\n\n\n<li><a href=\"\/nextjs-use-custom-login-and-signup-pages-for-nextauth-js\/\">Next.js &#8211; Use Custom Login and SignUp Pages for NextAuth.js<\/a><\/li>\n\n\n\n<li><a href=\"\/how-to-setup-prisma-orm-in-nextjs-13-app-directory\/\">How to Setup Prisma ORM in Next.js 13 App Directory<\/a><\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"850\" height=\"478\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/08\/Building-a-FullStack-SvelteKit-Application-with-Form-Actions.webp\" alt=\"Building a FullStack SvelteKit Application with Form Actions\" class=\"wp-image-11917\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/08\/Building-a-FullStack-SvelteKit-Application-with-Form-Actions.webp 850w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/08\/Building-a-FullStack-SvelteKit-Application-with-Form-Actions-300x169.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/08\/Building-a-FullStack-SvelteKit-Application-with-Form-Actions-768x432.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/08\/Building-a-FullStack-SvelteKit-Application-with-Form-Actions-100x56.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/08\/Building-a-FullStack-SvelteKit-Application-with-Form-Actions-700x394.webp 700w\" sizes=\"auto, (max-width: 850px) 100vw, 850px\" \/><\/figure>\n\n\n<style>.kb-table-of-content-nav.kb-table-of-content-id11884_e4611c-fd .kb-table-of-content-wrap{padding-top:var(--global-kb-spacing-sm, 1.5rem);padding-right:var(--global-kb-spacing-sm, 1.5rem);padding-bottom:var(--global-kb-spacing-sm, 1.5rem);padding-left:var(--global-kb-spacing-sm, 1.5rem);border-top:1px solid #abb8c3;border-right:1px solid #abb8c3;border-bottom:1px solid #abb8c3;border-left:1px solid #abb8c3;}.kb-table-of-content-nav.kb-table-of-content-id11884_e4611c-fd .kb-table-of-contents-title-wrap{padding-top:0px;padding-right:0px;padding-bottom:0px;padding-left:0px;}.kb-table-of-content-nav.kb-table-of-content-id11884_e4611c-fd .kb-table-of-contents-title-wrap{color:#ffffff;}.kb-table-of-content-nav.kb-table-of-content-id11884_e4611c-fd .kb-table-of-contents-title{color:#ffffff;font-weight:regular;font-style:normal;}.kb-table-of-content-nav.kb-table-of-content-id11884_e4611c-fd .kb-table-of-content-wrap .kb-table-of-content-list{color:#ffffff;font-weight:regular;font-style:normal;margin-top:var(--global-kb-spacing-sm, 1.5rem);margin-right:0px;margin-bottom:0px;margin-left:0px;}@media all and (max-width: 1024px){.kb-table-of-content-nav.kb-table-of-content-id11884_e4611c-fd .kb-table-of-content-wrap{border-top:1px solid #abb8c3;border-right:1px solid #abb8c3;border-bottom:1px solid #abb8c3;border-left:1px solid #abb8c3;}}@media all and (max-width: 767px){.kb-table-of-content-nav.kb-table-of-content-id11884_e4611c-fd .kb-table-of-content-wrap{border-top:1px solid #abb8c3;border-right:1px solid #abb8c3;border-bottom:1px solid #abb8c3;border-left:1px solid #abb8c3;}}<\/style>\n\n\n<h2 class=\"wp-block-heading\">Running the FullStack Feedback App<\/h2>\n\n\n\n<p>Here&#8217;s a preview of the feedback application we&#8217;ll create in this tutorial. At the top, there&#8217;s a form with a label, radio buttons to rate feedback, and a text box for adding comments.<\/p>\n\n\n\n<p>Below the form, you&#8217;ll see statistics showing the total number of reviews and the average ratings.<\/p>\n\n\n\n<p>After that, there&#8217;s a list of cards displaying the feedback items. Each card has Edit and Delete buttons. If you click Edit, an input field appears to edit the feedback text. When you click Delete, you&#8217;ll be asked to confirm before removing the feedback from both the database and the user interface.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"938\" height=\"1024\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/08\/SvelteKit-Feedback-Application-using-Form-Actions-in-Desktop-View-938x1024.webp\" alt=\"SvelteKit Feedback Application using Form Actions in Desktop View\" class=\"wp-image-11912\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/08\/SvelteKit-Feedback-Application-using-Form-Actions-in-Desktop-View-938x1024.webp 938w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/08\/SvelteKit-Feedback-Application-using-Form-Actions-in-Desktop-View-275x300.webp 275w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/08\/SvelteKit-Feedback-Application-using-Form-Actions-in-Desktop-View-768x839.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/08\/SvelteKit-Feedback-Application-using-Form-Actions-in-Desktop-View-92x100.webp 92w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/08\/SvelteKit-Feedback-Application-using-Form-Actions-in-Desktop-View-412x450.webp 412w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/08\/SvelteKit-Feedback-Application-using-Form-Actions-in-Desktop-View.webp 989w\" sizes=\"auto, (max-width: 938px) 100vw, 938px\" \/><\/figure>\n\n\n\n<p>Below is a preview of the application on a mobile device, demonstrating its full responsiveness and seamless adaptability to all screen sizes.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"469\" height=\"916\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/08\/SvelteKit-Feedback-Application-using-Form-Actions-in-Mobile-View.webp\" alt=\"SvelteKit Feedback Application using Form Actions in Mobile View\" class=\"wp-image-11913\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/08\/SvelteKit-Feedback-Application-using-Form-Actions-in-Mobile-View.webp 469w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/08\/SvelteKit-Feedback-Application-using-Form-Actions-in-Mobile-View-154x300.webp 154w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/08\/SvelteKit-Feedback-Application-using-Form-Actions-in-Mobile-View-51x100.webp 51w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/08\/SvelteKit-Feedback-Application-using-Form-Actions-in-Mobile-View-230x450.webp 230w\" sizes=\"auto, (max-width: 469px) 100vw, 469px\" \/><\/figure>\n<\/div>\n\n\n<p>To run the SvelteKit feedback app on your local machine, follow these steps:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Download and Set Up:<\/strong>\n<ol class=\"wp-block-list\">\n<li>Download or clone the project from its GitHub repository at <a href=\"https:\/\/github.com\/wpcodevo\/sveltekit-form-actions-simple-app\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/github.com\/wpcodevo\/sveltekit-form-actions-simple-app<\/a>.<\/li>\n\n\n\n<li>Open the source code in your preferred IDE or code editor.<\/li>\n<\/ol>\n<\/li>\n\n\n\n<li><strong>Install Dependencies:<\/strong>\n<ol class=\"wp-block-list\">\n<li>In your terminal, run <code>pnpm install<\/code> to install all the required dependencies. If you don&#8217;t have the PNPM package manager installed, you can install it by running <code>npm i -g pnpm<\/code>.<\/li>\n<\/ol>\n<\/li>\n\n\n\n<li><strong>Database Configuration:<\/strong>\n<ol class=\"wp-block-list\">\n<li>Duplicate the <code>.env.example<\/code> file and rename the copy to <code>.env<\/code>.<\/li>\n\n\n\n<li>Inside the <code>.env<\/code> file, add your cloud Postgres database URL to the <code>DATABASE_URL<\/code> variable. You can obtain a cloud PostgreSQL URL from <a href=\"https:\/\/supabase.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">Supabase<\/a>.<\/li>\n<\/ol>\n<\/li>\n\n\n\n<li><strong>Optional: Use Docker for PostgreSQL:<\/strong>\n<ol class=\"wp-block-list\">\n<li>If you don&#8217;t have a cloud PostgreSQL database, you can use Docker to set up a local PostgreSQL server in a Docker container. Ensure you have Docker installed on your machine.<\/li>\n\n\n\n<li>Run <code>docker-compose up -d<\/code> in the terminal to start the PostgreSQL server.<\/li>\n<\/ol>\n<\/li>\n\n\n\n<li><strong>Start the Development Server:<\/strong>\n<ol class=\"wp-block-list\">\n<li>Run the command <code>pnpm dev --port 3000<\/code> in the terminal. This will generate the Prisma Client, apply migrations to the database, and start the SvelteKit development server.<\/li>\n<\/ol>\n<\/li>\n\n\n\n<li><strong>Access the Application:<\/strong>\n<ol class=\"wp-block-list\">\n<li>Visit the provided URL in your web browser to access the application.<\/li>\n\n\n\n<li>Feel free to explore and interact with the application&#8217;s features, such as adding, editing, and deleting feedback items.<\/li>\n\n\n\n<li>If you wish to test the API using an API client, you can import the <code>Feedback App API.postman_collection.json<\/code> file, which is provided in the source code, into tools like Postman or the Thunder Client VS Code extension. Once imported, you can utilize the pre-defined requests to interact with the SvelteKit API, allowing you to add, edit, retrieve, and delete feedback items.<\/li>\n<\/ol>\n<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Setting up the SvelteKit Project<\/h2>\n\n\n\n<p>To begin building our SvelteKit application, let&#8217;s create a new project. If you already have one, feel free to skip this step. Navigate to the folder where you want to keep the source code and use one of the commands below, based on your preferred package manager. For example, I&#8217;ll use PNPM.<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\">\n<code>\n# For PNPM\npnpm create svelte@latest sveltekit-form-actions-simple-app\n\n# For Yarn\nyarn create svelte@latest sveltekit-form-actions-simple-app\n\n# For NPM\nnpm create svelte@latest sveltekit-form-actions-simple-app\n<\/code>\n<\/pre>\n\n\n\n<p>After running the command, you&#8217;ll be prompted to choose a template. Select the &#8220;<strong>skeleton project<\/strong>&#8221; option, and then choose &#8220;<strong>Yes<\/strong>&#8221; for TypeScript type checking. Optionally, you can include ESLint and Prettier by selecting them with the spacebar. Finally, press the Enter key to generate the project.<\/p>\n\n\n\n<p>After creating the project, navigate to its folder and run <code>pnpm install<\/code> to install all the necessary dependencies. Once the installation is complete, you can open the project in your preferred IDE.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Setting up the Database<\/h2>\n\n\n\n<p>To set up a cloud Postgres database, I&#8217;ll walk you through obtaining the connection URL from Supabase. However, you can choose any other provider if you prefer. Follow these simple steps:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Sign up at <a href=\"https:\/\/supabase.com\/dashboard\/sign-up\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/supabase.com\/dashboard\/sign-up<\/a> using your GitHub account.<\/li>\n\n\n\n<li>Create a new project. Once the project is set up, click on the settings icon located in the left sidebar.<\/li>\n\n\n\n<li>In the settings page, select &#8220;<strong>Database<\/strong>&#8221; and scroll down to the &#8220;<strong>Connection string<\/strong>&#8221; section. Under this, choose the &#8220;<strong>URI<\/strong>&#8221; tab and copy the provided URI.<\/li>\n\n\n\n<li>Create a <code>.env<\/code> file at the root level of your project and paste the copied connection string as the value for the <code>DATABASE_URL<\/code> variable. Remember to replace the <code>[YOUR-PASSWORD]<\/code> placeholder with your actual Postgres database password, which you received when creating the new project on Supabase.<br><img loading=\"lazy\" decoding=\"async\" width=\"850\" height=\"828\" class=\"wp-image-11843\" style=\"width: 850px;\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/07\/Copy-the-PostgreSQL-Connection-String-from-Supabase.webp\" alt=\"Copy the PostgreSQL Connection String from Supabase\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/07\/Copy-the-PostgreSQL-Connection-String-from-Supabase.webp 1005w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/07\/Copy-the-PostgreSQL-Connection-String-from-Supabase-300x292.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/07\/Copy-the-PostgreSQL-Connection-String-from-Supabase-768x748.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/07\/Copy-the-PostgreSQL-Connection-String-from-Supabase-100x97.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/07\/Copy-the-PostgreSQL-Connection-String-from-Supabase-462x450.webp 462w\" sizes=\"auto, (max-width: 850px) 100vw, 850px\" \/><\/li>\n<\/ol>\n\n\n\n<p><strong>.env<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-shell\">\n<code>\n# -----------------------------------------------------------------------------\n# Database (PostgreSQL - Supabase)\n# -----------------------------------------------------------------------------\n\nDATABASE_URL=paste_cloud_postgres_uri_here\n<\/code>\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Optional: Using Docker<\/h3>\n\n\n\n<p>To set up a Postgres server and pgAdmin in Docker, begin by creating a <code>docker-compose.yml<\/code> file at the root level of your project. Then, add the following configurations to the file:<\/p>\n\n\n\n<p><strong>docker-compose.yml<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-js\">\n<code>\nservices:\n  postgres:\n    image: postgres\n    container_name: postgres\n    ports:\n      - 6500:5432\n    env_file:\n      - .\/.env\n    volumes:\n      - postgres:\/var\/lib\/postgresql\/data\n  pgadmin:\n    image: dpage\/pgadmin4\n    container_name: pgadmin\n    ports:\n      - \"5050:80\"\n    env_file:\n      - .\/.env\n    volumes:\n      - pgadmin-data:\/var\/lib\/pgadmin\n    restart: always\nvolumes:\n  postgres:\n  pgadmin-data:\n<\/code>\n<\/pre>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>Please note that pgAdmin has been included to provide a visual interface for inspecting the data stored in the PostgreSQL server. This will enable us to conveniently view and manage the database contents.<\/p>\n<\/blockquote>\n\n\n\n<p>For the configuration, we used a <code>.env<\/code> file in the <code>env_file<\/code> field. This file will hold the necessary credentials to build the PostgreSQL and pgAdmin images. To ensure these credentials are accessible, create a <code>.env<\/code> file at the project&#8217;s root directory and include the following environment variables:<\/p>\n\n\n\n<p><strong>.env<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-js\">\n<code>\n# -----------------------------------------------------------------------------\n# PostgreSQL Credentials for Docker Compose\n# -----------------------------------------------------------------------------\nPOSTGRES_HOST=127.0.0.1\nPOSTGRES_USER=admin\nPOSTGRES_PASSWORD=password123\nPOSTGRES_DB=sveltekit_feedback_app\nPOSTGRES_PORT=6500\n\n# -----------------------------------------------------------------------------\n# pgAdmin4 Credentials for Docker Compose\n# -----------------------------------------------------------------------------\nPGADMIN_DEFAULT_EMAIL=admin@admin.com\nPGADMIN_DEFAULT_PASSWORD=password123\nPGADMIN_LISTEN_PORT=80\n\n\n# -----------------------------------------------------------------------------\n# Database (PostgreSQL - Docker)\n# -----------------------------------------------------------------------------\nDATABASE_URL=postgresql:\/\/admin:password123@localhost:6500\/sveltekit_feedback_app?schema=public\n<\/code>\n<\/pre>\n\n\n\n<p>With the environment variables set, run the command <code>docker-compose up -d<\/code> to start both the PostgreSQL and pgAdmin servers in their respective containers.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Initializing Prisma and Running Database Migrations<\/h2>\n\n\n\n<p>With the PostgreSQL database now operational, let&#8217;s proceed to integrate Prisma ORM into the project. Start by installing the Prisma CLI and Client using the following command:<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\">\n<code>\n# For PNPM\npnpm add @prisma\/client && pnpm add -D prisma\n\n# For Yarn\nyarn add @prisma\/client && yarn add -D prisma\n\n# For NPM\nnpm i @prisma\/client && npm i -D prisma\n<\/code>\n<\/pre>\n\n\n\n<p>Once the packages are installed, initialize Prisma in the project using the command below:<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\">\n<code>\n# For PNPM\npnpm prisma init --datasource-provider postgresql\n\n# For Yarn\nyarn prisma init --datasource-provider postgresql\n\n# For NPM\nnpx prisma init --datasource-provider postgresql\n<\/code>\n<\/pre>\n\n\n\n<p>Next, access the <code>prisma\/schema.prisma<\/code> file and replace its existing content with the following code snippet. This snippet defines the Feedback model along with its fields, representing the structure of a feedback item:<\/p>\n\n\n\n<p><strong>prisma\/schema.prisma<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-js\">\n<code>\n\/\/ This is your Prisma schema file,\n\/\/ learn more about it in the docs: https:\/\/pris.ly\/d\/prisma-schema\n\ngenerator client {\n  provider = \"prisma-client-js\"\n}\n\ndatasource db {\n  provider = \"postgres\"\n  url      = env(\"DATABASE_URL\")\n}\n\nmodel Feedback {\n  id        String   @id @default(uuid())\n  text      String   @unique\n  rating    Float\n  createdAt DateTime @default(now())\n  updatedAt DateTime @updatedAt\n}\n<\/code>\n<\/pre>\n\n\n\n<p>After making the necessary changes to the schema, execute the following command to establish a connection with the active PostgreSQL database, generate schema migrations based on the Feedback model, synchronize the migrations with the Postgres database schema, and generate the Prisma Client within the <code>node_modules<\/code> folder:<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\">\n<code>\n# For PNPM\npnpm prisma migrate dev --name init\n\n# For Yarn\nyarn prisma migrate dev --name init\n\n# For NPM\nnpx prisma migrate dev --name init\n<\/code>\n<\/pre>\n\n\n\n<p>To view the tables added to the PostgreSQL database, access the pgAdmin GUI by opening your web browser and navigating to <code>http:\/\/localhost:5050\/<\/code>. Ensure that your Docker container is up and running.<\/p>\n\n\n\n<p>Then, log in to pgAdmin using the credentials specified in the <code>.env<\/code> file. Once logged in, on the pgAdmin dashboard, create a new server using the PostgreSQL credentials from the <code>.env<\/code> file. Use the default Postgres port (<strong>5432<\/strong>) instead of the one provided in the <code>.env<\/code> file.<\/p>\n\n\n\n<p>To obtain the IP of the running Postgres server, you can use the following steps:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Run the command <code>docker ps<\/code> to list all running Docker containers.<\/li>\n\n\n\n<li>Find the container ID of the PostgreSQL container from the output.<\/li>\n\n\n\n<li>Run the command <code>docker inspect &lt;container_id&gt;<\/code>, replacing <code>&lt;container_id&gt;<\/code> with the actual container ID from the previous step.<\/li>\n\n\n\n<li>In the output, scroll down to the &#8220;<strong>NetworkSettings<\/strong>&#8221; section and copy the value of the &#8220;<strong>IPAddress<\/strong>&#8221; field.<br><img loading=\"lazy\" decoding=\"async\" width=\"705\" height=\"578\" class=\"wp-image-11670\" style=\"width: 705px;\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/06\/Flask-App-Add-a-New-Postgres-Server-on-pgAdmin.webp\" alt=\"Flask App Add a New Postgres Server on pgAdmin\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/06\/Flask-App-Add-a-New-Postgres-Server-on-pgAdmin.webp 705w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/06\/Flask-App-Add-a-New-Postgres-Server-on-pgAdmin-300x246.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/06\/Flask-App-Add-a-New-Postgres-Server-on-pgAdmin-100x82.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/06\/Flask-App-Add-a-New-Postgres-Server-on-pgAdmin-549x450.webp 549w\" sizes=\"auto, (max-width: 705px) 100vw, 705px\" \/><\/li>\n<\/ol>\n\n\n\n<p>After successfully registering the Postgres server in pgAdmin, go to the <strong>sveltekit_feedback_app<\/strong> -&gt; <strong>Schemas<\/strong> -&gt; <strong>Tables<\/strong> section. Next, right-click on the Feedback table and select &#8220;<strong>Properties<\/strong>&#8221; to view its details. In the displayed popup, click on the &#8220;<strong>Columns<\/strong>&#8221; tab to explore the table&#8217;s columns and their properties.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"855\" height=\"571\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/07\/Open-pgAdmin-and-Sign-In-To-See-the-Feedback-Table-Added-By-Prisma-ORM.webp\" alt=\"Open pgAdmin and Sign In To See the Feedback Table Added By Prisma ORM\" class=\"wp-image-11846\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/07\/Open-pgAdmin-and-Sign-In-To-See-the-Feedback-Table-Added-By-Prisma-ORM.webp 855w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/07\/Open-pgAdmin-and-Sign-In-To-See-the-Feedback-Table-Added-By-Prisma-ORM-300x200.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/07\/Open-pgAdmin-and-Sign-In-To-See-the-Feedback-Table-Added-By-Prisma-ORM-768x513.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/07\/Open-pgAdmin-and-Sign-In-To-See-the-Feedback-Table-Added-By-Prisma-ORM-100x67.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/07\/Open-pgAdmin-and-Sign-In-To-See-the-Feedback-Table-Added-By-Prisma-ORM-674x450.webp 674w\" sizes=\"auto, (max-width: 855px) 100vw, 855px\" \/><\/figure>\n\n\n\n<p>If you are using Supabase, you can get the connection info from the &#8220;<strong>Database Settings<\/strong>&#8221; page provided by Supabase.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Instantiating the Prisma Client<\/h2>\n\n\n\n<p>Now that the Prisma migrations are synchronized with the database schema, we can leverage the Prisma Client to interact with the database. However, before proceeding, there&#8217;s an essential step to consider. When using SvelteKit&#8217;s development server, it performs hot reloads whenever we make changes to the source code. To prevent creating a new Prisma Client instance with each hot reload, which could lead to additional database connections, we should store the Prisma Client as a global variable during development.<\/p>\n\n\n\n<p>To begin, open your <code>src\/app.d.ts<\/code> file and add a new variable named <code>prisma<\/code> with the type <code>PrismaClient<\/code>. Here&#8217;s how it should look:<\/p>\n\n\n\n<p><strong>src\/app.d.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\">\n<code>\n\/\/ See https:\/\/kit.svelte.dev\/docs\/types#app\n\nimport type { PrismaClient } from '@prisma\/client';\n\n\/\/ for information about these interfaces\ndeclare global {\n\tnamespace App {\n\t\t\/\/ interface Error {}\n\t\t\/\/ interface Locals {}\n\t\t\/\/ interface PageData {}\n\t\t\/\/ interface Platform {}\n\t}\n\tvar prisma: PrismaClient;\n}\n\nexport {};\n\n<\/code>\n<\/pre>\n\n\n\n<p>With that in place, go to the <code>src\/lib\/<\/code> directory and create a new folder called <code>server<\/code>. Inside the <code>server<\/code> folder, add a file named <code>prisma.ts<\/code> and include the following code:<\/p>\n\n\n\n<p><strong>src\/lib\/server\/prisma.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\">\n<code>\nimport { PrismaClient } from &#039;@prisma\/client&#039;;\n\nconst prisma =\n\tglobalThis.prisma ??\n\tnew PrismaClient({\n\t\tlog: [&#039;query&#039;]\n\t});\n\nif (process.env.NODE_ENV != &#039;production&#039;) globalThis.prisma = prisma;\n\nexport { prisma };\n<\/code>\n<\/pre>\n\n\n\n<p>By placing the <code>prisma.ts<\/code> file within a <code>server<\/code> directory, we tell SvelteKit that the content of the file should only be used on the server. Alternatively, we could have directly indicated this in the file name as <code>prisma.server.ts<\/code>, which would achieve the same result as the folder approach.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Adding and Configuring Tailwind CSS<\/h2>\n\n\n\n<p>As of the current time, SvelteKit doesn&#8217;t include Tailwind CSS by default as Next.js does. Nevertheless, adding Tailwind CSS to your SvelteKit project is a straightforward process. You can do this with just one terminal command:<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\">\n<code>\nnpx svelte-add tailwindcss\n<\/code>\n<\/pre>\n\n\n\n<p>Upon executing this command, Tailwind CSS configurations will be automatically integrated into your project. The process generates two crucial files, <code>postcss.config.cjs<\/code> and <code>tailwind.config.cjs<\/code>, which come with default settings optimized for SvelteKit. Additionally, a <code>app.postcss<\/code> file will be created in the <code>src<\/code> directory, containing the essential Tailwind CSS directives.<\/p>\n\n\n\n<p>In case the <code>+layout.svelte<\/code> file is not yet present in the <code>src\/routes\/<\/code> directory, the setup process will generate it for you. It automatically imports the <code>app.postcss<\/code> file, ensuring that Tailwind CSS classes are applied throughout your markup.<\/p>\n\n\n\n<p>Now, let&#8217;s move on to customizing the Tailwind CSS container property and adding a font to the <code>fontFamily<\/code> property. To accomplish this, navigate to the <code>tailwind.config.cjs<\/code> file and replace its current content with the following configurations:<\/p>\n\n\n\n<p><strong>tailwind.config.cjs<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-js\">\n<code>\n\/** @type {import('tailwindcss').Config}*\/\nconst config = {\n\tcontent: ['.\/src\/**\/*.{html,js,svelte,ts}'],\n\n\ttheme: {\n    extend: {\n      fontFamily: {\n        Poppins: ['Poppins, sans-serif'],\n      },\n      container: {\n        center: true,\n        padding: '1rem',\n        screens: {\n          lg: '1125px',\n          xl: '1125px',\n          '2xl': '1125px',\n        },\n      },\n    },\n  },\n\n\tplugins: []\n};\n\nmodule.exports = config;\n<\/code>\n<\/pre>\n\n\n\n<p>Next up, let&#8217;s add the font import and apply some default CSS styling to the <code>app.postcss<\/code> file. To achieve this, open <code>src\/app.postcss<\/code> and replace the current content with the following code:<\/p>\n\n\n\n<p><strong>src\/app.postcss<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-css\">\n<code>\n\/* Write your global styles here, in PostCSS syntax *\/\n@import url('https:\/\/fonts.googleapis.com\/css2?family=Poppins:wght@300;400;500;600;700&display=swap');\n\n@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\nhtml {\n\tfont-family: 'Poppins', sans-serif;\n}\n\nbody {\n\tbackground-color: #202142;\n\twidth: 100%;\n\theight: 100%;\n}\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Adding a Toast Notification<\/h2>\n\n\n\n<p>To improve our project, let&#8217;s incorporate toast notifications that will provide user feedback upon completing certain operations. While there are various toast notification libraries to choose from, we&#8217;ll opt for <code><a href=\"https:\/\/zerodevx.github.io\/svelte-toast\/\" target=\"_blank\" rel=\"noreferrer noopener\">@zerodevx\/svelte-toast<\/a><\/code> due to its elegant design and customizable features. To install it, use the following command:<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\">\n<code>\n# For PNPM\npnpm add @zerodevx\/svelte-toast\n\n# For Yarn\nyarn add @zerodevx\/svelte-toast\n\n# For NPM\nnpm i @zerodevx\/svelte-toast\n<\/code>\n<\/pre>\n\n\n\n<p>To integrate <code>@zerodevx\/svelte-toast<\/code> into our SvelteKit application, follow these steps to configure the root layout component. Open the <code>src\/routes\/+layout.svelte<\/code> file and replace its content with the provided code:<\/p>\n\n\n\n<p><strong>src\/routes\/+layout.svelte<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-html\">\n<code>\n&lt;script&gt;\n\timport &#039;..\/app.postcss&#039;;\n\timport { SvelteToast } from &#039;@zerodevx\/svelte-toast&#039;;\n\n\tconst options = {\n\t\tinitial: 0,\n\t\tnext: 1,\n\t\tduration: 6000,\n\t\tpausable: true\n\t};\n&lt;\/script&gt;\n\n&lt;slot \/&gt;\n&lt;SvelteToast {options} \/&gt;\n\n&lt;style&gt;\n\t:global(.danger) {\n\t\t--toastBackground: #4299e1;\n\t\t--toastBarBackground: #2b6cb0;\n\t}\n\n\t:global(.success) {\n\t\t--toastBackground: rgba(245, 208, 254, 0.95);\n\t\t--toastColor: #424242;\n\t\t--toastBarBackground: fuchsia;\n\t}\n&lt;\/style&gt;\n<\/code>\n<\/pre>\n\n\n\n<p>In the provided code, we imported the <code>&lt;SvelteToast \/&gt;<\/code> container and placed it next to the <code>&lt;slot \/&gt;<\/code> component. The configuration object <code>options<\/code> allows us to modify settings such as the duration and appearance of the toast notifications.<\/p>\n\n\n\n<p>Moreover, we defined global classes, &#8220;<strong>danger<\/strong>&#8221; for error messages and &#8220;<strong>success<\/strong>&#8221; for successful messages. We&#8217;ll apply these classes when displaying the toast notifications for different scenarios.<\/p>\n\n\n\n<p>Feel free to further customize the toast notifications based on your needs. Detailed interactive documentation and more customization options can be found at <a href=\"https:\/\/zerodevx.github.io\/svelte-toast\/\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/zerodevx.github.io\/svelte-toast\/<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Creating a Svelte Writable Store<\/h2>\n\n\n\n<p>Phew, that was quite a few configurations! Now, we&#8217;re ready to start working on our feedback application. But first, we&#8217;ll create a special way to handle loading states using the <code>svelte\/store<\/code> library. This will help us manage situations where things take time to load, like when we send requests to our Form Actions.<\/p>\n\n\n\n<p>Here&#8217;s what you need to do. Go to the <code>src\/lib\/<\/code> folder, and create a new file called <code>store.ts<\/code>. Inside this file, put the following code:<\/p>\n\n\n\n<p><strong>src\/lib\/store.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\">\n<code>\nimport { writable } from &#039;svelte\/store&#039;;\n\ntype Store = {\n\tpageloading: boolean;\n\tsetPageLoading: (val: boolean) =&gt; void;\n};\n\nfunction useFeedbackStore() {\n\tconst { update, subscribe, set } = writable&lt;Store&gt;({\n\t\tpageloading: false,\n\t\tsetPageLoading: (val) =&gt; update((state: Store) =&gt; ({ ...state, pageloading: val }))\n\t});\n\treturn { update, subscribe, set };\n}\n\nconst feedbackStore = useFeedbackStore();\nexport default feedbackStore;\n<\/code>\n<\/pre>\n\n\n\n<p>In this code, we created a custom store named <code>feedbackStore<\/code> using the <code>svelte\/store<\/code> library. This store holds a boolean value, <code>pageloading<\/code>, which indicates whether a page or operation is currently loading. Additionally, we have a handy function called <code>setPageLoading<\/code>, which we can use to update the <code>pageloading<\/code> value.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Loading Data using SvelteKit Load Function<\/h2>\n\n\n\n<p>Now, let&#8217;s create a SvelteKit load function to fetch feedback items from the database and make them available on the page. To achieve this, we&#8217;ll use the <code>load<\/code> function in a <code>+page.server.ts<\/code> file, which means it will only run on the server. This is because we&#8217;ll be accessing the database using Prisma, which can only be used on the server. If we weren&#8217;t accessing the database, we would have used the <code>+page.ts<\/code> file instead.<\/p>\n\n\n\n<p>To set this up, create a <code>+page.server.ts<\/code> file in the <code>src\/routes\/<\/code> folder and add the following code:<\/p>\n\n\n\n<p><strong>src\/routes\/+page.server.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\">\n<code>\nimport type { PageServerLoad } from &#039;.\/$types&#039;;\nimport { prisma } from &#039;$lib\/server\/prisma&#039;;\n\nexport const load: PageServerLoad = async ({ url, depends }) =&gt; {\n\tdepends(&#039;fetch:feedbacks&#039;);\n\n\tconst pageQueryParam = url.searchParams.get(&#039;page&#039;);\n\tconst limitQueryParam = url.searchParams.get(&#039;limit&#039;);\n\tconst orderBy = url.searchParams.get(&#039;orderBy&#039;) === &#039;asc&#039; ? &#039;asc&#039; : &#039;desc&#039;;\n\n\tconst page = pageQueryParam ? parseInt(pageQueryParam, 10) : 1;\n\tconst limit = limitQueryParam ? parseInt(limitQueryParam, 10) : 10;\n\tconst skip = (page - 1) * limit;\n\n\tconst [totalFeedbacks, feedbacks] = await Promise.all([\n\t\tprisma.feedback.count(),\n\t\tprisma.feedback.findMany({\n\t\t\tskip,\n\t\t\ttake: limit,\n\t\t\torderBy: {\n\t\t\t\tcreatedAt: orderBy\n\t\t\t}\n\t\t})\n\t]);\n\n\tconst totalPages = Math.ceil(totalFeedbacks \/ limit);\n\tconst hasNextPage = page &lt; totalPages;\n\tconst hasPreviousPage = page &gt; 1;\n\n\treturn {\n\t\tstatus: &#039;success&#039;,\n\t\tpagination: {\n\t\t\ttotalPages,\n\t\t\tcurrentPage: page,\n\t\t\ttotalResults: totalFeedbacks,\n\t\t\thasNextPage,\n\t\t\thasPreviousPage\n\t\t},\n\t\tfeedbacks\n\t};\n};\n<\/code>\n<\/pre>\n\n\n\n<p>Quite a lot is happening in the above code. Let&#8217;s break it down:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>First, we defined the <code>load<\/code> function and typed it using the <code>PageServerLoad<\/code> type provided by SvelteKit.<\/li>\n\n\n\n<li>Then, we used the <code>depends<\/code> function to specify that this page&#8217;s server load relies on data fetched using the name <code>'fetch:feedbacks'<\/code>. This approach allows us to utilize the <code>invalidate<\/code> function later, which triggers the <code>load<\/code> function to re-run and fetch the most up-to-date feedback data.<\/li>\n\n\n\n<li>Next, we extracted the <code>page<\/code>, <code>limit<\/code>, and <code>orderBy<\/code> query parameters from the incoming request URL and assigned default values to them in case they were omitted.<\/li>\n\n\n\n<li>Moving on, we used the Prisma client to perform two queries in parallel using <code>Promise.all()<\/code>: one to get the total count of feedbacks and another to get the feedback items for the current page, respecting the specified <code>limit<\/code>, <code>skip<\/code>, and <code>orderBy<\/code> values.<\/li>\n\n\n\n<li>Finally, we calculated the total number of pages based on the total number of feedback items and the limit. The function also determines whether there are next or previous pages available based on the current page number. Then, the function returns an object containing the status of the request, pagination information, and the array of feedback for the current page.<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">Creating the Form Actions<\/h2>\n\n\n\n<p>At this point, we come to the most important section of this article where we will create Form Actions that will enable us to POST data to the server to create, edit, and delete feedback items in the database. Apart from exporting a <code>load<\/code> function in the <code>+page.server.ts<\/code> file, we can also export <code>actions<\/code> that will enable us to mutate data on the server from the frontend.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Adding a New Feedback<\/h3>\n\n\n\n<p>Let&#8217;s begin by creating the first Form Action responsible for adding feedback items to the server database. First, open the <code>+page.server.ts<\/code> file and add the following import at the top:<\/p>\n\n\n\n<pre class=\"line-numbers language-ts\">\n<code>\nimport { type Actions, fail } from &#039;@sveltejs\/kit&#039;;\n<\/code>\n<\/pre>\n\n\n\n<p>Next, below the <code>load<\/code> function, include the following code:<\/p>\n\n\n\n<p><strong>src\/routes\/+page.server.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\">\n<code>\nexport const actions = {\n\taddFeedback: async ({ request }) =&gt; {\n\t\ttry {\n\t\t\tconst { text, rating } = Object.fromEntries(await request.formData()) as {\n\t\t\t\ttext?: string;\n\t\t\t\trating: string;\n\t\t\t};\n\n\t\t\tif (!text) {\n\t\t\t\treturn fail(400, {\n\t\t\t\t\ttype: &#039;add&#039;,\n\t\t\t\t\tmessage: &#039;feedback input cannot be empty&#039;,\n\t\t\t\t\tfeedback: { text, rating, id: &#039;&#039; }\n\t\t\t\t});\n\t\t\t} else if (text.trim().length &lt; 10) {\n\t\t\t\treturn fail(400, {\n\t\t\t\t\ttype: &#039;add&#039;,\n\t\t\t\t\tmessage: &#039;feedback text must be at least 10 characters&#039;,\n\t\t\t\t\tfeedback: { text, rating, id: &#039;&#039; }\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst feedback = await prisma.feedback.create({\n\t\t\t\tdata: { text, rating: Number(rating) }\n\t\t\t});\n\n\t\t\treturn { newFeedback: feedback };\n\t\t} catch (err: any) {\n\t\t\tif (err.code === &#039;P2002&#039;) {\n\t\t\t\treturn fail(409, {\n\t\t\t\t\ttype: &#039;add&#039;,\n\t\t\t\t\tmessage: &#039;Feedback with this title already exists&#039;,\n\t\t\t\t\tfeedback: { text: &#039;&#039;, id: &#039;&#039; }\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn fail(500, {\n\t\t\t\ttype: &#039;add&#039;,\n\t\t\t\tmessage: err.message,\n\t\t\t\tfeedback: { text: &#039;&#039;, id: &#039;&#039; }\n\t\t\t});\n\t\t}\n\t}\n} satisfies Actions;\n<\/code>\n<\/pre>\n\n\n\n<p>Here&#8217;s an overview of what the Form Action does:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>The <code>addFeedback<\/code> function is an asynchronous function that takes a <code>request<\/code> object as an argument. It is responsible for handling the addition of new feedback items to the server database.<\/li>\n\n\n\n<li>The function extracts the <code>text<\/code> and <code>rating<\/code> values from the form data sent with the request. It expects a form submission with the fields <code>text<\/code> (optional) and <code>rating<\/code> (required).<\/li>\n\n\n\n<li>It performs validations on the input data to ensure that the <code>text<\/code> field is not empty and that it contains at least 10 characters.<\/li>\n\n\n\n<li>If the input data is valid, it uses the Prisma client to add the new feedback item to the database. The <code>rating<\/code> value is converted to a number before being saved.<\/li>\n\n\n\n<li>If the addition is successful, the function returns an object containing the newly added feedback item with the key <code>newFeedback<\/code>.<\/li>\n\n\n\n<li>If there is an error during the addition process, the function handles different types of errors. If the feedback item with the same title already exists in the database, it returns a conflict error with an appropriate error message. Otherwise, it returns a generic error with the error message provided by the caught exception.<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">Editing an Existing Feedback<\/h3>\n\n\n\n<p>Still, in the same <code>+page.server.ts<\/code> file, let&#8217;s add another method to the <code>actions<\/code> object that will handle editing or modifying a feedback item in the database. Copy the <code>editFeedback<\/code> function and add it to the <code>actions<\/code> object.<\/p>\n\n\n\n<p><strong>src\/routes\/+page.server.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\">\n<code>\nexport const actions = {\n\t\/\/ \ud83d\udc48 addFeedback Action\n\n\teditFeedback: async ({ url, request }) =&gt; {\n\t\tconst feedbackId = url.searchParams.get(&#039;id&#039;);\n\t\ttry {\n\t\t\tif (!feedbackId) {\n\t\t\t\treturn fail(400, {\n\t\t\t\t\ttype: &#039;edit&#039;,\n\t\t\t\t\tmessage: &#039;invalid request&#039;,\n\t\t\t\t\tfeedback: { text: &#039;&#039;, id: feedbackId }\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst { text } = Object.fromEntries(await request.formData()) as { text?: string };\n\n\t\t\tif (!text) {\n\t\t\t\treturn fail(400, {\n\t\t\t\t\ttype: &#039;edit&#039;,\n\t\t\t\t\tmessage: &#039;feedback input cannot be empty&#039;,\n\t\t\t\t\tfeedback: { text, id: feedbackId }\n\t\t\t\t});\n\t\t\t} else if (text.trim().length &lt; 10) {\n\t\t\t\treturn fail(400, {\n\t\t\t\t\ttype: &#039;edit&#039;,\n\t\t\t\t\tmessage: &#039;feedback text must be at least 10 characters&#039;,\n\t\t\t\t\tfeedback: { text, id: feedbackId }\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tawait prisma.feedback.update({\n\t\t\t\twhere: { id: feedbackId },\n\t\t\t\tdata: { text }\n\t\t\t});\n\n\t\t\treturn { status: 200 };\n\t\t} catch (err: any) {\n\t\t\tif (err.code === &#039;P2002&#039;) {\n\t\t\t\treturn fail(409, {\n\t\t\t\t\ttype: &#039;edit&#039;,\n\t\t\t\t\tmessage: &#039;Feedback with this title already exists&#039;,\n\t\t\t\t\tfeedback: { text: &#039;&#039;, id: feedbackId }\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (err.code === &#039;P2025&#039;) {\n\t\t\t\treturn fail(404, {\n\t\t\t\t\ttype: &#039;edit&#039;,\n\t\t\t\t\tmessage: &#039;No Feedback with the Provided ID Found&#039;,\n\t\t\t\t\tfeedback: { text: &#039;&#039;, id: feedbackId }\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn fail(500, {\n\t\t\t\ttype: &#039;edit&#039;,\n\t\t\t\tmessage: err.message,\n\t\t\t\tfeedback: { text: &#039;&#039;, id: feedbackId }\n\t\t\t});\n\t\t}\n\t}\n} satisfies Actions;\n<\/code>\n<\/pre>\n\n\n\n<p>In the above code, we implemented the <code>editFeedback<\/code> function in the <code>actions<\/code> object. This function is responsible for handling the editing of existing feedback items in the database when a form action is submitted to the <code>?\/editFeedback&amp;id={feedback.id}<\/code> URL.<\/p>\n\n\n\n<p>When the function is invoked, it extracts the feedback ID from the URL and checks if it is provided. If the ID is missing, it returns a 400 (Bad Request) error with an appropriate message.<\/p>\n\n\n\n<p>Next, the function extracts the <code>text<\/code> value from the form data submitted with the request. It performs some validations to ensure that the text is provided and that it is at least 10 characters long.<\/p>\n\n\n\n<p>If the validation passes, the function uses the Prisma client to update the feedback item in the database with the new text content.<\/p>\n\n\n\n<p>In case of any errors during the editing process, the function catches and handles specific Prisma error codes to return appropriate error responses with status codes and error messages.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Deleting a Feedback<\/h3>\n\n\n\n<p>Continuing in the same <code>+page.server.ts<\/code> file, let&#8217;s add the last Form Action that will handle the deletion of feedback items in the database. We will name this Form Action <code>deleteFeedback<\/code>, and it will be triggered when a form action is submitted to the <code>?\/deleteFeedback&amp;id={feedback.id}<\/code> URL.<\/p>\n\n\n\n<p><strong>src\/routes\/+page.server.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\">\n<code>\nexport const actions = {\n\t\/\/ \ud83d\udc48 addFeedback Action\n\n\t\/\/ \ud83d\udc48 editFeedback Action\n\t\n\tdeleteFeedback: async ({ url }) =&gt; {\n\t\tconst feedbackId = url.searchParams.get(&#039;id&#039;);\n\t\ttry {\n\t\t\tif (!feedbackId) {\n\t\t\t\treturn fail(400, {\n\t\t\t\t\ttype: &#039;delete&#039;,\n\t\t\t\t\tmessage: &#039;invalid request&#039;,\n\t\t\t\t\tfeedback: { text: &#039;&#039;, id: feedbackId }\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tawait prisma.feedback.delete({\n\t\t\t\twhere: { id: feedbackId }\n\t\t\t});\n\n\t\t\treturn {};\n\t\t} catch (err: any) {\n\t\t\tif (err.code === &#039;P2025&#039;) {\n\t\t\t\treturn fail(404, {\n\t\t\t\t\ttype: &#039;delete&#039;,\n\t\t\t\t\tmessage: &#039;No Feedback with the Provided ID Found&#039;,\n\t\t\t\t\tfeedback: { text: &#039;&#039;, id: feedbackId }\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn fail(500, {\n\t\t\t\ttype: &#039;delete&#039;,\n\t\t\t\tmessage: err.message,\n\t\t\t\tfeedback: { text: &#039;&#039;, id: feedbackId }\n\t\t\t});\n\t\t}\n\t}\n} satisfies Actions;\n<\/code>\n<\/pre>\n\n\n\n<p>When the function is invoked, it extracts the feedback ID from the URL and checks if it is provided. If the ID is missing, it returns a 400 (Bad Request) error with an appropriate message.<\/p>\n\n\n\n<p>Next, the function uses the Prisma client to delete the feedback item from the database based on the provided ID.<\/p>\n\n\n\n<p>In case of any errors during the deletion process, the function catches and handles specific Prisma error codes to return appropriate error responses with status codes and error messages.<\/p>\n\n\n\n<p><strong>Note<\/strong>: The <code>return {};<\/code> statement after successful deletion is used to indicate that the deletion was successful. Since we do not need to return any additional data, an empty object is returned.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Creating the Svelte Components<\/h2>\n\n\n\n<p>In this part, we&#8217;ll create several components, including Rating, Statistic, Form, and Feedback Item components. This method allows us to divide the code into smaller, reusable parts, ensuring that the page files remain organized and uncluttered. By doing so, we avoid adding all the code directly to the page file, making it easier to maintain and understand.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Rating Component<\/h3>\n\n\n\n<p>The Rating component is our first component, allowing users to select a rating from a list of options. Only one option can be selected at a time, as each feedback item should have a single rating value. Here&#8217;s how to create the Rating component:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Start by creating a <code>components<\/code> folder within the <code>src<\/code> directory.<\/li>\n\n\n\n<li>Inside the <code>components<\/code> folder, create a new file named <code>rating.svelte<\/code>.<\/li>\n\n\n\n<li>Now, add the following code to the <code>rating.svelte<\/code> file:<\/li>\n<\/ol>\n\n\n\n<p><strong>src\/components\/rating.svelte<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-html\">\n<code>\n&lt;script lang=&quot;ts&quot;&gt;\n\texport let selected = 10;\n&lt;\/script&gt;\n\n&lt;ul\n\tclass=&quot;list-none grid grid-cols-5 sm:grid-cols-10 gap-y-2 sm:grid-rows-1 place-items-center sm:gap-0 sm:justify-around sm:my-7 my-4&quot;\n&gt;\n\t{#each Array.from({ length: 10 }, (_, i) =&gt; i + 1) as i}\n\t\t&lt;li\n\t\t\tclass=&quot;relative sm:w-14 sm:h-14 w-10 h-10 p-3 text-center rounded-full border-gray-300 border-2 transition duration-300 {selected ===\n\t\t\ti\n\t\t\t\t? &#039;bg-pink-500 text-white&#039;\n\t\t\t\t: &#039;bg-gray-200&#039;}&quot;\n\t\t&gt;\n\t\t\t&lt;input\n\t\t\t\ttype=&quot;radio&quot;\n\t\t\t\tclass=&quot;opacity-0&quot;\n\t\t\t\tid={`feedback-rating-radio-button-${i}`}\n\t\t\t\tname=&quot;rating&quot;\n\t\t\t\tvalue={i}\n\t\t\t\tbind:group={selected}\n\t\t\t\/&gt;\n\t\t\t&lt;label\n\t\t\t\tfor={`feedback-rating-radio-button-${i}`}\n\t\t\t\tclass=&quot;absolute text-sm sm:text-base w-full h-full flex items-center justify-center rounded-full top-1\/2 left-1\/2 transform -translate-x-1\/2 -translate-y-1\/2 cursor-pointer hover:bg-pink-500 hover:text-white transition duration-300&quot;\n\t\t\t&gt;\n\t\t\t\t{i}\n\t\t\t&lt;\/label&gt;\n\t\t&lt;\/li&gt;\n\t{\/each}\n&lt;\/ul&gt;\n<\/code>\n<\/pre>\n\n\n\n<p>In the above code, we&#8217;ve created an array of <strong>10<\/strong> elements and used the <code>{#each}<\/code> block to iterate through them. For each iteration, we display a radio button and a label inside a list item (<code>&lt;li&gt;<\/code>). The appearance of each rating option changes based on whether it is selected (<code>{selected === i}<\/code>).<\/p>\n\n\n\n<p>The <code>selected<\/code> prop is bound to the hidden input element (<code>&lt;input type=\"radio\"&gt;<\/code>). When a user clicks on a rating option, the value is updated, and the appearance of the rating options reflects the selected value.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Statistic Component<\/h3>\n\n\n\n<p>Next, let&#8217;s proceed with creating the Statistic component, which will display essential statistics about the feedback items, such as the total number of feedback items returned from the database and the average rating. To create this Statistic component, follow these steps:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Begin by creating a new file named <code>feedback-stats.svelte<\/code> in the <code>components<\/code> directory.<\/li>\n\n\n\n<li>Now, add the following code to the <code>feedback-stats.svelte<\/code> file:<\/li>\n<\/ol>\n\n\n\n<p><strong>src\/components\/feedback-stats.svelte<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-html\">\n<code>\n&lt;script lang=&quot;ts&quot;&gt;\n\timport type { Feedback } from &#039;@prisma\/client&#039;;\n\n\texport let feedbacks: Feedback[];\n\n\t$: count = feedbacks.length;\n\t$: sum = feedbacks.reduce((acc, feedback) =&gt; acc + feedback.rating, 0);\n\t$: average = count &gt; 0 ? (sum \/ count).toFixed(2) : &#039;0.00&#039;;\n&lt;\/script&gt;\n\n&lt;div class=&quot;flex justify-between items-center mb-11 text-white&quot;&gt;\n\t&lt;h4&gt;{count} Reviews&lt;\/h4&gt;\n\t&lt;h4&gt;Ratings Average: {average}&lt;\/h4&gt;\n&lt;\/div&gt;\n<\/code>\n<\/pre>\n\n\n\n<p>In the code above, we&#8217;ve created a Statistic component that receives a prop <code>feedbacks<\/code> containing an array of <code>Feedback<\/code> objects. We use reactive statements (<code>$:<\/code>) to calculate the <code>count<\/code>, <code>sum<\/code>, and <code>average<\/code> based on the <code>feedbacks<\/code> array.<\/p>\n\n\n\n<p>The <code>count<\/code> represents the total number of feedback items. The <code>sum<\/code> is the sum of all ratings from the feedback items. The <code>average<\/code> calculates the average rating, displayed with two decimal places (<code>toFixed(2)<\/code>), or &#8216;0.00&#8217; if no feedback is available.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Feedback Form Component<\/h3>\n\n\n\n<p>Now, let&#8217;s proceed with creating the Form component, which will allow users to add new feedback. We&#8217;ll use our <code>addFeedback<\/code> Form Action that we previously defined in the <code>+page.server.ts<\/code> file. Additionally, the form component will utilize the Rating component we created earlier. <\/p>\n\n\n\n<p>We will progressively enhance the form to ensure it functions even when JavaScript is disabled or unavailable. However, there will be a minor issue with the Rating component when JavaScript is disabled; selecting a rating will not trigger a color change to indicate selection, as that functionality depends on JavaScript.<\/p>\n\n\n\n<p>To create the Form component, follow these steps:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Create a new file named <code>feedback-form.svelte<\/code> in the <code>components<\/code> directory.<\/li>\n\n\n\n<li>Add the following code to the <code>feedback-form.svelte<\/code> file:<\/li>\n<\/ol>\n\n\n\n<p><strong>src\/components\/feedback-form.svelte<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-html\">\n<code>\n&lt;script lang=&quot;ts&quot;&gt;\n\timport { enhance } from &#039;$app\/forms&#039;;\n\timport { toast } from &#039;@zerodevx\/svelte-toast&#039;;\n\timport Rating from &#039;.\/rating.svelte&#039;;\n\timport feedbackStore from &#039;$lib\/store&#039;;\n\n\timport type { SubmitFunction } from &#039;@sveltejs\/kit&#039;;\n\timport type { ActionData } from &#039;..\/routes\/$types&#039;;\n\n\texport let form: ActionData;\n\tlet rating = 10;\n\n\tconst handleSubmit: SubmitFunction = () =&gt; {\n\t\t$feedbackStore.setPageLoading(true);\n\t\treturn async ({ update, result }) =&gt; {\n\t\t\tawait update();\n\t\t\tif (result.type === &#039;success&#039;) {\n\t\t\t\trating = 10;\n\t\t\t\ttoast.push(&#039;Added Feedback Successfully&#039;, { classes: [&#039;success&#039;] });\n\t\t\t}\n\t\t\t$feedbackStore.setPageLoading(false);\n\t\t};\n\t};\n&lt;\/script&gt;\n\n&lt;div class=&quot;bg-white text-gray-700 rounded-lg p-2 my-2 sm:p-8 sm:my-5 relative&quot;&gt;\n\t&lt;form action=&quot;?\/addFeedback&quot; method=&quot;POST&quot; use:enhance={handleSubmit}&gt;\n\t\t&lt;div class=&quot;max-w-md mx-auto&quot;&gt;\n\t\t\t&lt;label for=&quot;feedback-input&quot; class=&quot;inline-block text-center text-2xl font-bold&quot;\n\t\t\t\t&gt;How would you rate your service with us?&lt;\/label\n\t\t\t&gt;\n\t\t&lt;\/div&gt;\n\t\t&lt;Rating bind:selected={rating} \/&gt;\n\t\t&lt;div\n\t\t\tclass=&quot;flex flex-col items-center gap-y-4 sm:gap-y-0 sm:flex-row sm:border rounded-lg sm:my-4 px-2 py-3&quot;\n\t\t&gt;\n\t\t\t&lt;input\n\t\t\t\ttype=&quot;text&quot;\n\t\t\t\tid=&quot;feedback-input&quot;\n\t\t\t\tname=&quot;text&quot;\n\t\t\t\tvalue={form?.feedback?.text ?? &#039;&#039;}\n\t\t\t\tclass=&quot;sm:flex-grow border w-full rounded sm:rounded-none px-2 py-3 sm:border-none text-base sm:text-lg focus:outline-none&quot;\n\t\t\t\tplaceholder=&quot;Tell us something that keeps you coming back&quot;\n\t\t\t\/&gt;\n\t\t\t&lt;button\n\t\t\t\ttype=&quot;submit&quot;\n\t\t\t\tclass=&quot;border-0 rounded-md w-28 h-10 cursor-pointer bg-indigo-600 text-white hover:bg-indigo-500 disabled:bg-gray-400 disabled:text-gray-800 disabled:cursor-not-allowed&quot;\n\t\t\t&gt;\n\t\t\t\tSend\n\t\t\t&lt;\/button&gt;\n\t\t&lt;\/div&gt;\n\t\t{#if form?.message &amp;&amp; form.type === &#039;add&#039;}\n\t\t\t&lt;div role=&quot;alert&quot; aria-live=&quot;polite&quot; class=&quot;sm:pt-3 text-center text-purple-600&quot;&gt;\n\t\t\t\t{form.message}\n\t\t\t&lt;\/div&gt;\n\t\t{\/if}\n\t&lt;\/form&gt;\n&lt;\/div&gt;\n<\/code>\n<\/pre>\n\n\n\n<p>In the code above, we&#8217;ve created the Form component, which will allow users to add new feedback. The <code>rating<\/code> variable holds the selected rating, which will be initialized to 10. The form uses the <code>enhance<\/code> function from SvelteKit to handle form submissions. When the form is submitted, the <code>handleSubmit<\/code> function is called.<\/p>\n\n\n\n<p>The form contains a label, the Rating component, and text input for user feedback. When the user submits the form, the <code>addFeedback<\/code> Form Action will be triggered. If the addition is successful, a success toast notification will be displayed.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Feedback Item Component<\/h3>\n\n\n\n<p>Let&#8217;s proceed with creating the feedback item component, which will allow us to display the content of a feedback item. We&#8217;ll utilize the <code>editFeedback<\/code> and <code>deleteFeedback<\/code> Form Actions that we defined in the <code>+page.server.ts<\/code> file to enable editing and deleting feedback from the database. <\/p>\n\n\n\n<p>The component will have an Edit button that, when clicked, displays a hidden input element to edit the feedback&#8217;s text. Additionally, there will be a Delete icon that, when clicked, triggers the <code>deleteFeedback<\/code> action to permanently remove the feedback item from both the database and the UI.<\/p>\n\n\n\n<p>To create this component, follow these steps:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Create a new file named <code>feedback-item.svelte<\/code> in the <code>components<\/code> folder.<\/li>\n\n\n\n<li>Add the following code to the <code>feedback-item.svelte<\/code> file:<\/li>\n<\/ol>\n\n\n\n<p><strong>src\/components\/feedback-item.svelte<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-html\">\n<code>\n&lt;script lang=&quot;ts&quot;&gt;\n\timport { enhance } from &#039;$app\/forms&#039;;\n\timport { toast } from &#039;@zerodevx\/svelte-toast&#039;;\n\timport { tick } from &#039;svelte&#039;;\n\timport { fade } from &#039;svelte\/transition&#039;;\n\n\timport feedbackStore from &#039;$lib\/store&#039;;\n\timport type { Feedback } from &#039;@prisma\/client&#039;;\n\timport type { SubmitFunction } from &#039;@sveltejs\/kit&#039;;\n\timport type { ActionData } from &#039;..\/routes\/$types&#039;;\n\n\texport let feedback: Feedback;\n\texport let form: ActionData;\n\n\tlet editing = false;\n\tlet editedFeedbackText = feedback.text;\n\n\tfunction windowConfirm(message: string) {\n\t\treturn window.confirm(message);\n\t}\n\n\tlet inputElement: HTMLInputElement | null = null;\n\n\t$: if (editing) {\n\t\t(async () =&gt; {\n\t\t\tawait tick();\n\t\t\tinputElement?.focus();\n\t\t})();\n\t}\n\n\tconst handleSubmit: SubmitFunction = (options) =&gt; {\n\t\tconst deleteAction = options.action.search.includes(&#039;deleteFeedback&#039;);\n\t\tconst editAction = options.action.search.includes(&#039;editFeedback&#039;);\n\n\t\tif (deleteAction) {\n\t\t\tconst confirm = windowConfirm(&#039;Do you really want to delete this item?&#039;);\n\t\t\tif (!confirm) {\n\t\t\t\toptions.cancel();\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tif (feedback.text === editedFeedbackText &amp;&amp; editAction) {\n\t\t\tediting = false;\n\t\t\toptions.cancel();\n\t\t\treturn;\n\t\t}\n\n\t\t$feedbackStore.setPageLoading(true);\n\t\treturn async ({ update, result }) =&gt; {\n\t\t\tif (result.type === &#039;success&#039; &amp;&amp; editAction) editing = false;\n\t\t\tawait update();\n\n\t\t\tif (result.type === &#039;success&#039; &amp;&amp; editAction) {\n\t\t\t\ttoast.push(&#039;Edited Feedback Successfully&#039;, { classes: [&#039;success&#039;] });\n\t\t\t} else if (result.type === &#039;success&#039; &amp;&amp; deleteAction) {\n\t\t\t\tediting = false;\n\t\t\t\ttoast.push(&#039;Deleted Feedback Successfully&#039;, { classes: [&#039;success&#039;] });\n\t\t\t}\n\n\t\t\t$feedbackStore.setPageLoading(false);\n\t\t};\n\t};\n&lt;\/script&gt;\n\n&lt;form\n\taction=&quot;?\/editFeedback&amp;id={feedback.id}&quot;\n\tmethod=&quot;POST&quot;\n\tuse:enhance={handleSubmit}\n\tin:fade={{ delay: 300, duration: 300 }}\n\tout:fade={{ delay: 300, duration: 300 }}\n\tclass=&quot;bg-white text-gray-700 rounded-lg p-8 my-5 relative&quot;\n&gt;\n\t&lt;div\n\t\tclass=&quot;bg-pink-500 text-white rounded-full border-2 border-gray-200 w-12 h-12 flex items-center justify-center text-2xl font-bold absolute top-0 left-0 -mt-4 -ml-4&quot;\n\t&gt;\n\t\t{feedback.rating}\n\t&lt;\/div&gt;\n\t&lt;button\n\t\tclass=&quot;absolute text-gray-900 font-semibold top-2 right-4&quot;\n\t\tformaction=&quot;?\/deleteFeedback&amp;id={feedback.id}&quot;\n\t&gt;\n\t\tX\n\t&lt;\/button&gt;\n\t&lt;button\n\t\ttype=&quot;button&quot;\n\t\tclass=&quot;absolute top-2 right-12 text-pink-500 hover:text-pink-700&quot;\n\t\tclass:hidden={editing}\n\t\ton:click={() =&gt; (editing = true)}&gt;Edit&lt;\/button\n\t&gt;\n\t{#if editing}\n\t\t&lt;div class=&quot;flex flex-col items-center sm:flex-row justify-between sm:gap-4 gap-y-2&quot;&gt;\n\t\t\t&lt;input\n\t\t\t\ttype=&quot;text&quot;\n\t\t\t\tname=&quot;text&quot;\n\t\t\t\tclass=&quot;border rounded-lg p-2 w-full focus:outline-none&quot;\n\t\t\t\tbind:value={editedFeedbackText}\n\t\t\t\tbind:this={inputElement}\n\t\t\t\/&gt;\n\t\t\t&lt;div class=&quot;flex gap-2&quot;&gt;\n\t\t\t\t&lt;button type=&quot;submit&quot; class=&quot;text-pink-500 hover:text-pink-700&quot;&gt;Save&lt;\/button&gt;\n\t\t\t\t&lt;button class=&quot;hover:text-pink-700&quot; on:click={() =&gt; (editing = false)}&gt;Cancel&lt;\/button&gt;\n\t\t\t&lt;\/div&gt;\n\t\t&lt;\/div&gt;\n\t\t{#if form?.message &amp;&amp; form.type === &#039;edit&#039; &amp;&amp; feedback.id === form.feedback.id}\n\t\t\t&lt;div class=&quot;sm:pt-3 text-sm text-center text-purple-600&quot;&gt;{form.message}&lt;\/div&gt;\n\t\t{\/if}\n\t{:else}\n\t\t&lt;p&gt;{feedback.text}&lt;\/p&gt;\n\t{\/if}\n&lt;\/form&gt;\n<\/code>\n<\/pre>\n\n\n\n<p>It&#8217;s essential to be aware that the Edit functionality won&#8217;t work when JavaScript is disabled, as it relies on state changes to display the hidden input element. However, the Delete functionality will still work even when JavaScript is disabled. This means that users with JavaScript disabled will be able to delete feedback items without any issues.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Using the Components in a Page File<\/h2>\n\n\n\n<p>Oops, there seems to be quite a lot of code now. However, don&#8217;t worry; we&#8217;re almost done with creating all the necessary components for our project. The next step is to import these components into the root page file and use them so that they can be rendered on the actual page.<\/p>\n\n\n\n<p>To do this, let&#8217;s open the <code>+page.svelte<\/code> file in the <code>routes<\/code> directory and add the following code:<\/p>\n\n\n\n<p><strong>src\/routes\/+page.svelte<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-html\">\n<code>\n&lt;script lang=&quot;ts&quot;&gt;\n\timport { fade } from &#039;svelte\/transition&#039;;\n\timport { onMount } from &#039;svelte&#039;;\n\timport FeedbackForm from &#039;..\/components\/feedback-form.svelte&#039;;\n\timport FeedbackStats from &#039;..\/components\/feedback-stats.svelte&#039;;\n\timport FeedbackItem from &#039;..\/components\/feedback-item.svelte&#039;;\n\timport type { ActionData, PageData } from &#039;.\/$types&#039;;\n\timport { invalidate } from &#039;$app\/navigation&#039;;\n\timport feedbackStore from &#039;$lib\/store&#039;;\n\n\texport let data: PageData;\n\texport let form: ActionData;\n\t$: ({ feedbacks } = data);\n\tlet pageLoading = false;\n\n\tasync function onFocus() {\n\t\t$feedbackStore.setPageLoading(true);\n\t\tawait invalidate(&#039;fetch:feedbacks&#039;);\n\t\t$feedbackStore.setPageLoading(false);\n\t}\n\n\tonMount(() =&gt; {\n\t\twindow.addEventListener(&#039;focus&#039;, onFocus);\n\n\t\treturn () =&gt; {\n\t\t\twindow.removeEventListener(&#039;focus&#039;, onFocus);\n\t\t};\n\t});\n\n\t$: pageLoading = $feedbackStore.pageloading;\n&lt;\/script&gt;\n\n&lt;svelte:head&gt;\n\t&lt;title&gt;Feedback App \u2705&lt;\/title&gt;\n&lt;\/svelte:head&gt;\n\n&lt;main class=&quot;md:container mt-10 sm:mt-24 px-5&quot;&gt;\n\t&lt;FeedbackForm {form} \/&gt;\n\t&lt;FeedbackStats {feedbacks} \/&gt;\n\n\t{#each feedbacks as feedback}\n\t\t&lt;FeedbackItem {form} {feedback} \/&gt;\n\t{\/each}\n\n\t{#if feedbacks.length === 0}\n\t\t&lt;p\n\t\t\tin:fade={{ delay: 700, duration: 300 }}\n\t\t\tclass=&quot;max-w-md mx-auto py-6 text-center text-lg rounded-md bg-white&quot;\n\t\t&gt;\n\t\t\tNo Feedbacks Found\n\t\t&lt;\/p&gt;\n\t{\/if}\n&lt;\/main&gt;\n{#if pageLoading}\n\t&lt;div\n\t\tclass=&quot;fixed top-2 left-2 sm:top-5 sm:left-5 inline-block h-4 w-4 sm:h-8 sm:w-8 animate-spin rounded-full border-2 sm:border-4 border-solid border-yellow-400 border-r-transparent align-[-0.125em] text-warning motion-reduce:animate-[spin_1.5s_linear_infinite]&quot;\n\t\trole=&quot;status&quot;\n\t\/&gt;\n{\/if}\n<\/code>\n<\/pre>\n\n\n\n<p>In the code above, we have imported the necessary components and functions at the top of the file. The <code>onFocus<\/code> function is used to handle page reloading when the page gains focus. We invoke <code>invalidate('fetch:feedbacks')<\/code> to trigger the <code>load<\/code> function defined in <code>+page.server.ts<\/code> to re-run and fetch the latest changes in the feedback items from the database.<\/p>\n\n\n\n<p>The main content of the page is wrapped inside the <code>&lt;main&gt;<\/code> tag. We render the <code>FeedbackForm<\/code>, <code>FeedbackStats<\/code>, and a list of <code>FeedbackItem<\/code> components for each feedback item in the <code>feedbacks<\/code> array.<\/p>\n\n\n\n<p>If there are no feedback items, we display a message &#8220;<strong>No Feedbacks Found<\/strong>&#8221; using the <code>fade<\/code> transition. Additionally, if the page is still loading (indicated by <code>pageLoading<\/code> being true), a spinning loader animation is displayed.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Testing the SvelteKit Application Locally<\/h2>\n\n\n\n<p>Now that we have completed the project, it&#8217;s time to test it to ensure everything works as expected. First, make sure the Postgres and pgAdmin Docker containers are running, and the Prisma migrations have been applied to the database. If the Docker containers are not running, you can launch them using the command <code>docker-compose up -d<\/code>. If you haven&#8217;t synced the database schema with the Prisma schema, you can do so by running the command <code>pnpm prisma migrate dev<\/code>. This will connect to the Postgres database, apply the migrations, and generate the Prisma Client.<\/p>\n\n\n\n<p>Next, start the SvelteKit development server on port 3000 by running the command <code>pnpm dev --port 3000<\/code>. Once the server is up and running, open the application in your browser at <code>http:\/\/localhost:3000\/<\/code>.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"938\" height=\"1024\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/08\/SvelteKit-Feedback-Application-using-Form-Actions-in-Desktop-View-938x1024.webp\" alt=\"SvelteKit Feedback Application using Form Actions in Desktop View\" class=\"wp-image-11912\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/08\/SvelteKit-Feedback-Application-using-Form-Actions-in-Desktop-View-938x1024.webp 938w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/08\/SvelteKit-Feedback-Application-using-Form-Actions-in-Desktop-View-275x300.webp 275w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/08\/SvelteKit-Feedback-Application-using-Form-Actions-in-Desktop-View-768x839.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/08\/SvelteKit-Feedback-Application-using-Form-Actions-in-Desktop-View-92x100.webp 92w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/08\/SvelteKit-Feedback-Application-using-Form-Actions-in-Desktop-View-412x450.webp 412w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/08\/SvelteKit-Feedback-Application-using-Form-Actions-in-Desktop-View.webp 989w\" sizes=\"auto, (max-width: 938px) 100vw, 938px\" \/><\/figure>\n\n\n\n<p>Now, you can thoroughly test the application by performing the following actions:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Add Feedback: Use the feedback form to add new feedback items. Verify that the feedback is successfully added to the database and displayed on the page.<\/li>\n\n\n\n<li>Edit Feedback: Click the &#8220;<strong>Edit<\/strong>&#8221; button on a feedback item to activate the editing mode. Modify the feedback text and save the changes. Ensure that the feedback is updated in the database and the changes are reflected on the page.<\/li>\n\n\n\n<li>Delete Feedback: Click the &#8220;<strong>X<\/strong>&#8221; icon on a feedback item to delete it. Confirm the deletion and verify that the feedback is removed from the database and no longer displayed on the page.<\/li>\n\n\n\n<li>Progressive Enhancement: Disable JavaScript in your browser settings and try adding and deleting feedback items. These functionalities should still work because they are progressively enhanced to function even without JavaScript.<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">Deploying the Application to Vercel<\/h2>\n\n\n\n<p>Now that we have confirmed the application works in the development environment, it&#8217;s time to deploy the project to Vercel and test its functionality in a production environment.<\/p>\n\n\n\n<p>To begin, install the Vercel adaptor for SvelteKit and configure the project to use it. Open your terminal and run the following command to install the adaptor:<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\">\n<code>\n# For PNPM\npnpm add -D @sveltejs\/adapter-vercel\n\n# For Yarn\nyarn add -D @sveltejs\/adapter-vercel\n\n# For NPM\nnpm i -D @sveltejs\/adapter-vercel\n<\/code>\n<\/pre>\n\n\n\n<p>Once the Vercel adaptor is installed, modify the <code>svelte.config.js<\/code> file to use the Vercel adaptor. Replace <code>'@sveltejs\/adapter-auto'<\/code> with <code>'@sveltejs\/adapter-vercel'<\/code> in the import and adapter section, as shown below:<\/p>\n\n\n\n<p><strong>svelte.config.js<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-js\">\n<code>\nimport adapter from '@sveltejs\/adapter-vercel';\nimport { vitePreprocess } from '@sveltejs\/kit\/vite';\n\n\/** @type {import('@sveltejs\/kit').Config} *\/\nconst config = {\n\t\/\/ Consult https:\/\/kit.svelte.dev\/docs\/integrations#preprocessors\n\t\/\/ for more information about preprocessors\n\tpreprocess: [vitePreprocess({})],\n\n\tkit: {\n\t\t\/\/ adapter-auto only supports some environments, see https:\/\/kit.svelte.dev\/docs\/adapter-auto for a list.\n\t\t\/\/ If your environment is not supported or you settled on a specific environment, switch out the adapter.\n\t\t\/\/ See https:\/\/kit.svelte.dev\/docs\/adapters for more information about adapters.\n\t\tadapter: adapter()\n\t}\n};\n\nexport default config;\n<\/code>\n<\/pre>\n\n\n\n<p>Next, build the project in the development environment to simulate how Vercel will build the project. In your terminal, run the command <code>pnpm build<\/code>. Make sure you have Node v18 installed to avoid any build errors. After the project build is complete, a <code>.vercel<\/code> folder will be generated at the root level of the project. Add this folder to the <code>.gitignore<\/code> file to exclude it from commits. To preview the build, run the command <code>pnpm preview --port 3000<\/code> and access it in your browser.<\/p>\n\n\n\n<p>Now, modify the build script in the <code>package.json<\/code> file to generate the Prisma client, apply migrations to the database, and then build the project. Here&#8217;s how the modified build script should look:<\/p>\n\n\n\n<p><strong>package.json<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-json\">\n<code>\n{\n\t\"name\": \"sveltekit-form-actions-simple-app\",\n\t\"version\": \"0.0.1\",\n\t\"private\": true,\n\t\"scripts\": {\n\t\t\"dev\": \"prisma db push && prisma generate && vite dev\",\n\t\t\"build\": \"prisma db push && prisma generate && vite build\",\n\t\t\"preview\": \"vite preview\"\n\t},\n\t\"devDependencies\": {\n\t\n\t},\n\t\"type\": \"module\",\n\t\"dependencies\": {\n\t\t\n\t}\n}\n<\/code>\n<\/pre>\n\n\n\n<p>Next, deploy the project to Vercel. Create a GitHub repository for your project and push the source code into it. Then, sign in to your Vercel account and click on the &#8220;<strong>Add New<\/strong>&#8221; button. Select the &#8220;<strong>Project<\/strong>&#8221; option from the dropdown menu and choose the repository where you pushed the source code.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"822\" height=\"667\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/08\/Select-the-SvelteKit-Feedback-Application-Project-From-Vercel.webp\" alt=\"Select the SvelteKit Feedback Application Project From Vercel\" class=\"wp-image-11901\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/08\/Select-the-SvelteKit-Feedback-Application-Project-From-Vercel.webp 822w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/08\/Select-the-SvelteKit-Feedback-Application-Project-From-Vercel-300x243.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/08\/Select-the-SvelteKit-Feedback-Application-Project-From-Vercel-768x623.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/08\/Select-the-SvelteKit-Feedback-Application-Project-From-Vercel-100x81.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/08\/Select-the-SvelteKit-Feedback-Application-Project-From-Vercel-555x450.webp 555w\" sizes=\"auto, (max-width: 822px) 100vw, 822px\" \/><\/figure>\n\n\n\n<p>Vercel will recognize your project as a SvelteKit project and provide default configurations, which usually work well. Customize settings as needed, and add your cloud database URL as the value of the <code>DATABASE_URL<\/code> variable in the &#8220;<strong>Environment Variables<\/strong>&#8221; section.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"904\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/08\/Deploy-the-SvelteKit-Feedback-App-on-Vercel-1024x904.webp\" alt=\"Deploy the SvelteKit Feedback App on Vercel\" class=\"wp-image-11902\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/08\/Deploy-the-SvelteKit-Feedback-App-on-Vercel-1024x904.webp 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/08\/Deploy-the-SvelteKit-Feedback-App-on-Vercel-300x265.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/08\/Deploy-the-SvelteKit-Feedback-App-on-Vercel-768x678.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/08\/Deploy-the-SvelteKit-Feedback-App-on-Vercel-100x88.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/08\/Deploy-the-SvelteKit-Feedback-App-on-Vercel-509x450.webp 509w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/08\/Deploy-the-SvelteKit-Feedback-App-on-Vercel.webp 1045w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Click the &#8220;<strong>Deploy<\/strong>&#8221; button to start the deployment process. Vercel will handle the building of your SvelteKit project. Once the deployment is finished, you will be provided with a domain specifically for your project. Access this domain to see your SvelteKit app live in action. You can now add, edit, and delete feedback items.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>And we&#8217;ve reached the end! Congratulations on making it this far. Throughout this tutorial, you learned how to create a full-stack application in SvelteKit using Form Actions and successfully deploy it to Vercel. I hope you found this tutorial helpful and enjoyable.<\/p>\n\n\n\n<p>If you have any questions or feedback, don&#8217;t hesitate to leave a comment below. Your input is valuable, and I&#8217;ll be glad to assist you. Thank you for reading, and best of luck with your future SvelteKit projects! Happy coding!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this article, you will learn how to build a full-stack feedback application in SvelteKit using Form Actions and Prisma ORM. Using Form Actions will&#8230;<\/p>\n","protected":false},"author":1,"featured_media":11917,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[82],"tags":[83,91],"class_list":["post-11884","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-svelte","tag-svelte","tag-sveltekit"],"acf":[],"_links":{"self":[{"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/posts\/11884","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/comments?post=11884"}],"version-history":[{"count":4,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/posts\/11884\/revisions"}],"predecessor-version":[{"id":11921,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/posts\/11884\/revisions\/11921"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/media\/11917"}],"wp:attachment":[{"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/media?parent=11884"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/categories?post=11884"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/tags?post=11884"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}