bonsai / crud
A reusable CRUD package for Laravel with Inertia.js and Vue 3
Installs: 16
Dependents: 1
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
Language:Vue
pkg:composer/bonsai/crud
Requires
- php: ^8.1|^8.2|^8.3
- inertiajs/inertia-laravel: ^0.6|^1.0|^2.0
- laravel/framework: ^10.0|^11.0|^12.0
README
A powerful, reusable CRUD (Create, Read, Update, Delete) package for Laravel applications using Inertia.js and Vue 3. Build complete admin panels and data management interfaces in minutes without repeating yourself.
β¨ Features
- π Zero Configuration CRUD - Define your fields and you're done
- π¨ Bootstrap 5 Ready - Beautiful UI out of the box
- π Built-in Search - Real-time search across all visible columns
- π Pagination - Automatic pagination with customizable items per page
- βοΈ Multiple Field Types - Text, textarea, number, currency, date, datetime, checkbox, combo/select, and more
- π Relationship Support - Handle Eloquent relationships with ease
- π― Validation - Full Laravel validation support
- π Flexible Actions - Enable/disable create, edit, delete, and search actions
- π¨ Customizable Buttons - Add custom action buttons per row
- π i18n Ready - Easy to translate
- π¦ No Publishing Required - Components stay in the package for automatic updates
π οΈ Technology Stack
This package is built with:
-
Backend:
- Laravel 10.x / 11.x / 12.x
- PHP 8.1+
- Inertia.js (Server-side adapter)
-
Frontend:
- Vue 3 (Composition API)
- Inertia.js (Client-side adapter)
- Bootstrap 5
- FontAwesome 6
- Axios
- SweetAlert2
-
Build Tools:
- Vite
- Laravel Vite Plugin
π Requirements
- PHP >= 8.1
- Laravel >= 10.0
- Node.js >= 16.x
- NPM >= 8.x
- Inertia.js
- Vue 3
π¦ Installation
Step 1: Install the Package
Install via Composer:
composer require bonsai/crud
Step 2: Install Frontend Dependencies
Install the required JavaScript dependencies:
npm install axios bootstrap @fortawesome/fontawesome-free sweetalert2
Step 3: Configure Vite
Add the package alias to your vite.config.js
:
import { defineConfig } from 'vite'; import laravel from 'laravel-vite-plugin'; import vue from '@vitejs/plugin-vue'; export default defineConfig({ plugins: [ laravel({ input: ['resources/css/app.css', 'resources/js/app.js'], refresh: true, }), vue({ template: { transformAssetUrls: { base: null, includeAbsolute: false, }, }, }), ], resolve: { alias: { '@': '/resources/js', '@crud': '/vendor/bonsai/crud/resources/js', }, }, });
Step 4: Configure Your App Entry Point
Update your resources/js/app.js
(or app.ts
):
import './bootstrap'; import '../css/app.css'; // Import Bootstrap and FontAwesome import 'bootstrap/dist/css/bootstrap.min.css'; import 'bootstrap'; import '@fortawesome/fontawesome-free/css/all.min.css'; // Import the CRUD helpers plugin import CrudHelpers from '../../vendor/bonsai/crud/resources/js/plugin.js'; import axios from 'axios'; import { createApp, h } from 'vue'; import { createInertiaApp } from '@inertiajs/vue3'; import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'; // Make axios globally available window.axios = axios; const appName = import.meta.env.VITE_APP_NAME || 'Laravel'; createInertiaApp({ title: (title) => `${title} - ${appName}`, resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue')), setup({ el, App, props, plugin }) { return createApp({ render: () => h(App, props) }) .use(plugin) .use(CrudHelpers) // Register the CRUD helpers .mount(el); }, progress: { color: '#4B5563', }, });
For TypeScript users, create resources/js/types/global.d.ts
:
import type { AxiosInstance } from 'axios'; declare global { interface Window { axios: AxiosInstance; } } export {};
Step 5: Create Inertia Page Wrappers
Create the directory structure:
mkdir -p resources/js/Pages/Crud
Create resources/js/Pages/Crud/Index.vue
:
<script setup> import IndexComponent from '../../../../vendor/bonsai/crud/resources/js/Pages/Index.vue'; defineProps(['title', 'breadcrumb', 'url', 'actions', 'extra_buttons', 'alert']); </script> <template> <IndexComponent v-bind="$attrs" /> </template>
Create resources/js/Pages/Crud/Edit.vue
:
<script setup> import EditComponent from '../../../../vendor/bonsai/crud/resources/js/Pages/Edit.vue'; defineProps(['title', 'breadcrumb', 'id', 'url', 'static_url']); </script> <template> <EditComponent v-bind="$attrs" /> </template>
Step 6: Build Assets
npm run dev
Or for production:
npm run build
π Usage
Basic Example
- Create a Model and Migration
php artisan make:model Product -m
- Define Your Migration
// database/migrations/xxxx_create_products_table.php public function up() { Schema::create('products', function (Blueprint $table) { $table->id(); $table->string('name'); $table->text('description')->nullable(); $table->decimal('price', 10, 2)->default(0); $table->boolean('active')->default(true); $table->timestamps(); }); }
- Configure Your Model
// app/Models/Product.php <?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Product extends Model { protected $fillable = [ 'name', 'description', 'price', 'active' ]; protected $casts = [ 'active' => 'boolean', 'price' => 'decimal:2' ]; }
- Create a Controller
php artisan make:controller ProductController
<?php namespace App\Http\Controllers; use bonsai\Crud\Http\Controllers\CrudController; use App\Models\Product; class ProductController extends CrudController { public function __construct() { $this->render([ 'model' => new Product(), 'title' => '<i class="fas fa-box"></i> Products', 'breadcrumb' => [ ['title' => 'Home', 'icon' => '<i class="fas fa-home"></i>', 'url' => '/', 'active' => false], ['title' => 'Products', 'icon' => '<i class="fas fa-box"></i>', 'url' => '/products', 'active' => true], ], 'url' => '/products', 'fields' => [ ['title' => 'ID', 'column' => 'id', 'editable' => false], ['title' => 'Name', 'column' => 'name', 'type' => 'texto'], ['title' => 'Description', 'column' => 'description', 'type' => 'textarea'], ['title' => 'Price', 'column' => 'price', 'type' => 'currency'], ['title' => 'Active', 'column' => 'active', 'type' => 'checkbox'], ], 'rules' => [ 'data.name' => 'required|string|max:255', 'data.price' => 'required|numeric|min:0', ], ]); } }
- Add Routes
// routes/web.php use App\Http\Controllers\ProductController; Route::resource('products', ProductController::class);
- Visit Your CRUD
Navigate to http://your-app.test/products
and start managing your data!
π Configuration Options
Field Types
The package supports various field types:
Type | Description | Example |
---|---|---|
texto |
Text input (default) | ['title' => 'Name', 'column' => 'name'] |
textarea |
Multi-line text | ['title' => 'Description', 'column' => 'description', 'type' => 'textarea'] |
number |
Number input | ['title' => 'Quantity', 'column' => 'quantity', 'type' => 'number'] |
currency |
Formatted currency display | ['title' => 'Price', 'column' => 'price', 'type' => 'currency'] |
date |
Date picker | ['title' => 'Birth Date', 'column' => 'birth_date', 'type' => 'date'] |
datetime |
DateTime picker | ['title' => 'Created', 'column' => 'created_at', 'type' => 'datetime'] |
checkbox |
Boolean checkbox | ['title' => 'Active', 'column' => 'active', 'type' => 'checkbox'] |
combo |
Select dropdown | ['title' => 'Category', 'column' => 'category_id', 'type' => 'combo', 'relation' => 'category', 'options' => $categories] |
password |
Password input | ['title' => 'Password', 'column' => 'password', 'type' => 'password'] |
url |
Clickable link | ['title' => 'Website', 'column' => 'url', 'type' => 'url'] |
Field Options
[ 'title' => 'Field Label', // Required: Display name 'column' => 'database_column', // Required: Database column name 'type' => 'texto', // Optional: Field type (default: 'texto') 'editable' => true, // Optional: Can be edited (default: true) 'visible' => true, // Optional: Show in list (default: true) 'relation' => 'relationName', // Optional: For combo fields 'options' => $collection, // Optional: For combo fields ]
Render Options
$this->render([ 'model' => new YourModel(), // Required: Eloquent model instance 'title' => 'Page Title', // Required: Page title 'url' => '/your-route', // Required: Base URL for CRUD 'breadcrumb' => [], // Optional: Breadcrumb array 'fields' => [], // Required: Field definitions 'rules' => [], // Optional: Validation rules 'perpage' => 25, // Optional: Items per page (default: 25) 'buttons' => [], // Optional: Custom row buttons 'extra_buttons' => [], // Optional: Extra header buttons 'actions' => [ // Optional: Enable/disable actions 'create' => true, 'edit' => true, 'delete' => true, 'search' => true, ], 'where' => [], // Optional: Query filters 'order' => [], // Optional: Default ordering 'relations' => [], // Optional: Eager load relations 'hiddens' => [], // Optional: Hidden field values 'filesystem' => 'public', // Optional: File storage disk 'alert' => null, // Optional: Alert message ]);
Custom Buttons
Add custom action buttons to each row:
'buttons' => [ [ 'type' => 'info', // Bootstrap button type 'icon' => 'fas fa-eye', // FontAwesome icon 'url' => '/products/{id}/view', // URL with {id} placeholder 'tooltip' => 'View Details', // Tooltip text 'target' => '_blank', // Optional: Link target 'confirm' => false, // Optional: Show confirmation 'confirmsg' => 'Are you sure?', // Optional: Confirmation message ] ],
Extra Header Buttons
Add buttons to the header area:
'extra_buttons' => [ [ 'title' => 'Export', 'url' => '/products/export', 'class' => 'success', // Bootstrap color class 'icon' => 'fa-download', // FontAwesome icon ] ],
Relationships
For select/combo fields with relationships:
// In your controller public function __construct() { $categories = Category::all()->map(function($cat) { return [ 'id' => $cat->id, 'name' => $cat->name ]; }); $this->render([ // ... other options 'fields' => [ [ 'title' => 'Category', 'column' => 'category_id', 'type' => 'combo', 'relation' => 'category', 'options' => $categories ] ] ]); }
Filters and Ordering
Apply default filters and sorting:
'where' => [ ['column' => 'status', 'operator' => '=', 'value' => 'active'], ['column' => 'created_at', 'operator' => '>', 'value' => '2024-01-01'], ], 'order' => [ ['column' => 'created_at', 'direction' => 'desc'], ['column' => 'name', 'direction' => 'asc'], ],
π¨ Customization
Using a Layout
Wrap your CRUD pages with a layout:
<!-- resources/js/Pages/Crud/Index.vue --> <script setup> import AppLayout from '@/Layouts/AppLayout.vue'; import IndexComponent from '../../../../vendor/bonsai/crud/resources/js/Pages/Index.vue'; defineProps(['title', 'breadcrumb', 'url', 'actions', 'extra_buttons', 'alert']); </script> <template> <AppLayout> <div class="py-12"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> <IndexComponent v-bind="$attrs" /> </div> </div> </AppLayout> </template>
Overriding Styles
Since Bootstrap is used, you can override styles in your resources/css/app.css
:
/* Customize CRUD table */ .table-striped tbody tr:nth-of-type(odd) { background-color: rgba(0, 0, 0, 0.02); } /* Customize buttons */ .btn-primary { background-color: #your-color; }
π§ Troubleshooting
Components Not Found
Error: Failed to resolve import "../../../../vendor/bonsai/crud/resources/js/Pages/Index.vue"
Solution:
- Check that the package is installed:
composer show bonsai/crud
- Verify the symlink exists:
ls -la vendor/bonsai/crud
- Make sure Vite alias is configured in
vite.config.js
- Restart Vite: Stop
npm run dev
and run it again
Axios is Not Defined
Error: Uncaught ReferenceError: axios is not defined
Solution:
- Install axios:
npm install axios
- Add to your
app.js
:import axios from 'axios'
andwindow.axios = axios
- Rebuild:
npm run dev
Styles Not Loading
Error: Buttons don't appear, inputs look unstyled
Solution:
- Install Bootstrap:
npm install bootstrap
- Import in
app.js
:import 'bootstrap/dist/css/bootstrap.min.css'
andimport 'bootstrap'
- Install FontAwesome:
npm install @fortawesome/fontawesome-free
- Import in
app.js
:import '@fortawesome/fontawesome-free/css/all.min.css'
- Rebuild:
npm run dev
SweetAlert2 Not Found
Error: Failed to resolve import "sweetalert2"
Solution:
npm install sweetalert2 npm run dev
Class CrudController Not Found
Error: Class "bonsai\Crud\Http\Controllers\CrudController" not found
Solution:
composer dump-autoload
If that doesn't work:
composer remove bonsai/crud composer require bonsai/crud
Validation Not Working
Issue: Form submits without validation
Solution: Make sure you've defined validation rules in the render()
method:
'rules' => [ 'data.name' => 'required|string|max:255', 'data.email' => 'required|email', ]
Search Not Working
Issue: Search input doesn't filter results
Solution:
- Make sure
'search' => true
in actions - Ensure visible fields have searchable data
- Check browser console for JavaScript errors
Pagination Not Appearing
Issue: No pagination controls show up
Solution: Pagination only appears when you have more than 25 records (or your custom perpage
value). Add more test data or reduce perpage
:
'perpage' => 5, // Show pagination with 5+ records
π Advanced Usage
Working with File Uploads
// In your controller 'fields' => [ ['title' => 'Image', 'column' => 'image_path', 'type' => 'file'], ], 'filesystem' => 'public', // or 's3', etc.
Hidden Fields (Auto-fill)
Automatically set field values (like user_id):
'hiddens' => [ ['field' => 'user_id', 'value' => auth()->id()], ['field' => 'company_id', 'value' => auth()->user()->company_id], ],
Nested Relationships
Display nested relationship data:
'fields' => [ ['title' => 'Category Name', 'column' => 'category.name', 'editable' => false], ], 'relations' => ['category'],
π€ Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature
) - Commit your changes (
git commit -m 'Add some AmazingFeature'
) - Push to the branch (
git push origin feature/AmazingFeature
) - Open a Pull Request
π License
This package is open-sourced software licensed under the MIT license.
π€ Author
Erick MarroquΓn
- GitHub: @menene
- Email: menene@bonsai.com.gt
π Acknowledgments
- Built with Laravel
- Powered by Inertia.js
- UI by Bootstrap
- Icons by FontAwesome
π Support
If you discover any issues or have questions, please open an issue on GitHub.
Made with β€οΈ for the Laravel community in π¬πΉ