Building an Inventory Management App with Laravel Livewire

In this article, we will be building an Inventory Management App with Laravel Livewire. Laravel Livewire is a powerful full-stack framework for Laravel that allows you to build dynamic interfaces without leaving the comfort of Laravel. We will walk through the process of creating a CRUD (Create, Read, Update, Delete) application for managing an inventory using Laravel Livewire. This inventory app will allow you to keep track of products, their quantities, and other relevant details.

Before we start, make sure you have the following installed:

  • PHP (>= 7.4)
  • Composer
  • Node.js
  • Laravel (latest version)
  • MySQL or any other database of your choice

I. Setting Up the Project

1. Create a New Laravel Project

First, let’s create a new Laravel project. Open your terminal and run:

composer create-project --prefer-dist laravel/laravel inventory-app
cd inventory-app

2. Install Livewire

Next, we need to install Livewire. Run the following command in your project directory:

composer require livewire/livewire

3. Set Up the Database

Configure your database by updating the .env file with your database details:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=inventory_db
DB_USERNAME=root
DB_PASSWORD=

Then, create a database with the name inventory_db (or whatever name you choose).

4. Create a Product Model and Migration

Let’s create a Product model and its corresponding migration file:

php artisan make:model Product -m

Open the migration file in database/migrations and define the schema for the products table:

Schema::create('products', function (Blueprint $table) {
            $table->id();
            $table->foreignIdFor(\App\Models\User::class);
            $table->string('name');
            $table->text('description')->nullable();
            $table->integer('quantity');
            $table->timestamps();
        });

Run the migration to create the table:

php artisan migrate

This command will execute all the migration files and create the database for us. If the database you set in your .env config is new then you will be prompted with this message, asking if you will allow the process to create the database for you.

Building an Inventory Management App with Laravel Livewire

Type yes if you agree and you will be able to check the migration file that is executed at the end of the process. See the image below for reference.

Building an Inventory Management App with Laravel Livewire

You can verify the database by opening your MySql server and look for your database name which in my case is inventory_db. Below are all the initial tables created including the products table we added in the previous steps.

Building an Inventory Management App with Laravel Livewire

5. Create Product Model fillable Columns

Now, we need to set a fillable column for the Product Model. Open app/Models/Product.php then add the following line.

protected $fillable = [
        'user_id',
        'name',
        'description',
        'quantity'
    ];

II. Create Login Authentication

To make this a more real-life scenario, let’s create a login authentication using Laravel breeze. Laravel Breeze is a simple implementation of all of Laravel’s authentication features, which include login, registration, password reset, email verification, and password confirmation.

1. Install Laravel Breeze

Run the following command to install Laravel Breeze using Composer.

composer require laravel/breeze --dev

Then we can implement it in our project using the following command.

php artisan breeze:install

You will be prompted with this option. Select Livewire by typing the option(livewire) and press enter on your keyboard

Building an Inventory Management App with Laravel Livewire

To proceed answer the succeeding option with your preference. For this demo, I’ll use dark mode support.

Building an Inventory Management App with Laravel Livewire

Then wait for the process to finish and afterward run the following command.

php artisan serve

This is how my App looks like at this time.

Building an Inventory Management App with Laravel Livewire

2. Create a Test Account

Now, you will be able to see the login and register menu. By default Laravel has a test user in place, to check you can navigate to database » seeders » DatabaseSeeder.php. Run the following command to activate this user and add it to your database.

php artisan db:seed

Then you can use the following credentials to log in:

User: test@example.com
Password: password

After logging in, you will only see a Dashboard in the Menu at the moment.

III. Creating the Livewire Component

1. Create a Livewire Component

Generate a Livewire component for managing products:

php artisan make:livewire ProductComponent

This command creates two files: ProductComponent.php in app/Http/Livewire and product-component.blade.php in resources/views/livewire.

2. Implementing CRUD Operations

Open ProductComponent.php and add the necessary properties and methods:

<?php


namespace App\Livewire;


use Livewire\Component;
use App\Models\Product;
use Livewire\Attributes\Layout;
use Livewire\WithPagination;


