Di SvelteKit, kita tidak selalu perlu membuat endpoint API manual hanya untuk menangani submit form sederhana seperti login, tambah komentar, atau update profil. Framework ini menyediakan form actions, yaitu mekanisme untuk menerima request POST langsung di file +page.server.js pada route yang sama dengan halamannya.

Pendekatan ini sangat cocok untuk pemula karena alurnya lebih ringkas: form HTML dikirim ke halaman yang sama, server membaca request.formData(), melakukan validasi, lalu mengembalikan data error atau sukses ke komponen halaman. Hasilnya, kita bisa membangun form yang tetap sederhana, server-side, dan mudah dirawat.

Artikel ini akan membahas contoh end-to-end menggunakan form login. Pola yang sama bisa diterapkan untuk form komentar, kontak, registrasi, dan kebutuhan CRUD sederhana lainnya.

Mengapa memakai SvelteKit Actions?

Jika Anda berasal dari pendekatan SPA atau backend REST tradisional, mungkin Anda terbiasa dengan alur seperti ini:

  1. Buat form di frontend.
  2. Buat endpoint API terpisah, misalnya /api/login.
  3. Kirim fetch POST dari browser.
  4. Parse body di endpoint.
  5. Kirim response JSON.
  6. Tampilkan error atau sukses di frontend.

Di SvelteKit, untuk banyak kasus, alur itu bisa disederhanakan. Dengan actions:

  • Form tetap memakai HTML biasa.
  • Submit menuju route yang sama.
  • Logika server ditulis di +page.server.js.
  • Hasil validasi dikembalikan ke halaman melalui properti form.

Keuntungan utamanya:

  • Lebih sedikit boilerplate karena tidak perlu endpoint API manual untuk kasus sederhana.
  • Progressive enhancement: form HTML tetap berfungsi walau tanpa JavaScript tambahan.
  • Logika lebih dekat dengan halaman, sehingga route dan handling form mudah dilacak.

Namun, actions bukan berarti menggantikan semua API. Jika Anda butuh endpoint untuk mobile app, integrasi pihak ketiga, atau akses lintas halaman secara programatik, endpoint API tetap relevan.

Struktur file yang akan dipakai

Untuk contoh login sederhana, kita akan memakai struktur berikut:

src/routes/login/
├── +page.svelte
└── +page.server.js

+page.svelte berisi tampilan form. +page.server.js berisi action yang memproses request POST.

Membuat form login di +page.svelte

Berikut halaman login dasar. Form ini mengirimkan method POST ke route yang sama.

<script>
  export let form;
</script>

<h2>Login</h2>

