Bosia Bosia v0.9.5

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 menginterspsi setiap request — halaman, API route, dan aset statis.

Tipe Handle

type Handle = (input: { event: RequestEvent; resolve: ResolveFunction }) => MaybePromise<Response>;
  • event — event request dengan request, url, params, 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 loggingHandle: Handle = async ({ event, resolve }) => {
	const start = Date.now();
	const res = await resolve(event);
	const ms = Date.now() - start;
	console.log(`[${event.request.method}] ${event.url.pathname} ${res.status} (${ms}ms)`);
	res.headers.set("X-Response-Time", `${ms}ms`);
	return res;
};

export const handle = sequence(authHandle, loggingHandle);

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

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.url.pathname.startsWith("/admin") && !event.locals.user) {
		throw redirect(303, "/login");
	}
	return resolve(event);
};

Satu pengecekan menutup kedua cara halaman diakses. Saat router klien bernavigasi, yang diambil adalah data loader halaman, bukan dokumennya — tapi event.url tetap URL halaman: /admin, bukan path transport internal. event.isDataRequest membedakan keduanya kalau kamu memang perlu tahu; otorisasi seharusnya tidak peduli.

⚠️ Sebelum 0.9.5 pengecekan ini tidak berjalan pada navigasi klien. Guard yang membaca event.url.pathname melihat path transport internal saat sebuah link diklik, bukan /admin — jadi route yang "diproteksi" begini tetap mengirim data loader-nya ke pengunjung yang belum login. Kalau kamu menyalin contoh versi lama, naikkan versinya — tidak ada yang perlu ditambal di sisi aplikasimu.

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.