#[Layout('layouts.app')]
class ProductComponent extends Component
{
    use WithPagination;
    public $name, $description, $quantity, $product_id;
    public $isOpen = 0;
    public $modalText = '';


    public function render()
    {
        $products = Product::where('user_id', auth()->user()->id)->paginate(5);
        return view('livewire.product-component',[
            'products' => $products
        ]);
    }


    public function create()
    {
        $this->resetInputFields();
        $this->openModal();
        $this->modalText = "Create";
    }


    public function openModal()
    {
        $this->isOpen = true;
        $this->dispatch('modal-open');
    }


    public function closeModal()
    {
        $this->isOpen = false;
        $this->dispatch('modal-close');
    }


    private function resetInputFields()
    {
        $this->name = '';
        $this->description = '';
        $this->quantity = '';
        $this->product_id = '';
    }


    public function store()
    {
        $this->validate([
            'name' => 'required',
            'quantity' => 'required|integer',
        ]);


        Product::updateOrCreate(['id' => $this->product_id], [
            'user_id' => auth()->id(),
            'name' => $this->name,
            'description' => $this->description,
            'quantity' => $this->quantity,
        ]);
     
        session()->flash(
            'message',
            $this->product_id ? 'Product Updated Successfully.' : 'Product Created Successfully.'
        );


        $this->closeModal();
        $this->resetInputFields();
    }


    public function edit($id)
    {
        $product = Product::findOrFail($id);
        $this->product_id = $id;
        $this->name = $product->name;
        $this->description = $product->description;
        $this->quantity = $product->quantity;
        $this->modalText = "Edit";
        $this->openModal();
    }


    public function delete($id)
    {
        Product::find($id)->delete();
        session()->flash('message', 'Product Deleted Successfully.');
    }
}

Here are the details on the code used above:

  1. Layout Attribute: The #[Layout(‘layouts.app’)] attribute specifies that this component will use the layouts.app layout.
  2. render(): This method is responsible for generating the view.
    • It retrieves a paginated list of products belonging to the authenticated user.
    • Returns the livewire.product-component view, passing the retrieved products to the view.
  3. create(): This method prepares the component for creating a new product.
  4. openModal() and closeModal(): These methods control the modal’s visibility and dispatch corresponding events (modal-open and modal-close).
  5. store(): This method handles storing (creating or updating) a product.
  6. edit(): This method prepares the component for editing an existing product.
  7. delete(): This method deletes a product by ID and sets a flash message indicating success.

3. Create Blade View for Livewire Component

Next, open product-component.blade.php and add the following code:

<div class="py-12">
    <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
        <div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg p-5">
            @if (session()->has('message'))
                <div class="p-4 mb-4 text-sm text-green-800 rounded-lg bg-green-50 dark:bg-gray-800 dark:text-green-400"
                    role="alert">
                    <span class="font-medium"> {{ session('message') }}</span>
                </div>
            @endif
            <div class="float-right">
                <button wire:click="create()" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">Create Product</button>
            </div>
            @if ($isOpen)
                @include('livewire.create')
            @endif
            <table class="table-auto w-full text-white">
                <thead>
                    <tr>
                        <th class="px-4 py-2">
                            <div class="flex items-center">ID</div>
                        </th>
                        <th class="px-4 py-2">
                            <div class="flex items-center">Name</div>
                        </th>
                        <th class="px-4 py-2">
                            <div class="flex items-center"></div>
                        </th>
                        <th class="px-4 py-2">
                            <div class="flex items-center"></div>
                        </th>
                        <th class="px-4 py-2">
                            <div class="flex items-center">Actions</div>
                        </th>
                    </tr>
                </thead>
                <tbody>
                    @foreach ($products as $product)
                        <tr>
                            <td class="border px-4 py-2">{{ $product->id }}</td>
                            <td class="border px-4 py-2">{{ $product->name }}</td>
                            <td class="border px-4 py-2">{{ $product->description }}</td>
                            <td class="border px-4 py-2">{{ $product->quantity }}</td>
                            <td class="border px-4 py-2">
                                <button wire:click="edit({{ $product->id }})" class="bg-blue-500 hover:bg-blue-700 text-white text-sm font-bold py-1 px-2 rounded">Edit</button>
                                <button wire:click="delete({{ $product->id }})" class="bg-red-500 hover:bg-red-700 text-white text-sm font-bold py-1 px-2 rounded">Delete</button>
                            </td>
                        </tr>
                    @endforeach
                </tbody>
            </table>
        </div>
        <div class="mt-4">
           {{ $products->links() }}
        </div>
    </div>
