Next.js: Fullstack dalam Satu Proyek · 5/9
Mengubah data dengan Server Action
Function yang jalan di server, dipanggil dari sebuah form. Plus validasi yang tidak boleh kamu lewatkan.
Baca 32 menit
Setelah pelajaran ini kamu bisa
- Menulis Server Action dan memanggilnya dari sebuah form
- Menjelaskan kenapa setiap action harus melakukan authorization sendiri
- Memvalidasi input di batasnya
- Melakukan revalidate dan redirect setelah mutasi
Server Action — dokumentasinya juga menyebutnya Server Function — adalah function async yang ditandai "use server", jalan di server, dan bisa dipanggil dari client. Ia menghilangkan kebutuhan menulis route API untuk form milikmu sendiri.
// features/venue/actions.ts
"use server"; // marks EVERY export in this file
import { redirect } from "next/navigation";
import { revalidatePath } from "next/cache";
import { getSessionUser } from "@/features/auth/session";
export async function createVenue(formData: FormData) {
// 1. AUTHORISE. Always. See the warning below.
const user = await getSessionUser();
if (!user) return { error: "notSignedIn" };
if (user.role !== "lead") return { error: "notAllowed" };
// 2. VALIDATE. Everything in formData came from the browser.
const name = formData.get("name");
if (typeof name !== "string" || name.trim().length < 2) {
return { error: "nameTooShort" };
}
// 3. Do the work.
const venue = await insertVenue({ name: name.trim(), ownerId: user.id });
// 4. Invalidate what is now stale, then navigate.
revalidatePath("/venues");
redirect("/venues/" + venue.id);
}Dua action, fitur yang sama. Satu memercayai pemanggilnya; satunya tidak. Lihat apa yang dicapai request palsu terhadap masing-masingnya.
Hasil
Tekan Jalankan untuk melihat hasilnya.
Ini jalan di browser kamu, di dalam sandbox. Apa pun yang kamu tulis di sini tidak bisa merusak situs.
Perhatikan versi aman mengambil identitas dari session, bukan dari payload. Action yang menerima argument userId lalu memercayainya adalah IDOR — siapa pun bisa mengirim id orang lain. Ini persis alasan getMyProgressAction di proyek ini tidak menerima parameter sama sekali.
Memanggil sebuah action
// 1. Directly as a form action. Works without JavaScript.
<form action={createVenue}>
<input name="name" />
<button type="submit">Create</button>
</form>
// 2. With useActionState, for errors and a pending flag.
"use client";
const [result, formAction, pending] = useActionState(createVenue, undefined);
<form action={formAction}>...</form>
// 3. Called from an event handler, for non-form mutations.
"use client";
function DeleteButton({ id }) {
const [pending, startTransition] = useTransition();
return (
<button
disabled={pending}
onClick={() => startTransition(() => deleteVenue(id))}
>
Delete
</button>
);
}Setelah mutasi
| Pemanggilan | Fungsinya |
|---|---|
revalidatePath("/venues") | Menandai data cache path itu jadi usang |
revalidateTag("venues") | Menginvalidasi berdasarkan tag, stale-while-revalidate |
updateTag("venues") | Kedaluwarsa segera, jadi pengguna melihat hasil tulisannya sendiri. Hanya di action. |
redirect("/venues/1") | Berpindah halaman. Ia melempar, jadi tidak ada yang jalan setelahnya. |
return { error } | Mengirim sebuah nilai kembali untuk ditampilkan form-nya |
Tugas praktik
Di proyek ini, buka features/progress/actions.ts. Perhatikan setLessonCompleteAction membaca user dari session dan memvalidasi id pelajaran dengan isKnownLessonId sebelum menulis. Sekarang coba rusak: panggil dengan id pelajaran yang tidak ada dan pastikan tidak ada yang tertulis. Lalu baca komentar di atas getMyProgressAction yang menjelaskan kenapa ia tidak menerima id user.
Hasil yang diharapkan
Id yang tidak dikenal diabaikan, dan progres yang ada dikembalikan tanpa perubahan.