Integrasi Remix dengan Laravel: Membangun Vanilla Fetch API dengan TypeScript

Qisthi Ramadhani
Javan Cipta Solusi
Published in
4 min readJan 9, 2024

Apakah Anda pernah merasa frustrasi saat mencoba mengintegrasikan request HTTP di aplikasi TypeScript Anda, khususnya saat bekerja dengan framework Remix dan Laravel? Mungkin Anda telah mencoba berbagai third-party libraries seperti axios atau ofetch, tetapi selalu menemui hambatan. Saya sendiri pernah berada di posisi yang sama, menghadapi batasan dan komplikasi yang tidak terduga. Dalam perjalanan saya mencari solusi, saya menemukan inspirasi dalam ‘The Ultimate Guide to Building APIs & Single-page Applications’ oleh ServerSideUP, yang memberikan wawasan mendalam tentang pembuatan API yang efisien dan aplikasi single-page yang ultimate.

Berbekal pengetahuan ini, saya menemukan solusi yang tidak hanya efektif tetapi juga elegan: menggunakan Fetch API secara langsung dalam aplikasi TypeScript. Hari ini, saya ingin berbagi pengalaman dan pengetahuan saya dalam mengatasi tantangan ini, memberikan Anda panduan langkah demi langkah untuk memanfaatkan Fetch API secara maksimal dalam proyek Anda.

Untuk referensi lebih lanjut, silakan kunjungi The Ultimate Guide to Building APIs & Single-page Applications di ServerSideUP.

Photo by Lautaro Andreani on Unsplash

Dalam dunia pengembangan web modern, integrasi antar berbagai framework menjadi sangat penting. Kali ini, saya akan membahas bagaimana kita dapat mengintegrasikan framework Remix dengan Laravel menggunakan Fetch API kustom yang dibangun di TypeScript. Khususnya, kita akan melihat file laravelFetchers.ts, yang merupakan solusi efektif ketika tidak bisa menggunakan pustaka pihak ketiga di kode server.

Pendekatan Kustom dalam Fetch API

Fungsi `laraFetch`

Fungsi `laraFetch` adalah inti dari integrasi kita. Fungsi ini dirancang untuk mempermudah request HTTP dari Remix ke Laravel, dengan dukungan berbagai metode seperti GET, POST, PUT, PATCH, dan DELETE.

import { redirect } from "@remix-run/node";
import { authCookie } from "./auth";

type laraFetchOptions = {
method?: string;
body?: FormData | Record<string, any>;
};

export const CSRF_COOKIE = "XSRF-TOKEN",
CSRF_HEADER = "X-XSRF-TOKEN";

export default async function laraFetch<T>(
path: RequestInfo,
{ method = "get", body: requestBody }: laraFetchOptions,
request?: Request
): Promise<T> {
const { BE_URL } = process.env;
const isMutation = ["post", "put", "patch", "delete"].includes(
method.toLowerCase()
);

let token: string | null = null;
let cookieHeader = request?.headers.get("Cookie");

if (cookieHeader) {
const cookies = await authCookie.parse(cookieHeader);

token = cookies[CSRF_HEADER];
cookieHeader = cookies.Cookie;
}

if (isMutation && !cookieHeader) {
const responseCsrf = await fetch(`${BE_URL}/sanctum/csrf-cookie`, {
method: "GET",
});
const cookies = String(responseCsrf.headers.get("set-cookie"));
const laravelCookies = await parseCookie(cookies);

token = laravelCookies.XSRFToken;
cookieHeader = `laravel_session=${laravelCookies.laravelSession}; ${CSRF_COOKIE}=${token}`;
}

const headers = new Headers({
accept: "application/json",
Referer: "http://localhost:3000",
});

if (token && cookieHeader) {
headers.set("Cookie", cookieHeader);
headers.set(CSRF_HEADER, token);
}

let body = undefined;

if (requestBody instanceof FormData) {
headers.set("Content-Type", "application/json");
body = JSON.stringify(Object.fromEntries(requestBody));
}

const response = await fetch(`${BE_URL}${path}`, {
headers,
method: method.toUpperCase(),
body,
});

if ([500, 502, 503, 504, 505].includes(response.status)) {
throw new Error(`Laravel server error! Status: ${response.statusText}`)
}

return response as T;
}

