Inertia.js bence SPA ve geleneksel web uygulama geliştirme arasında köprü kuran ve günümüzde geliştirme çözümü olarak bence en mantıklı çözümlerden birini sunan bir kütüphane. Modern geliştirme süreçlerinde SPA deneyimi sunmak isteyen ancak klasik backend araçlarını bırakmak istemeyenler için en mantıklı çözümü sağlıyor.
Inertia.js’in avantajlarına geçmeden önce, nasıl çalıştığını daha iyi anlayabilmek adına basit bir örnek üzerinden ilerlemek istiyorum.
Inertia.js Nasıl Çalışır?
Inertia.js, uygulamanıza gelen isteklerin Ajax (XHR/XmlHttpRequest) ile mi yapıldığını kontrol eder ve eğer belirli bir standart başlık (X-Inertia) içeriyorsa, sunucunun SPA frontend’ine uygun bir şekilde yanıt vermesini sağlar. Yani, normalde controller’dan view döndüren bir backend framework’ü (Rails, Laravel, Django) ile çalışırken, Inertia.js bu controller’dan JSON verisi döndürmesini sağlar. Bu JSON, frontend tarafında React, Vue, ya da Svelte gibi modern framework’lerde bileşenlerle işlenir.
Aşağıdaki örnekte, Rails kullanarak bu mantığı nasıl işleyebileceğimizi göstereceğim. Standart bir adaptör kullanmadan Inertia.js’in temel çalışma prensibini göstermeye çalışacağım. Ancak gerçek bir uygulamada adaptör olarak ‘inertia_rails’ gem’ini kullanmanız daha uygun olacaktır, çünkü anlatacağım şeyler sadece basit çalışma mantığını anlatmak için vereceğim örnekler olacak.
Bu örneği vermemdeki amaç, normalde herhangi bir backendle rahatlıkla kullanabileceğinizi göstermek. Rails, Laravel ve Django gibi popüler frameworkler için adaptörleri bulunmakta ve kullandığınız backende ait bir adaptör bulamadıysanız aşağıdaki örnekteki mantıkla kendiniz için bir adaptör oluşturabilirsiniz. Ben son zamanlarda Inertia.js’i Axum için basit bir adaptör hazılayarak kullanıyorum ve bunun örneğinide yazımın sonunda paylaşacağım.
Basit çalışma mantığı
Uygulamamımızın dahsboard’unun /dashboard
route’una sahip olduğunu ve bu routu’un app/controllers/dashboard_controller.rb
controller’ında show
metodu tarafından yönetildiğini varsayalım.
# app/controllers/dashboard_controller.rb
class DashboardController < ApplicationController
def show
# Örnek veri (props)
props = {
title: 'Dashboard',
user: {
name: 'John Doe',
email: 'john@example.com'
}
}
# Inertia isteği olup olmadığını kontrol et
if request.headers['X-Inertia']
# Inertia.js isteği ise, JSON olarak döndür
render json: {
component: 'Dashboard/Show',
props: props,
url: request.original_url
}
else
# Geleneksel Rails isteği ise, normal görünümü render et
@props = props
render template: 'layouts/spa'
end
end
end
Gördüğünüz gibi, controller’a gelen istediğin Inertia.js isteği olup olmadığını kontrol ediyor ve eğer öyleyse props nesnesini oluşturarak bir JSON render ediyor. Eğer istek Inertia.js isteği değilse, standart bir view render ediyor ve bu view gelen props nesnesini data-page attribute’u olarak ayarlıyor. Bu view, sunucudaki tüm route’larda normal isteklerde render edilecek view olarak ayarlayabiliriz. Aslında adaptörler, bu işlemi tüm controller’ımızda tekrarlamadan yapabilmemizi sağlıyor.
<!-- app/views/layouts/spa.html.erb -->
<!DOCTYPE html>
<html>
<head>
<title>My Inertia SPA</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
</head>
<body>
<div id="app" data-page="<%= @props.to_json %>"></div>
</body>
</html>
Inertia.js gem’ini kullanarak bunu yapmak isteseydik, aşağıdaki gibi yapı kullanabilirdik. Bu, yukarıdaki örneğe göre çok daha basit bir şekilde anlattığım adımları uygulamamızı sağlar.
def index
render inertia: 'Dashboard/Show', props: {
users: User.all,
}
end
Hatta sonraki versiyonlarda Rails standartlarını kullanabilmeniz için işi daha basit hale getirmişler.
class DashboardController < ApplicationController
use_inertia_instance_props
def index
@users = User.all
end
end
Avantajları nelerdir?
Yukarıdaki örnek üzerinden anlatacak olursak:
Backend routing
Artık routing kısmını SPA yerine Rails uygulaması üzerinden yapabiliriz. Bana göre, her zaman frontend routing daha karmaşık ve az yönetilebilir olmuştur. Rails ve Laravel gibi framework’lerde yıllardır kullanılan ve oturmuş bir routing yapısı bulunmakta. Özellikle günümüzde frontend için sık sık değişen routing düzenleri beni hep rahatsız etti. Hatta çoğu zaman yönetilebilirliği çok zor hale gelmekte.
Backend authentication ve session
Çoğu framework olgun bir authentication ve session yapısına sahiptir. Inertia.js ile geliştireceğiniz uygulamalarda bunları tamamen backend’e bırakabilirsiniz.
API geliştirmenize gerek yoktur
Bunu bir artı olarak ifade etmek gerekir mi tartışılır ama Inertia.js ile bir API geliştirmenize gerek kalmaz, fakat kullanıcılarınıza SPA deneyimi sunmaya devam edebilirsiniz.
Veri yönetimi basitleşir
Tüm veriler controller’lardan doğrudan frontend bileşenlerine geçer. Bu, hem API yazma ihtiyacını ortadan kaldırır hem de verilerin frontend’de kullanımını daha basit hale getirir.
Axum örneği
Yukarıda bahsettiğim Axum örneğini inceleyecek olursak:
Bu projede Axum ile birlikte React kullandım ve controller eğer Inertia.js isteği ise BookmarkCreate.tsx
dosyasını render ediyor.
//src/http/controllers/bookmarks_controllers.rs
use crate::{frontend::Inertia, validate_payload};
use axum::{
debug_handler,
extract::{Extension, Json, Path},
http::StatusCode,
response::IntoResponse,
};
pub async fn create_bookmark(inertia: Inertia) -> impl IntoResponse {
inertia.render("Bookmark/BookmarkCreate", ())
}
Aşağıda ise Inertia
struct’ına ait render implementasyonu görebilirsiniz. Aslında yukarıda çalışma mantığı olarak anlattığım şeylerin bir özeti niteliğinde. Özellikle Response
struct’ı implementasyonunda JSON ve HTML render koşulunu görebilirsiniz.
//src/frontend/inertia.rs
pub fn render<T: serde::Serialize + std::fmt::Debug>(
&self,
component: &'static str,
props: T,
) -> Response {
let request = self.request.clone().unwrap();
let page = Page {
component,
url: request.path.clone(),
version: self.version.clone(),
props: serde_json::to_value(props).unwrap_or_else(|_| serde_json::json!({})),
};
Response {
page,
request,
vite: self.vite.clone(),
}
}
impl IntoResponse for Response {
fn into_response(self) -> axum::response::Response {
let mut headers = HeaderMap::new();
headers.insert("X-Inertia", "true".parse().unwrap());
if self.request.is_xhr {
(headers, Json(self.page)).into_response()
} else {
Html(Self::html_page(&self)).into_response()
}
}
}