{#if form?.message}
  <p style="color: green;">{form.message}</p>
{/if}

{#if form?.error}
  <p style="color: red;">{form.error}</p>
{/if}

<form method="POST">
  <div>
    <label for="email">Email</label>
    <input
      id="email"
      name="email"
      type="email"
      value={form?.values?.email ?? ''}
      required
    />
  </div>

  <div>
    <label for="password">Password</label>
    <input
      id="password"
      name="password"
      type="password"
      required
    />
  </div>

  <button type="submit">Masuk</button>
</form>

Beberapa hal penting dari contoh di atas:

  • export let form; dipakai untuk menerima data hasil action dari server.
  • method="POST" memberi tahu browser bahwa form harus dikirim sebagai POST.
  • Nilai email dikembalikan ke input memakai form?.values?.email agar pengguna tidak perlu mengetik ulang jika validasi gagal.

Secara default, form akan submit ke URL halaman saat ini. Karena kita berada di route /login, maka request dikirim ke route yang sama.

Menangani submit POST di +page.server.js

Sekarang kita buat file server-nya. Di sinilah SvelteKit action didefinisikan.

import { fail } from '@sveltejs/kit';

export const actions = {
  default: async ({ request }) => {
    const data = await request.formData();

    const email = data.get('email');
    const password = data.get('password');

    if (!email || !password) {
      return fail(400, {
        error: 'Email dan password wajib diisi.',
        values: { email }
      });
    }

    if (typeof email !== 'string' || typeof password !== 'string') {
      return fail(400, {
        error: 'Input form tidak valid.',
        values: { email: '' }
      });
    }

    if (!email.includes('@')) {
      return fail(400, {
        error: 'Format email tidak valid.',
        values: { email }
      });
    }

    if (password.length < 6) {
      return fail(400, {
        error: 'Password minimal 6 karakter.',
        values: { email }
      });
    }

    const user = {
      id: 1,
      email: 'admin@example.com',
      password: 'rahasia123'
    };

    if (email !== user.email || password !== user.password) {
      return fail(401, {
        error: 'Email atau password salah.',
        values: { email }
      });
    }

    return {
      message: 'Login berhasil.'
    };
  }
};

Mari pecah alurnya:

1. Mendefinisikan actions

actions adalah object yang berisi satu atau lebih action. Pada contoh ini kita memakai action default, artinya submit form biasa akan diarahkan ke action tersebut.

2. Membaca data form

await request.formData() membaca body request POST bertipe form. Hasilnya adalah objek FormData, lalu field dibaca dengan data.get('email') dan data.get('password').

Perlu diingat, FormData.get() bisa menghasilkan string, File, atau null. Karena itu validasi tipe tetap penting, walau field tampak sederhana.

3. Validasi input

Contoh di atas melakukan validasi dasar:

  • Field wajib diisi.
  • Email harus punya format minimal yang masuk akal.
  • Password harus memenuhi panjang minimum.

Jika validasi gagal, kita memakai fail(status, data). Fungsi ini mengembalikan response gagal yang datanya tetap bisa diakses oleh halaman melalui variabel form.

4. Mengembalikan hasil ke halaman

Saat gagal, kita kirim:

return fail(400, {
  error: 'Format email tidak valid.',
  values: { email }
});

Lalu di +page.svelte, nilai ini tersedia sebagai form.error dan form.values.email. Saat sukses, kita bisa mengembalikan object biasa:

return {
  message: 'Login berhasil.'
};

Nilai itu kemudian muncul sebagai form.message.

Alur end-to-end yang sebenarnya terjadi

Berikut urutan proses saat pengguna menekan tombol submit:

  1. Browser mengirim POST dari form ke route /login.
  2. SvelteKit menjalankan action di +page.server.js.
  3. Server membaca formData dari request.
  4. Server memvalidasi input.
  5. Jika gagal, server mengembalikan fail(...).
  6. Jika sukses, server mengembalikan data sukses atau melakukan redirect.
  7. Halaman dirender ulang dengan properti form berisi hasil action.

Karena alur ini berbasis server, kita tidak perlu menulis fetch('/api/login') sendiri untuk kasus sederhana. Itulah inti dari “tanpa API manual”.

Contoh variasi: tambah komentar

Pola yang sama bisa dipakai untuk form komentar. Misalnya di route artikel, pengguna mengirim nama dan isi komentar.

import { fail } from '@sveltejs/kit';

export const actions = {
  default: async ({ request }) => {
    const data = await request.formData();
    const name = data.get('name');
    const comment = data.get('comment');

    if (typeof name !== 'string' || typeof comment !== 'string') {
      return fail(400, {
        error: 'Data form tidak valid.'
      });
    }

    if (!name.trim() || !comment.trim()) {
      return fail(400, {
        error: 'Nama dan komentar wajib diisi.',
        values: { name, comment }
      });
    }

    if (comment.length > 500) {
      return fail(400, {
        error: 'Komentar terlalu panjang.',
        values: { name, comment }
      });
    }

    // Simpan ke database di sini

    return {
      message: 'Komentar berhasil ditambahkan.'
    };
  }
};

Intinya tetap sama: baca formData, validasi, simpan ke database jika valid, lalu kirim hasil kembali ke halaman.

Best practice untuk pemula

Validasi tetap harus dilakukan di server

Walau Anda bisa menambahkan atribut HTML seperti required, minlength, atau type="email", jangan anggap itu cukup. Validasi browser hanya membantu pengalaman pengguna, bukan keamanan. Request tetap bisa dimanipulasi dari luar browser.

Kembalikan nilai input yang aman

Jika validasi gagal, biasanya nyaman untuk mengembalikan field yang sudah diisi pengguna, misalnya email atau nama. Tapi hati-hati dengan data sensitif seperti password. Umumnya password tidak perlu dikirim balik ke halaman.

Gunakan status code yang sesuai

fail(400, ...) cocok untuk input tidak valid, sedangkan fail(401, ...) bisa dipakai untuk kredensial salah. Ini membantu debugging dan memperjelas jenis kegagalan.

Simpan logika berat di layer terpisah

Action sebaiknya menangani input/output request. Jika validasi atau query database makin kompleks, pindahkan ke helper atau service agar file route tetap rapi.

Kesalahan umum yang sering terjadi

  • Lupa memakai method="POST", sehingga form dikirim sebagai GET.
  • Salah nama field antara atribut name di input dan data.get(...) di server.
  • Menganggap FormData.get() selalu string, padahal bisa null atau File.
  • Tidak memakai fail() untuk error validasi, sehingga data error tidak mengalir rapi ke form.
  • Mengembalikan password ke client saat validasi gagal.

Debugging tips

Jika form terasa tidak bekerja sebagaimana mestinya, periksa beberapa hal berikut:

  • Cek tab Network di browser: pastikan request benar-benar POST ke URL yang diinginkan.
  • Tambahkan logging sementara di server: misalnya log hasil data.get('email') untuk memastikan field terbaca.
  • Pastikan file berada di route yang benar: action untuk halaman harus diletakkan di +page.server.js.
  • Pastikan nama field konsisten antara form dan server.

Jika Anda mulai butuh alur setelah sukses, misalnya pindah ke dashboard setelah login, biasanya langkah berikutnya adalah menambahkan redirect dari action. Untuk tutorial pemula ini, kita fokus dulu pada pola dasar kirim data, validasi, dan menampilkan hasil di halaman yang sama.

Kapan actions cukup, dan kapan perlu API terpisah?

Pakai SvelteKit actions jika:

  • Form terkait erat dengan satu halaman.
  • Anda ingin alur HTML form yang sederhana.
  • Anda tidak perlu endpoint publik untuk klien lain.

Pakai endpoint API terpisah jika:

  • Data harus diakses oleh aplikasi mobile atau service lain.
  • Anda butuh interface JSON yang dipanggil dari banyak tempat.
  • Alur frontend Anda memang berbasis AJAX/fetch yang kompleks.

Untuk pemula, actions sering menjadi pilihan terbaik karena lebih mudah dipahami dan lebih sedikit kode yang harus ditulis.

Penutup

SvelteKit actions memberi cara yang rapi untuk menangani submit form POST tanpa membuat endpoint API manual. Dengan menulis action di +page.server.js, Anda bisa membaca request.formData(), memvalidasi input, lalu mengembalikan error atau sukses langsung ke halaman melalui form.

Pola ini sangat pas untuk kebutuhan seperti login, tambah komentar, contact form, atau form sederhana lain yang terkait langsung dengan route tertentu. Setelah memahami dasar ini, Anda bisa melanjutkan ke topik lanjutan seperti redirect setelah sukses, penyimpanan session/cookie, integrasi database, dan progressive enhancement yang lebih halus.

Jika tujuan Anda adalah membangun form server-side yang bersih, aman, dan minim boilerplate, maka form actions adalah salah satu fitur SvelteKit yang paling praktis untuk dipelajari lebih dulu.