Laravel + Blade (Hybrid Mount)
React (Vite) – Full Folder Structure + Full Code
Rust API – Full Folder Structure + Full Code (Axum + SQLx)
Rust as a separate API service (recommended)
Below is a complete, step-by-step, no-missing-parts setup for Hybrid SSR (Laravel Blade page-1) + React UI + Rust API (page-2+) with a clean folder structure for both React (inside Laravel Vite) and Rust (Axum + SQLx).
I’m assuming:
Laravel is serving the Home page Blade.
React is bundled via Vite.
Rust will serve: GET /api/home/vehicles?page=2&per_page=9
Images live at: /storage/vechile_image/{file}
Laravel + Blade (Hybrid Mount)
A) Laravel + Blade (Hybrid Mount)
A1) .env (Laravel)
VEHICLES_API_BASE=https://rust-api.yourdomain.com
A2) config/app.php
Add this inside the returned array:
'vehicles_api_base' => env('VEHICLES_API_BASE', ''),
A3) Controller (THIS WAS MISSING)
Create a controller method that:
Calls Rust API for page=1
Converts response into LengthAwarePaginator
Passes $data to Blade
✅ app/Http/Controllers/HomeController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\Http;
class HomeController extends Controller
{
public function index(Request $request)
{
$apiBase = rtrim(config('app.vehicles_api_base') ?? '', '/');
$endpoint = '/api/home/vehicles';
$page = 1; // SSR loads page 1
$perPage = 9;
$items = [];
$total = 0;
$currentPage = 1;
$lastPage = 1;
$res = Http::acceptJson()
->timeout(10)
->get($apiBase . $endpoint, [
'page' => $page,
'per_page' => $perPage,
]);
if ($res->ok()) {
$json = $res->json();
// expected Rust shape:
// { ok: true, data: [...], pagination: { current_page, last_page, per_page, total } }
if (($json['ok'] ?? false) === true) {
$items = $json['data'] ?? [];
$pg = $json['pagination'] ?? [];
$currentPage = (int)($pg['current_page'] ?? 1);
$lastPage = (int)($pg['last_page'] ?? 1);
$perPage = (int)($pg['per_page'] ?? $perPage);
$total = (int)($pg['total'] ?? count($items));
}
}
// Convert to Laravel paginator so Blade can call items(), currentPage(), lastPage()
$data = new LengthAwarePaginator(
$items,
$total,
$perPage,
$currentPage,
[
'path' => $request->url(),
'query' => $request->query(),
]
);
return view('home', compact('data'));
}
}
A4) Route
✅ routes/web.php
use App\Http\Controllers\HomeController;
Route::get('/', [HomeController::class, 'index']);
A5) Blade file (home/welcome/dashboard)
✅ Now your Blade is correct (because $data exists):
@php
$apiBase = config('app.vehicles_api_base');
$apiBase = $apiBase ? rtrim($apiBase, '/') : '';
@endphp
<div id="HomeVehiclesSection"
data-api-base="{{ $apiBase }}"
data-endpoint="/api/home/vehicles"
data-initial='@json($data->items())'
data-page="{{ $data->currentPage() }}"
data-last-page="{{ $data->lastPage() }}">
</div>
@vite('resources/js/appss.jsx')
✅ This gives React:
initial vehicles (data-initial)
rust base URL (data-api-base)
endpoint (data-endpoint)
✅ B)
React (Vite) – Full Folder Structure + Full Code
B1) Folder structure (inside Laravel)
resources/js/appss.jsx (mount)
import React from "react";
import { createRoot } from "react-dom/client";
import HomeVehiclesSection from "./components/HomeVehiclesSection/HomeVehiclesSection";
document.addEventListener("DOMContentLoaded", () => {
const el = document.getElementById("HomeVehiclesSection");
if (!el) return;
const props = {
apiBase: el.dataset.apiBase || "",
endpoint: el.dataset.endpoint || "/api/home/vehicles",
initialData: el.dataset.initial ? JSON.parse(el.dataset.initial) : [],
initialPage: parseInt(el.dataset.page || "1", 10),
initialLastPage: parseInt(el.dataset.lastPage || "1", 10),
};
if (!el.__reactRoot) {
const root = createRoot(el);
root.render(<HomeVehiclesSection {...props} />);
el.__reactRoot = root;
}
});
B3) resources/js/components/utils/slug.js
export function slugify(s = "") {
return s
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "")
.replace(/[^a-zA-Z0-9\s-]/g, "")
.trim()
.replace(/\s+/g, "-")
.toLowerCase();
}
B4) resources/js/components/HomeVehiclesSection/index.js
export { default } from "./HomeVehiclesSection";
B5) resources/js/components/HomeVehiclesSection/HomeVehiclesSection.jsx
✅ Hybrid logic:
Page-1 uses SSR initialData
Page-2+ loads from Rust API using Load More
Prevents missing/blank UI
import React, { useMemo, useState } from "react";
import VehicleCard from "./VehicleCard";
export default function HomeVehiclesSection({
apiBase,
endpoint,
initialData,
initialPage,
initialLastPage,
}) {
const [items, setItems] = useState(Array.isArray(initialData) ? initialData : []);
const [page, setPage] = useState(Number(initialPage || 1));
const [lastPage, setLastPage] = useState(Number(initialLastPage || 1));
const [loading, setLoading] = useState(false);
const [err, setErr] = useState("");
const apiUrl = useMemo(() => {
const base = (apiBase || "").replace(/\/$/, "");
return base ? `${base}${endpoint}` : endpoint;
}, [apiBase, endpoint]);
const loadMore = async () => {
if (loading) return;
if (page >= lastPage) return;
setLoading(true);
setErr("");
try {
const nextPage = page + 1;
const res = await fetch(`${apiUrl}?page=${nextPage}&per_page=9`, {
headers: { Accept: "application/json" },
});
const json = await res.json();
if (!res.ok || !json?.ok) throw new Error(json?.message || "API failed");
setItems((prev) => [...prev, ...(json.data || [])]);
setPage(json.pagination?.current_page || nextPage);
setLastPage(json.pagination?.last_page || lastPage);
} catch (e) {
setErr(e?.message || "Failed to load vehicles");
} finally {
setLoading(false);
}
};
return (
<section className="section-padding gray-bg" style={{ marginTop: 25 }}>
<div className="containerrrr">
<div className="vehicle-grid">
{items.map((v) => (
<VehicleCard key={v.vechicle_id} v={v} />
))}
</div>
{err && (
<div className="text-center" style={{ marginTop: 18, color: "#b91c1c" }}>
{err}
</div>
)}
{page < lastPage && (
<div className="text-center" style={{ marginTop: 25 }}>
<button className="btn btn-primary" disabled={loading} onClick={loadMore}>
{loading ? "Loading..." : "Load More Vehicles"}
</button>
</div>
)}
</div>
</section>
);
}
B6) resources/js/components/HomeVehiclesSection/VehicleSlider.jsx
✅ React slider (no jQuery). Supports partner/default badge.
import React, { useEffect, useRef, useState } from "react";
export default function VehicleSlider({ images = [], intervalMs = 3000 }) {
const [idx, setIdx] = useState(0);
const timer = useRef(null);
const hasMany = images.length > 1;
const currentSrc = (images[idx]?.src || "default").toLowerCase();
const stop = () => {
if (timer.current) clearInterval(timer.current);
timer.current = null;
};
const start = () => {
stop();
if (!hasMany) return;
timer.current = setInterval(() => {
setIdx((p) => (p + 1) % images.length);
}, intervalMs);
};
useEffect(() => {
start();
return () => stop();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [images.length]);
return (
<div className="veh-slider" data-index={idx}
onMouseEnter={stop}
onMouseLeave={start}
onTouchStart={stop}
onTouchEnd={start}>
<div className={`veh-photo-badge ${currentSrc === "partner" ? "is-partner" : "is-default"}`}>
<span className="dot"></span>
<span className="txt"></span>
</div>
<div className="veh-slides" style={{ transform: `translateX(-${idx * 100}%)` }}>
{images.map((img, i) => (
<div className="veh-slide" key={i} data-source={img.src}>
<img
src={img.url}
className="vehicle-card-img"
alt="vehicle"
loading="lazy"
/>
</div>
))}
</div>
{hasMany && (
<>
<button className="veh-nav veh-prev" type="button"
onClick={(e) => { e.preventDefault(); e.stopPropagation(); setIdx((p) => (p - 1 + images.length) % images.length); start(); }}>
‹
</button>
<button className="veh-nav veh-next" type="button"
onClick={(e) => { e.preventDefault(); e.stopPropagation(); setIdx((p) => (p + 1) % images.length); start(); }}>
›
</button>
<div className="veh-dots" aria-hidden="true">
{images.map((_, i) => (
<button key={i} type="button"
className={`veh-dot ${i === idx ? "is-active" : ""}`}
onClick={(e) => { e.preventDefault(); e.stopPropagation(); setIdx(i); start(); }} />
))}
</div>
</>
)}
</div>
);
}
B7) resources/js/components/HomeVehiclesSection/VehicleCard.jsx
✅ Uses your same CSS classes.
import React from "react";
import VehicleSlider from "./VehicleSlider";
import { slugify } from "../utils/slug";
export default function VehicleCard({ v }) {
const images = Array.isArray(v.images_meta) && v.images_meta.length
? v.images_meta
: [{
src: "default",
file: "",
url: "https://bd.gaadicdn.com/processedimages/yamaha/mt-15-2-0/source/mt-15-2-06613f885e681c.jpg",
}];
const vehicleLower = String(v.vehical || "").trim().toLowerCase();
const cityHref = `/${slugify(v.city || "")}/${
vehicleLower === "car" ? "car-rentals" :
vehicleLower === "bike" ? "bike-rentals" :
"bicycle-rentals"
}`;
const brandHref = `/${slugify(vehicleLower)}s/${slugify(v.brand || "")}`;
return (
<div className="col-list-3">
<div className="vehicle-card">
<div className="vehicle-image">
<a href={v.show_url}>
<VehicleSlider images={images} />
</a>
<div className="vehicle-specs">
<ul>
<li><i className="fa fa-car"></i> Vehicle Type: <span>{v.vehical}</span></li>
<li><i className="fa fa-car"></i> Model: <span>{v.model}</span></li>
</ul>
</div>
</div>
<div className="rating-badge-container">
<div className="rating-badge">
{v.rating_10 && v.comment_count ? (
<a href={v.review_url} className="rating-link" style={{ textDecoration: "none" }}>
<i className="fa fa-star"></i>
<span className="rating-score">{Number(v.rating_10).toFixed(1)}/10</span>
<span className="rating-votes">
{Number(v.rating_count || 0)} Ratings • {Number(v.comment_count || 0)} Comments
</span>
</a>
) : (
<span className="no-rating">No ratings yet</span>
)}
</div>
<a href={v.shop_url} className="view-shop-link">View Shop & Reviews</a>
</div>
<div className="vehicle-title">
<h6>
Brand:{" "}
<a href={brandHref} className="brand-link">
{v.brand}
</a>
</h6>
<span className="price">{v.price} {v.currency_code || ""}/Day</span>
</div>
<div className="vehicle-footer">
<div className="location">
<p>City: <a href={cityHref} className="city-link">{v.city}</a></p>
</div>
<div className="booking-action">
<a href={v.booking_url}>
<button className="book-btn">Book</button>
</a>
</div>
</div>
</div>
</div>
);
}
B8) Build
npm install
npm run dev
# or production:
npm run build
Rust API – Full Folder Structure + Full Code (Axum + SQLx)
C1) Rust folder structure
C2) Cargo.toml
[package]
name = "motoshare_api"
version = "0.1.0"
edition = "2021"
[dependencies]
axum = "0.7"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
dotenvy = "0.15"
sqlx = { version = "0.7", features = ["runtime-tokio", "mysql", "macros"] }
tower-http = { version = "0.5", features = ["cors", "compression-gzip"]
}
C3) .env (Rust)
DATABASE_URL=mysql://user:pass@127.0.0.1:3306/motoshare
PUBLIC_BASE_URL=https://motoshare.in
ASSET_BASE_URL=https://motoshare.in
PORT=8080
ALLOWED_ORIGIN=https://motoshare.in
C4) src/config.rs
use std::env;
#[derive(Clone)]
pub struct AppConfig {
pub database_url: String,
pub public_base_url: String,
pub asset_base_url: String,
pub allowed_origin: String,
}
impl AppConfig {
pub fn from_env() -> Self {
Self {
database_url: env::var("DATABASE_URL").expect("DATABASE_URL missing"),
public_base_url: env::var("PUBLIC_BASE_URL").expect("PUBLIC_BASE_URL missing"),
asset_base_url: env::var("ASSET_BASE_URL").expect("ASSET_BASE_URL missing"),
allowed_origin: env::var("ALLOWED_ORIGIN").unwrap_or_else(|_| "*".to_string()),
}
}
}
C5) src/db.rs
use sqlx::{MySql, Pool};
pub type Db = Pool<MySql>;
pub async fn connect(database_url: &str) -> Db {
Pool::<MySql>::connect(database_url).await.expect("DB connect failed")
}
C6) s*rc/utils/slug.rs*
pub fn slugify(s: &str) -> String {
let s = s.trim().to_lowercase();
let mut out = String::new();
let mut dash = false;
for ch in s.chars() {
if ch.is_ascii_alphanumeric() {
out.push(ch);
dash = false;
} else if ch.is_whitespace() || ch == '-' {
if !dash && !out.is_empty() {
out.push('-');
dash = true;
}
}
}
while out.ends_with('-') { out.pop(); }
out
}
C7) src/utils/images.rs
use serde_json::Value;
use std::collections::HashSet;
#[derive(Clone)]
pub struct ImageMetaBuilt {
pub src: String,
pub file: String,
}
pub fn build_images_meta(json_str: Option<&str>) -> Vec<ImageMetaBuilt> {
let mut partner: Vec<String> = vec![];
let mut admin: Vec<String> = vec![];
if let Some(s) = json_str {
if let Ok(v) = serde_json::from_str::<Value>(s) {
if let Some(a) = v.get("partner").and_then(|x| x.as_array()) {
partner = a.iter().filter_map(|x| x.as_str().map(|z| z.to_string())).collect();
}
if let Some(a) = v.get("admin").and_then(|x| x.as_array()) {
admin = a.iter().filter_map(|x| x.as_str().map(|z| z.to_string())).collect();
}
}
}
let clean = |arr: Vec<String>| {
arr.into_iter().filter(|x| {
let t = x.trim();
!t.is_empty() && t != "null"
}).collect::<Vec<_>>()
};
partner = clean(partner);
admin = clean(admin);
let mut seen: HashSet<String> = HashSet::new();
let mut out: Vec<ImageMetaBuilt> = vec![];
for f in partner {
if seen.insert(f.clone()) {
out.push(ImageMetaBuilt { src: "partner".to_string(), file: f });
}
}
for f in admin {
if seen.insert(f.clone()) {
out.push(ImageMetaBuilt { src: "default".to_string(), file: f });
}
}
out
}
C8) src/utils/urls.rs
use crate::utils::slug::slugify;
pub fn image_url(asset_base: &str, file: &str) -> String {
format!("{}/storage/vechile_image/{}", asset_base.trim_end_matches('/'), file)
}
pub fn shop_url(base: &str, partner_slug: &str, shop_slug: &str) -> String {
format!("{}/partners/{}/{}", base.trim_end_matches('/'), partner_slug, shop_slug)
}
pub fn review_url(base: &str, partner_slug: &str, shop_slug: &str) -> String {
format!("{}/partners/{}/{}/review", base.trim_end_matches('/'), partner_slug, shop_slug)
}
pub fn booking_url(base: &str, id: i64) -> String {
format!("{}/booking/{}", base.trim_end_matches('/'), id)
}
// Adjust this to your exact show route pattern if needed
pub fn show_vehicle_url(base: &str, city: &str, vehicle_type: &str, brand: &str, model: &str, id: i64) -> String {
format!(
"{}/{}/{}/{}/{}/{}",
base.trim_end_matches('/'),
slugify(city),
slugify(vehicle_type),
slugify(brand),
slugify(model),
id
)
}
C9) src/models/home_vehicles.rs
use serde::Serialize;
#[derive(Serialize)]
pub struct Pagination {
pub current_page: u32,
pub last_page: u32,
pub per_page: u32,
pub total: u64,
}
#[derive(Serialize)]
pub struct ApiResponse<T> {
pub ok: bool,
pub data: Vec<T>,
pub pagination: Pagination,
}
#[derive(Serialize)]
pub struct ImageMeta {
pub src: String,
pub file: String,
pub url: String,
}
#[derive(Serialize)]
pub struct VehicleItem {
pub vechicle_id: i64,
pub brand: Option<String>,
pub model: Option<String>,
pub vehical: Option<String>,
pub city: Option<String>,
pub price: Option<String>,
pub currency_code: Option<String>,
pub has_partner_photos: bool,
pub images_meta: Vec<ImageMeta>,
pub main_image: Option<String>,
pub slug: Option<String>,
pub shop_slug: Option<String>,
pub shop_url: String,
pub review_url: String,
pub show_url: String,
pub booking_url: String,
pub rating_10: Option<f32>,
pub rating_count: Option<i64>,
pub comment_count: Option<i64>,
}
C10) src/services/home_vehicles_service.rs (FULL QUERY + TRANSFORM)
use sqlx::Row;
use crate::{
config::AppConfig,
db::Db,
models::home_vehicles::{VehicleItem, ImageMeta},
utils::{images::build_images_meta, urls},
};
pub async fn fetch_home_vehicles(
db: &Db,
cfg: &AppConfig,
page: u32,
per_page: u32,
) -> Result<(Vec<VehicleItem>, u64), sqlx::Error> {
let offset = (page.saturating_sub(1) * per_page) as i64;
let limit = per_page as i64;
let total_row = sqlx::query(
r#"
SELECT COUNT(*) as c
FROM addvechicles
WHERE status='active' AND publish='1' AND dummy_data='0'
"#
)
.fetch_one(db)
.await?;
let total: i64 = total_row.try_get("c").unwrap_or(0);
let total_u64 = total.max(0) as u64;
let rows = sqlx::query(
r#"
SELECT
av.id as vechicle_id,
av.status, av.publish, av.dummy_data, av.vehical, av.price, av.vechicle_image,
aba.brand, aba.model,
s.city, s.shop_slug,
u.slug
FROM addvechicles av
JOIN addvehical_byadmins aba ON av.vehical_id = aba.id
JOIN shops s ON av.shop_id = s.id
JOIN users u ON av.vender_ID = u.id
WHERE av.status='active' AND av.publish='1' AND av.dummy_data='0'
ORDER BY av.id DESC
LIMIT ? OFFSET ?
"#
)
.bind(limit)
.bind(offset)
.fetch_all(db)
.await?;
let mut out = Vec::with_capacity(rows.len());
for r in rows {
let vechicle_id: i64 = r.try_get("vechicle_id").unwrap_or(0);
let brand: Option<String> = r.try_get("brand").ok();
let model: Option<String> = r.try_get("model").ok();
let vehical: Option<String> = r.try_get("vehical").ok();
let city: Option<String> = r.try_get("city").ok();
let price: Option<String> = r.try_get("price").ok();
let shop_slug: Option<String> = r.try_get("shop_slug").ok();
let slug: Option<String> = r.try_get("slug").ok();
let vechicle_image_json: Option<String> = r.try_get("vechicle_image").ok();
let built = build_images_meta(vechicle_image_json.as_deref());
let has_partner_photos = built.iter().any(|x| x.src == "partner");
let images_meta: Vec<ImageMeta> = built
.iter()
.map(|x| ImageMeta {
src: x.src.clone(),
file: x.file.clone(),
url: urls::image_url(&cfg.asset_base_url, &x.file),
})
.collect();
let main_image = images_meta.get(0).map(|x| x.url.clone());
let partner_slug = slug.clone().unwrap_or_default();
let shop_slug_s = shop_slug.clone().unwrap_or_default();
let shop_url = urls::shop_url(&cfg.public_base_url, &partner_slug, &shop_slug_s);
let review_url = urls::review_url(&cfg.public_base_url, &partner_slug, &shop_slug_s);
let show_url = urls::show_vehicle_url(
&cfg.public_base_url,
city.as_deref().unwrap_or(""),
vehical.as_deref().unwrap_or(""),
brand.as_deref().unwrap_or(""),
model.as_deref().unwrap_or(""),
vechicle_id,
);
let booking_url = urls::booking_url(&cfg.public_base_url, vechicle_id);
out.push(VehicleItem {
vechicle_id,
brand,
model,
vehical,
city,
price,
currency_code: None,
has_partner_photos,
images_meta,
main_image,
slug,
shop_slug,
shop_url,
review_url,
show_url,
booking_url,
rating_10: None,
rating_count: None,
comment_count: None,
});
}
Ok((out, total_u64))
}
C11) src/handlers/home_vehicles.rs
use axum::{extract::{Query, State}, Json};
use serde::Deserialize;
use crate::{
config::AppConfig,
db::Db,
models::home_vehicles::{ApiResponse, Pagination, VehicleItem},
services::home_vehicles_service::fetch_home_vehicles,
};
#[derive(Clone)]
pub struct AppState {
pub db: Db,
pub cfg: AppConfig,
}
#[derive(Deserialize)]
pub struct VehiclesQuery {
pub page: Option<u32>,
pub per_page: Option<u32>,
}
pub async fn index(
State(state): State<AppState>,
Query(q): Query<VehiclesQuery>,
) -> Json<ApiResponse<VehicleItem>> {
let per_page = q.per_page.unwrap_or(9).max(1).min(50);
let page = q.page.unwrap_or(1).max(1);
let (data, total) = fetch_home_vehicles(&state.db, &state.cfg, page, per_page)
.await
.unwrap_or((vec![], 0));
let last_page = ((total as f64) / (per_page as f64)).ceil().max(1.0) as u32;
Json(ApiResponse {
ok: true,
data,
pagination: Pagination {
current_page: page,
last_page,
per_page,
total,
},
})
}
C12) src/routes.rs
use axum::{routing::get, Router};
use crate::handlers::home_vehicles::{self, AppState};
pub fn build_router(state: AppState) -> Router {
Router::new()
.route("/api/home/vehicles", get(home_vehicles::index))
.with_state(state)
}
C13) src/main.rs (CORS + gzip)
mod config;
mod db;
mod routes;
mod handlers {
pub mod home_vehicles;
}
mod models {
pub mod home_vehicles;
}
mod services {
pub mod home_vehicles_service;
}
mod utils {
pub mod slug;
pub mod images;
pub mod urls;
}
use dotenvy::dotenv;
use std::{env, net::SocketAddr};
use tower_http::{
cors::{CorsLayer, Any},
compression::CompressionLayer,
};
use crate::{config::AppConfig, handlers::home_vehicles::AppState};
#[tokio::main]
async fn main() {
dotenv().ok();
let cfg = AppConfig::from_env();
let db = db::connect(&cfg.database_url).await;
let state = AppState { db, cfg: cfg.clone() };
let cors = if cfg.allowed_origin == "*" {
CorsLayer::new().allow_origin(Any).allow_methods(Any).allow_headers(Any)
} else {
CorsLayer::new()
.allow_origin(cfg.allowed_origin.parse().unwrap())
.allow_methods(Any)
.allow_headers(Any)
};
let app = routes::build_router(state)
.layer(cors)
.layer(CompressionLayer::new());
let port: u16 = env::var("PORT").ok().and_then(|v| v.parse().ok()).unwrap_or(8080);
let addr: SocketAddr = format!("0.0.0.0:{}", port).parse().unwrap();
println!("Rust API running at {}", addr);
axum::serve(tokio::net::TcpListener::bind(addr).await.unwrap(), app)
.await
.unwrap();
}
C14) Run Rust
cd rust-backend
cargo run
Test:
curl "http://127.0.0.1:8080/api/home/vehicles?page=1&per_page=9"
✅ D) Final Step: Connect React to Rust
Set in Laravel .env:
VEHICLES_API_BASE=https://rust-api.yourdomain.com
Now React loads page 2+ from Rust automatically.
✅ E) Performance Must-Do (DB Index)
Run in MySQL:
ALTER TABLE addvechicles
ADD INDEX idx_av_status_publish_dummy_id (status, publish, dummy_data, id),
ADD INDEX idx_av_vehical_id (vehical_id),
ADD INDEX idx_av_shop_id (shop_id),
ADD INDEX idx_av_vender_id (vender_ID);
Rust as a separate API service (recommended)
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
public function vehiclesFromRust(Request $request)
{
$page = (int) $request->query('page', 1);
$per = (int) $request->query('per_page', 9);
$base = rtrim(config('app.vehicles_api_base'), '/');
$url = $base . '/api/home/vehicles';
$res = Http::acceptJson()
->timeout(5)
->get($url, ['page' => $page, 'per_page' => $per]);
if (!$res->successful()) {
return response()->json([
'ok' => false,
'message' => 'Rust API failed',
'status' => $res->status(),
], 502);
}
return response()->json($res->json(), 200);
}
Top comments (0)