Bosia Bosia v1.2.9

Middleware Hooks

Intersep setiap request dengan hooks.server.ts, susun handler dengan sequence().

Middleware hook memungkinkan Anda menjalankan kode pada setiap request — autentikasi, pencatatan log, injeksi header, dan lainnya.

hooks.server.ts

Buat src/hooks.server.ts dan ekspor fungsi handle:

import type { Handle } from "bosia";

export const handle: Handle = async ({ event, resolve }) => {
	// Runs before the route handler
	event.locals.requestTime = Date.now();

	const response = await resolve(event);

	// Runs after the route handler
	response.headers.set("X-Custom", "value");

	return response;
};

Fungsi handle mengintersepsi setiap request halaman dan API. File statis (chunk JS/CSS, file public/) dilayani sebelum hooks berjalan, sehingga pencarian sesi di handle tidak berjalan sekali per aset. Request file yang tidak ditemukan tetap melewati hooks sebelum 404.

event.params sudah berisi params dari route yang cocok saat handle berjalan.

Tipe Handle

type Handle = (input: { event: RequestEvent; resolve: ResolveFunction }) => MaybePromise<Response>;
  • event — event request dengan request, url, params, route, locals, cookies
  • resolve — panggil ini untuk melanjutkan ke handler berikutnya atau ke route

Menyusun dengan sequence()

Gunakan sequence() untuk menyusun beberapa handler:

import { sequence } from "bosia";
import type { Handle } from "bosia";

const authHandle: Handle = async ({ event, resolve }) => {
	event.locals.requestTime = Date.now();
	event.locals.user = null; // replace with real session logic
	return resolve(event);
};

const langHandle: Handle = async ({ event, resolve }) => {
	event.locals.lang = event.request.headers.get("accept-language")?.split(",")[0] ?? "en";
	return resolve(event);
};

export const handle = sequence(authHandle, langHandle);

Handler dieksekusi dari kiri ke kanan. resolve milik setiap handler memanggil handler berikutnya dalam rantai.

Access log: jangan console.log setiap request dari hook. Saat stdout berupa pipe (docker logs, systemd) tiap baris adalah satu syscall, ~11–14µs CPU per request. Serahkan access log ke reverse proxy (nginx, Caddy), atau pasang plugin accessLog() yang menulis secara batch.

Menyetel Locals

event.locals adalah objek biasa yang dibagikan di antara hooks, loader, dan handler API untuk request saat ini:

// hooks.server.ts
const auth: Handle = async ({ event, resolve }) => {
	const session = getSession(event.cookies.get("session_id"));
	event.locals.user = session?.user ?? null;
	return resolve(event);
};
// +page.server.ts — locals are available here
export async function load({ locals }: LoadEvent) {
	return { user: locals.user };
}

Baca dan tulis cookie melalui event.cookies:

const handle: Handle = async ({ event, resolve }) => {
	// Read
	const token = event.cookies.get("auth_token");

	// Write (added to the response)
	event.cookies.set("visited", "true", {
		path: "/",
		httpOnly: true,
		maxAge: 60 * 60 * 24, // 1 day
	});

	// Delete
	event.cookies.delete("old_cookie"); // mengikuti path bawaan yang dipakai set()

	return resolve(event);
};

Pola Umum

Autentikasi

const auth: Handle = async ({ event, resolve }) => {
	const token = event.cookies.get("session");
	event.locals.user = token ? await validateSession(token) : null;
	return resolve(event);
};

Pencatatan Log Request

const logger: Handle = async ({ event, resolve }) => {
	const start = Date.now();
	const res = await resolve(event);
	console.log(
		`${event.request.method} ${event.url.pathname} → ${res.status} (${Date.now() - start}ms)`,
	);
	return res;
};

Proteksi Route

import { redirect } from "bosia";

const guard: Handle = async ({ event, resolve }) => {
	if (event.route.id?.startsWith("/(private)") && !event.locals.user) {
		throw redirect(303, "/login");
	}
	return resolve(event);
};

event.route.id adalah folder route yang cocok di bawah src/routes, grup tetap disertakan: halaman di src/routes/(private)/dashboard/[id]/+page.svelte punya id /(private)/dashboard/[id]. Taruh halaman yang butuh login di bawah (private) dan satu pengecekan ini melindungi semuanya, termasuk halaman yang ditambahkan nanti. Nilainya null jika tidak ada route yang cocok.

Satu pengecekan menutup semua cara masuk ke halaman: muat halaman penuh, navigasi sisi klien (router mengambil data loader halaman, dan event.route adalah route halaman tersebut), dan POST form action. event.isDataRequest membedakan navigasi kalau kamu memang perlu tahu; otorisasi seharusnya tidak peduli. Lihat Keamanan › Penjaga Rute di Hook.

throw redirect() lebih disarankan daripada mengembalikan Response.redirect(...): bosia mengubahnya menjadi bentuk yang tepat untuk kedua jenis permintaan, dan BASE_PATH diterapkan otomatis. Response.redirect yang di-return tetap bekerja, tapi Location-nya diteruskan apa adanya — di bawah base path, prefiksnya harus kamu tulis sendiri.

throw error(404, "…") bekerja dengan pola yang sama: halaman error untuk permintaan dokumen, error boundary router untuk navigasi klien.