Di sini, kita memastikan bahwa setiap request HTTP dilengkapi dengan token CSRF yang diperlukan, menjamin keamanan dalam pertukaran data antara kedua framework.

Pengelolaan Token CSRF

Aspek penting dari fungsi ini adalah pengelolaan token CSRF. Fungsi ini secara cerdas mengambil token dari cookie atau mengambilnya langsung dari server jika perlu. Ini memastikan bahwa setiap mutasi data yang dilakukan melalui request ini aman dan terlindungi.

Fungsi `laraReq`

Fungsi ini bertugas mengelola response dari request yang dibuat. Dengan `laraReq`, kita dapat dengan mudah menangani berbagai status HTTP, termasuk kasus-kasus seperti unauthorized (401) atau entity error (422).

export async function laraReq<T, K = Response>(
fetchable: Promise<T>,
onSuccess?: (param?: Response),
onUnauthorized?: (param?: Response) => K
): Promise<{ data: T | null; errors: Record<string, string[]> | null }> {
const response = await fetchable;

if (!(response instanceof Response)) {
throw new Error("Something went wrong");
}

if (response.status === 419) {
throw redirect("/login", {
headers: {
"Set-Cookie": await authCookie.serialize("", { maxAge: 0 }),
},
});
}

if (response.status === 401) {
await onUnauthorized?.(response);
}

let data: T | null = null;
let errors: Record<string, string[]> | null = null;

if ([422, 200].includes(response.status)) {
const json = await response.json();

if (response.status === 422) {
errors = json.errors;
} else {
data = json;
}
}

await onSuccess?.(response);

return { data, errors };
}

Melalui fungsi ini, kita dapat memproses data JSON dari response dan mengelola error yang mungkin terjadi dengan cara yang lebih terstruktur.

Fungsi `parseCookie`

Mengurai cookie dari response HTTP adalah tugas fungsi `parseCookie`. Fungsi ini memecah string `set-cookie` menjadi objek yang lebih mudah diolah, memungkinkan kita untuk mengakses nilai-nilai seperti token CSRF dengan lebih mudah.

export async function parseCookie(setCookie: string): Promise<{
XSRFToken: string;
laravelSession: string;
}> {
let XSRFToken = "",
laravelSession = "",
cookies = setCookie.split(",");

for (let index = 0; index < cookies.length; index++) {
let cookie = cookies[index];

if (cookie.includes(CSRF_COOKIE)) {
XSRFToken = await getCookie(CSRF_COOKIE, cookie);
}

if (cookie.includes("laravel_session")) {
laravelSession = await getCookie("laravel_session", cookie.split(";")[0]);
}
}

return {
XSRFToken,
laravelSession,
};
}

async function getCookie(name: string, cookieString: string): Promise<string> {
if (name === "laravel_session") {
return decodeURIComponent(cookieString.split("=")[1]);
}

let match = cookieString.match(new RegExp("(^|;\\s*)(" + name + ")=([^;]*)"));

return match ? decodeURIComponent(match[3]) : "";
}

Kesimpulan dan Implementasi

Dengan menggunakan kode ini, integrasi antara Remix dan Laravel menjadi lebih aman dan efisien. Kita tidak hanya mendapatkan kontrol penuh atas cara request dan response ditangani, tetapi juga memastikan bahwa aspek-aspek penting seperti keamanan CSRF terkelola dengan baik.

Untuk mengimplementasikan kode ini, cukup salin dan sesuaikan fungsi-fungsi tersebut sesuai dengan kebutuhan spesifik aplikasi Anda. Pendekatan kustom ini menawarkan fleksibilitas tinggi dan memungkinkan integrasi yang lancar antara Remix dan Laravel, bahkan tanpa menggunakan pustaka pihak ketiga di kode server.

Saya harap penjelasan ini memberikan wawasan baru dalam membangun Fetch API kustom untuk integrasi framework di dunia pengembangan web. Jika ada pertanyaan atau diskusi lebih lanjut, jangan ragu untuk meninggalkan komentar di bawah. Happy coding!

--

--