Build a Todo App with Laravel, React & Inertia.js: Zero API Headaches!

Author

Kritim Yantra

Jun 10, 2025

Build a Todo App with Laravel, React & Inertia.js: Zero API Headaches!

πŸ’‘ Why This Combo is Magic

Imagine building a LEGO castle:

  • Laravel = Foundation plates (backend structure)
  • React = Colorful bricks (UI components)
  • Inertia.js = Instruction manual (seamless connection)

No glue, no duct tape – everything snaps together perfectly! Today, we’ll build a fully functional Todo app without writing a single API endpoint. Let’s dive in!


πŸ› οΈ Setup: Install the Power Trio

1. Create Laravel Project

laravel new inertia-todo  
cd inertia-todo  

2. Install Inertia + React

composer require inertiajs/inertia-laravel  
npm install @inertiajs/react react react-dom  

3. Configure Inertia

Update resources/views/app.blade.php:

<!DOCTYPE html>  
<html>  
  <head>  
    @vite('resources/js/app.js')  
  </head>  
  <body>  
    <div id="app" data-page="{{ json_encode($page) }}"></div>  
  </body>  
</html>  

Create resources/js/app.js:

import { createInertiaApp } from '@inertiajs/react';  
import { createRoot } from 'react-dom/client';  

createInertiaApp({  
  resolve: name => require(`./Pages/${name}`),  
  setup({ el, App, props }) {  
    createRoot(el).render(<App {...props} />);  
  },  
});  

πŸ“¦ Step 1: Create Todo Model & Migration

Generate Model + Migration

php artisan make:model Todo -m  

Edit migration file (database/migrations/..._create_todos_table.php):

public function up() {  
  Schema::create('todos', function (Blueprint $table) {  
    $table->id();  
    $table->string('title');  
    $table->boolean('completed')->default(false);  
    $table->timestamps();  
  });  
}  

Run migration:

php artisan migrate  

πŸ”„ Step 2: Build Laravel Controller

Create Controller

php artisan make:controller TodoController  

Edit app/Http/Controllers/TodoController.php:

use App\Models\Todo;  
use Inertia\Inertia;  
use Illuminate\Http\Request;  

class TodoController extends Controller {  
  // Show all todos  
  public function index() {  
    return Inertia::render('Todos/Index', [  
      'todos' => Todo::latest()->get()  
    ]);  
  }  

  // Add new todo  
  public function store(Request $request) {  
    Todo::create($request->validate(['title' => 'required|min:3']));  
    return redirect()->back();  
  }  

  // Update completion status  
  public function update(Request $request, Todo $todo) {  
    $todo->update(['completed' => $request->completed]);  
    return redirect()->back();  
  }  

  // Delete todo  
  public function destroy(Todo $todo) {  
    $todo->delete();  
    return redirect()->back();  
  }  
}  

πŸ“ Step 3: Define Routes

Edit routes/web.php:

use App\Http\Controllers\TodoController;  

Route::get('/', [TodoController::class, 'index']);  
Route::post('/todos', [TodoController::class, 'store']);  
Route::put('/todos/{todo}', [TodoController::class, 'update']);  
Route::delete('/todos/{todo}', [TodoController::class, 'destroy']);  

βš›οΈ Step 4: Build React Components

1. Main Page (resources/js/Pages/Todos/Index.jsx)

import { Head, Link } from '@inertiajs/react';  
import TodoForm from './Form';  
import TodoList from './List';  

export default function TodoIndex({ todos }) {  
  return (  
    <div className="max-w-2xl mx-auto p-8">  
      <Head title="Your Todo List" />  
      <h1 className="text-3xl font-bold mb-6">✨ Your Todo List</h1>  
      <TodoForm />  
      <TodoList todos={todos} />  
    </div>  
  );  
}  

2. Todo Form (resources/js/Pages/Todos/Form.jsx)

import { useForm } from '@inertiajs/react';  

export default function TodoForm() {  
  const { data, setData, post, processing } = useForm({  
    title: ''  
  });  

  const submit = (e) => {  
    e.preventDefault();  
    post('/todos');  
  };  

  return (  
    <form onSubmit={submit} className="mb-8">  
      <input  
        type="text"  
        value={data.title}  
        onChange={e => setData('title', e.target.value)}  
        placeholder="Buy groceries..."  
        className="w-full p-3 border rounded"  
        disabled={processing}  
      />  
      <button  
        type="submit"  
        className="mt-2 bg-blue-500 text-white p-2 rounded"  
        disabled={processing}  
      >  
        {processing ? 'Adding...' : 'Add Task'}  
      </button>  
    </form>  
  );  
}  

3. Todo List (resources/js/Pages/Todos/List.jsx)

import { Link } from '@inertiajs/react';  

export default function TodoList({ todos }) {  
  return (  
    <div className="space-y-4">  
      {todos.map(todo => (  
        <div key={todo.id} className="flex items-center justify-between p-4 bg-white shadow rounded">  
          <div className="flex items-center">  
            <input  
              type="checkbox"  
              checked={todo.completed}  
              onChange={() => Inertia.put(`/todos/${todo.id}`, {  
                completed: !todo.completed  
              })}  
              className="mr-3 h-5 w-5"  
            />  
            <span className={todo.completed ? 'line-through text-gray-500' : ''}>  
              {todo.title}  
            </span>  
          </div>  
          <button  
            onClick={() => Inertia.delete(`/todos/${todo.id}`)}  
            className="text-red-500 hover:text-red-700"  
          >  
            Delete  
          </button>  
        </div>  
      ))}  
    </div>  
  );  
}  

πŸš€ Launch Your App!

  1. Start backend:
    php artisan serve  
    
  2. Start frontend:
    npm run dev  
    
  3. Visit http://localhost:8000

Behold! Your fully functional Todo app:

  • βœ… Add tasks
  • βœ… Mark as complete
  • βœ… Delete items
  • ⚑ All updates happen without page reloads!

πŸ” How It Works: The Inertia Magic

  1. User adds task:

    • React form β†’ Laravel store() method β†’ Database
    • No API layer!
  2. User checks todo:

    • Inertia sends PUT request β†’ Laravel update() method
    • Database updated β†’ React UI instantly re-renders
  3. User deletes item:

    • Inertia.delete() β†’ Laravel destroy()
    • Item vanishes from UI instantly

✨ Key Takeaways

  1. No API Boilerplate: Inertia lets Laravel controllers talk directly to React!
  2. Real-Time UI Updates: Pages update without reloads (SPA experience).
  3. Shared Validation: Use Laravel validation rules in React forms.
  4. Blazing Fast Dev: Build full-stack features in record time.

πŸ’‘ Pro Tip: Add Laravel Breeze for authentication:

composer require laravel/breeze --dev  
php artisan breeze:install react  

πŸš€ Your Next Challenge

Enhance your Todo app with:

  • Due dates
  • Priority levels
  • Drag-and-drop sorting

Questions? Drop them below! πŸ‘‡

Tags

Comments

No comments yet. Be the first to comment!

Please log in to post a comment:

Sign in with Google

Related Posts