joshcirre/inertiakit

File-based Inertia routing, simplified server controllers + typed models & props

v0.0.10-alpha 2025-04-25 22:39 UTC

This package is auto-updated.

Last update: 2025-04-30 23:06:54 UTC


README

inertiaKIT logo

inertiaKIT (WIP)

inertiaKIT is a zero-boilerplate approach to file-based routing and typed props in Laravel + InertiaJS created by Josh Cirre. It auto-generates:

  • Routes (and optional Controllers) from resources/js/pages/*.server.php
  • TypeScript interfaces for your Eloquent models
  • TypeScript interfaces for your page props, with camelCased keys and model types

⚠️ Alpha software—expect rough edges!

📦 Installation

  1. Require the package

    composer require joshcirre/inertiakit:"^0.0.10-alpha"
  2. Install the package

    php artisan inertiakit:install
  3. (Optional but recommended) Install Laravel Wayfinder for zero-config route-action types:

    composer require laravel/wayfinder

⚙️ Configuration

Edit your config/inertiakit.php to tailor:

return [
    // Explicit Eloquent models to type, or empty = auto-discover all under app/Models
    'models'         => [],

    // Where to write your generated TS model interfaces
    'types_output'   => 'resources/js/types/models.d.ts',

    // Generate real Controllers under app/Http/Controllers/Generated
    'use_controllers'=> env('INERTIAKIT_USE_CONTROLLERS', true),

    // Where to dump your auto-generated routes file
    'routes_file'    => 'routes/inertiakit.php',

    // Glob patterns under resources/js/pages to ignore
    'ignore'         => [
        'auth/*',
        'settings/*',
        'welcome',
        'dashboard',
    ],
];

🚀 Usage

Run the generation command manually once php artisan inertiakit:generate, or run the following as needed:

php artisan inertiakit:generate
php artisan inertiakit:model-types
php artisan inertiakit:page-types

🖌️ Defining Page Data & Actions

Each page pairs a React component (.tsx) with a PHP server file (.server.php). In the server file you export:

  • load: a closure that returns an array of props.
  • Named actions: any other key whose value is a closure becomes an Inertia endpoint.
<?php

use App\Models\Todo;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

return [
    'load' => function () {
        return [
            'todos' => Todo::all(),
        ];
    },
    // -> add actions here:
    'addTodo' => function (Request $request) {
        $data = $request->validate(['title' => 'required|string', 'description' => 'required|string']);
        Auth::user()->todos()->create($data);

        return back();
    },

    'deleteTodo' => function (Todo $todo) {
        $todo = Todo::find($todo->id);

        $todo->delete();

        return back();
    },

    'sayHi' => function () {
        return 'Hello, World!';
    }
];

On the client, InertiaKit injects typed actions and props that you can use:

import type { TodosIndexProps } from '@/types/page-props';
import { Link, useForm, usePage } from '@inertiajs/react';

import { addTodo } from '@/actions/App/Http/Controllers/Generated/Pages/TodosIndexController';
import InputError from '@/components/input-error';
import AppLayout from '@/layouts/app-layout';
import todos from '@/routes/todos';
import deleteTodo from '@/routes/todos/index/deleteTodo';

type CreateTodoForm = {
  title: string;
  description: string;
};

export default function TodosIndex() {
  const { props } = usePage<TodosIndexProps>();
  const form = useForm<CreateTodoForm>();

  const submit = (e: React.FormEvent) => {
    e.preventDefault();
    form.submit(addTodo(), {
      onSuccess: () => form.setData({ title: '', description: '' }),
      preserveScroll: true,
    });
  };

  return (
    <AppLayout>
      <h1>My Todos</h1>

      {props.todos.map((todo) => (
        <div key={todo.id}>
          <p>{todo.title}</p>
          <p>{todo.description}</p>
          <Link href={deleteTodo(todo.id)}>
            <button type="button">Delete</button>
          </Link>
        </div>
      ))}

      <form onSubmit={submit}>
        <label htmlFor="title">Title</label>
        <input
          id="title"
          value={form.data.title}
          onChange={(e) => form.setData('title', e.target.value)}
        />
        <InputError message={form.errors.title} />

        <label htmlFor="description">Description</label>
        <input
          id="description"
          value={form.data.description}
          onChange={(e) => form.setData('description', e.target.value)}
        />
        <InputError message={form.errors.description} />

        <button type="submit" disabled={form.processing}>
          Add Todo
        </button>
      </form>

      <Link href={todos.about()}>About todos…</Link>
    </AppLayout>
  );
}

🔄 Automatic Re-generation with Vite

For seamless DX, you can hook up a file watcher in your vite.config.js. (this is done automatically when you run inertiakit:install)

npm install -D vite-plugin-run

And in your vite.config.js:

import laravel from 'laravel-vite-plugin';
import { run } from 'vite-plugin-run';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [
    laravel({
      input: ['resources/css/app.css', 'resources/js/app.tsx'],
      ssr:   'resources/js/ssr.tsx',
      refresh: true,
    }),
    run([
      {
        name: 'inertiakit:types',
        run: ['php', 'artisan', 'inertiakit:model-types && php artisan inertiakit:page-types'],
        pattern: ['app/Models/**/*.php', 'resources/js/pages/**/*.server.php'],
      },
    ]),
  ],
});

📖 Further Reading

Happy coding! 🎉