</div>

4. Create new Blade Template

Create a new Blade view file create.blade.php in the resources/views/livewire directory:

<div class="fixed z-10 inset-0 overflow-y-auto @if(!$isOpen) hidden @endif">
    <div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
        <div class="fixed inset-0 transition-opacity" aria-hidden="true">
            <div class="absolute inset-0 bg-gray-500 opacity-75"></div>
        </div>


        <span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">​</span>
        <div class="inline-block align-bottom bg-white rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full sm:p-6">
            <div class="sm:flex sm:items-start">
                <div class="w-full mt-3 text-center sm:mt-0 sm:text-left">
                    <h3 class="text-lg leading-6 font-medium text-gray-900" id="modal-title">
                        {{$modalText}} Product
                    </h3>
                    <div class="mt-2">
                        <form>
                            <div class="mb-4">
                                <label for="name" class="block text-gray-700 text-sm font-bold mb-2">Name</label>
                                <input type="text" id="name" wire:model="name" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline">
                                @error('name') <span class="text-red-500 text-xs">{{ $message }}</span> @enderror
                            </div>
                            <div class="mb-4">
                                <label for="description" class="block text-gray-700 text-sm font-bold mb-2">Description</label>
                                <textarea id="description" wire:model="description" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"></textarea>
                                @error('description') <span class="text-red-500 text-xs">{{ $message }}</span> @enderror
                            </div>
                            <div class="mb-4">
                                <label for="quantity" class="block text-gray-700 text-sm font-bold mb-2">Quantity</label>
                                <input type="text" id="quantity" wire:model="quantity" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline">
                                @error('quantity') <span class="text-red-500 text-xs">{{ $message }}</span> @enderror
                            </div>
                            <div class="flex items-center justify-between">
                                <button type="button" wire:click.prevent="store()" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">Save</button>
                                <button type="button" wire:click="closeModal" class="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">Cancel</button>
                            </div>
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

IV. Integrating Livewire with Laravel

1: Setting Up Routes

Open routes/web.php and add the route for the product component:

use App\Livewire\ProductComponent;

Route::get('products', ProductComponent::class)
        ->middleware(['auth'])
        ->name('products');

This route will only be accessible to authenticated users. 

2: Update Layout File

Include Livewire styles and scripts in your main layout file. (resources/views/layouts/app.blade.php):

To do that, add this line inside the <head> section.

@livewireStyles

And add the livewire script before the end of </body> tag

@livewireScripts

3: Add Product Menu

Now, add our product route into our App by opening the navigation layout inside resources » views » livewire » layout » navigation.blade.php.

Add the following line after the Dashboard navigation:

<div class="hidden space-x-8 sm:-my-px sm:ms-10 sm:flex">
                    <x-nav-link :href="route('products')" :active="request()->routeIs('products')" wire:navigate>
                        {{ __('Products') }}
                    </x-nav-link>
                </div>

V. Running the Application

Start the Laravel development server:

1. Open a new terminal in VSCode and run the following command. 

php artisan serve

2. If you use Laravel version 11.x and above, the project uses Vite by default. This means you will need to run an additional command to compile your local development assets.

npm run dev

3. Navigate to http://localhost:8000 in your browser. You should be able to see the default welcome page which we did not modify but just added a login authentication using Laravel Breeze.

4. Login using the default test account:

5. You should see the product management interface where you can create, view, update, and delete products.

Building an Inventory Management App with Laravel Livewire

Now to download our free source code from this tutorial, you can use the button below.

Note: Extract the file using 7Zip and use password: freecodespot

VI. Conclusion

In this article, we built a simple inventory management application using Laravel Livewire and added user authentication using Laravel Breeze. Livewire simplifies the process of building dynamic interfaces by handling the communication between the client and server seamlessly. With this foundation, you can extend the application to include more features such as product categories and more advanced validation. Keep coding!