Laravel + Inertia + Vuejs CRUD

A tutorial on how to make a simple CRUD using Laravel, Inertia, and Vuejs

Jose Garcia
8 min readJan 20, 2024
A futuristic massive battleship inspired by the Warhammer 40K universe, predominantly painted in violet colors, opening fire.
Image by Dall-E-3

Inertia is a tool that bridges the gap between Backend (BE) and Frontend (FE). It allows you to use some of your favorite FE frameworks on Laravel like React and Vue. It is useful in situations where you want to have a monolith. All your code base works in harmony, in one code base.

When I started using Inertia, I had some issues getting used to it. The communication with the Backend is different from what I am used to. That inspired this tutorial.

In this article, I make a CRUD (Create + Read + Update + Delete) with Laravel, Inertia, and Vuejs.

I divided this tutorial into two parts.

  1. Install Inertia on Laravel.
  2. Base setup the files we are going to use for the CRUD.
  3. CRUD. I go over each part of what makes the CRUD. Touching on both BE and FE.

In this tutorial, I assume you have:

  • Basic knowledge of Laravel.
  • Basic knowledge of Vuejs.
  • A simple Laravel installation.

I used the following versions to make this tutorial:

  • Laravel 10 and 11
  • Vue 3
  • Inertia 1

Enough of foreplay. Let us begin.

Context

We are going with a classic for this: A CRUD of a simple blog.

  • We are going to assume that a blog only needs posts.
  • Every one of our posts has a title and body. Both are strings.

Install

In this section, I install Inertia on a Laravel project. Feel free to skip if you know how to get this done. You should have a Laravel install by this point.

I am going to use the Breeze starter kit. This starter kit has everything we need to kick-start any Inertia project.

Inside of your project run:

composer require laravel/breeze --dev

After that, you should have the new artisan command:

php artisan breeze:install

That will display the different flavors of breeze. Select Vue with Inertia:

In the next steps, I am not selecting anything to keep this simple.

After that, you need to install the node dependencies, run:

npm install

To start the client-side server to run the old usual:

npm run dev

Done and done. Let us get to the files set up.

Base set up

We need to set up the base of our little project. For simplicity’s sake, I am going to skip validation and authentication.

We are focusing on:

  • Routes.
  • Migration.
  • Model.
  • Controller.

We will make the Vue views and edit the Laravel controllers in the CRUD section.

Model

One nit trick is to use Laravel’s commands to generate everything we need. Run:

php artisan make:model Post -mc

That will generate your model, controller, and migration for you.

Next, throw in the resource routes in your web.php file.

Route::resource('posts', PostController::class);

Get into your new model. In there, set the fillable attribute with the name and body fields to mass assign.

protected $fillable = ['title', 'body'];

Migration

Search for the create posts migration. In the up method add:

$table->string('title');
$table->string('body');

With that, we have our basic setup. Now, let’s get to the meat of this article. Let’s build our CRUD.

CRUD

We are here, the point of this article. No more fluff.

This section has the following order:

  1. Read.
  2. Create.
  3. Delete.
  4. Update.

Let’s begin.

Read

Let’s see how to take data from the server and display it on the client.

In /resources/js/Pages/ make a new folder called Post. In the folder, make an Index.vue component. Add this to it:

<template>
<div>
<h1>My Inertia CRUD</h1>
<table>
<thead>
<tr>
<th v-for="header in headers" :key="header">
{{ header }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="post in posts" :key="post.id">
<td>{{ post.title }}</td>
<td>{{ post.body }}</td>
</tr>
</tbody>
</table>
</div>
</template>

<script setup>
defineProps({
posts: {
type: Array,
default: () => [],
},
});

const headers = ["posts", "body"];
</script>

This is our Posts’ principal page.

Notice the “posts” prop. Inertia injects data thru the props of the “page components”.

Page components are the components you render using Inertia’s API on Laravel. More on that on the controller code.

Inside PostController, add this function:

public function index()
{
$posts = Post::all();
return Inertia::render('Post/Index', ['posts' => $posts]);
}

We get all posts of the database with $posts = Post::all(). We pass them to the view with return Inertia::render(‘Post/Index’, [‘posts’ => $posts]);.

Notice that:

  • You don’t need to define the extension of the view.
  • The data sent with Inertia::render is accessible through the components’ props.

Create

Let’s insert new posts.

We need a button that redirects to the create form.

Inside the <script setup> import Inertia’s Link component like so:

import { Link } from "@inertiajs/vue3";

To create links to other pages within an Inertia app, you will typically use the Inertia <Link> component. This component is a light wrapper around a standard anchor <a> link that intercepts click events and prevents full page reloads. This is how Inertia provides a single-page app experience once your application has been loaded.

See the rest of the article on Inertia’s page on the Link component.

You use this component how you use an <a> tag. Throw this above the table.

<Link href="posts/create">Create new Post</Link>
<table>...</table>

This will call the create method inside our PostController.php. In the create method put:

public function create()
{
return Inertia::render('Post/Create');
}

This method will redirect us to a new nonexistent view. The view to insert new posts.

In the same folder you made Index.vue component, make a new Create.vue component. In there throw the following code:

<template>
<form @submit.prevent="submit">
<label for="title">Title</label>
<input type="text" id="title" v-model="form.title" />
<label for="body">Body</label>
<input type="text" id="body" v-model="form.body" />
<button type="submit">Submit</button>
</form>
</template>

<script setup>
import { useForm } from "@inertiajs/vue3";
const form = useForm({
title: "",
body: "",
});
const submit = () => {
form.post("/posts");
};
</script>
<style lang="scss" scoped></style>

Something important here is the useForm helper from Inertia. When we use Inertia, we use this helper for every interaction with the BE. Only GET requests are not made from the form helper.

Back to the controller search for the store method. This method is in charge of the inserting. In there add:

public function store(Request $request)
{
$post = new Post($request->all());
$post->save();
return redirect()->route('posts.index');
}

In Laravel v11 if $request->all() fails try $request::all() . There was no change in the docs but only the later worked for me.

Notice that I am using the redirect method. This method helps you do redirects. In this case, I am redirecting back to the index method.

The route sub-method allows you to pass the name of the route to the redirect instead of the complete route. If you want to see all your routes and their names run on the root of your project:

php artisan route:list

You should see something like the following:

You can see the name of each route at the right of your terminal.

If you want to know how create with a dialog instead of a view check this article:

Delete

Delete is one of the easiest actions. Let’s destroy some data.

Head to the Index.vue component. We will make a few additions there.

First on your component’s <script>, we need to make the logic for deletion.

<script setup>
import { Link, useForm } from "@inertiajs/vue3";

defineProps({
posts: {
type: Array,
default: () => [],
},
});

const headers = ["title", "body", "actions"];

const form = useForm({});

const deletePost = (id) => {
form.delete(`posts/${id}`);
};
</script>

Yes, we will use the useForm helper to also delete. (Inertia best practices). Not the most intuitive move by the Inertia guys I agree.

Now, on the template we need to add the delete button. Something simple like this suffices:

<tr v-for="post in posts" :key="post.id">
<td>{{ post.title }}</td>
<td>{{ post.body }}</td>
<td>
<button @click="deletePost(post.id)">Delete</button>
</td>
</tr>

Add the destroy method:

public function destroy(Post $post)
{
$post->delete();
return redirect()->back();
}

And that’s that. We are missing the update functionality.

Update

Last but not least we have the update action.

To begin, we need to add the Update action link to the table. Inside the Index.vue page, in the same row as the Delete button, add the following:

<td>
<button @click="deletePost(post.id)">Delete</button>
<Link :href="`posts/${post.id}/edit`">Edit</Link>
</td>

This will redirect to one of our last unused controller methods “edit”.

We can make the update view in 2 different ways.

  1. Make a new view and add the logic.
  2. Use the old Create.vue view.

I will use the 2nd way here. Is more DRY.

Add the edit method inside PostController.php. Inside it, add:

public function edit(Post $post)
{
return Inertia::render('Post/Create', ['post' => $post, 'isUpdating' => true]);
}

Notice that I am using the same view but I am also passing the post we want to edit. Since we are using the same view, we need to tell it that it’s updating. Hence the 'isUpdating' => true part.

Since we are using the same view we need to make a few changes to accommodate the edit logic. In the Create.vue view add:

import { onMounted } from "vue";
import { useForm } from "@inertiajs/vue3";

const props = defineProps({
post: {
type: Object,
default: null,
},
isUpdating: {
type: Boolean,
default: false,
},
});

const form = useForm({
title: "",
body: "",
});

const submit = () => (props.isUpdating ? updatePost() : addPost());
const addPost = () => form.post("/posts");
const updatePost = () => form.put(`/posts/${props.post.id}`);
onMounted(() => {
form.title = props.post.title;
form.body = props.post.body;
});

The only novelty is the put method in form.put. As you may have guessed, it updates.

Take a look at the onMounted method. In it, I fill the form with the post prop.

Finally, we need to add the logic for updating. Find the update method inside PostController and add:

public function update(Request $request, Post $post)
{
$post->update($request->all());
return redirect()->route('posts.index');
}

In Laravel v11 if $request->all() fails try $request::all() . There was no change in the docs but only the later worked for me.

And with that, we finished our CRUD.

Conclusions

If you are here, congrats! This was a long one. Let’s go over what we covered.

  • We touched on how to install Inertia on Laravel.
  • We did a simple setup to prepare for our CRUD.
  • And finally, we got our CRUD (create, read, update, and delete).

I hope this article is instructive for you. Go with luck.

--

--

Jose Garcia

Hello There! I am a fullstack developer, a fan of open source and crypto and an obssesive fella. Well met.