Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Crud Service Laravel Package

budgetdumpster/crud-service

A Laravel package that scaffolds complete CRUD services from your Eloquent models, generating controllers, form requests, API resources, and routes. It cuts repetitive admin and API boilerplate while leaving every generated class fully customizable and easy to extend.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install and inspect before you integrate.

    composer require budgetdumpster/crud-service
    

    With zero stars and no dependents, there is effectively no community documentation to lean on. Your first hour should be spent in vendor/budgetdumpster/crud-service/src/, reading the base service class and any contracts/traits it ships. That source is your README.

  2. Find the entry points, in this order:

    • The base abstract service class (usually something like BaseCrudService / AbstractService) — this defines the vocabulary: index, show, store, update, destroy, or all, find, create, update, delete.
    • Any interface it implements — bind that in a service provider so controllers type-hint the contract, not the class.
    • Any published config (php artisan vendor:publish --provider="...CrudServiceServiceProvider") — check for pagination defaults, model resolution, or transaction toggles.
  3. First use case: move one existing resource's controller logic into a service. Pick your simplest CRUD resource (tags, categories) and create:

    namespace App\Services;
    
    use App\Models\Category;
    use BudgetDumpster\CrudService\BaseCrudService;
    
    class CategoryService extends BaseCrudService
    {
        protected $model = Category::class;
    }
    

    Then inject it into the controller and reduce store/update/destroy to one-liners. This gives you a working baseline to compare against your hand-rolled logic before touching a complex resource.

  4. Verify behaviour, not just green tests. Write a feature test that hits the controller endpoints and asserts DB state, then read the service source to confirm how it performs each operation (e.g. does update use fill() + save() or update()? does create fire model events?). The differences matter for observers and audit logs.

Implementation Patterns

Thin controller, fat service. The whole point of the package is to get query/validation/persistence logic out of controllers:

class CategoryController extends Controller
{
    public function __construct(private CategoryService $service) {}

    public function index()          { return $this->service->paginate(request()->all()); }
    public function store(StoreCategoryRequest $r) { return $this->service->create($r->validated()); }
    public function update(UpdateCategoryRequest $r, Category $category)
    {
        return $this->service->update($category->id, $r->validated());
    }
}

Keep validation in FormRequests, not in the service. Most CRUD service packages accept a plain array and assume it is already validated. Doing validation in the service couples it to HTTP and makes it awkward to reuse from jobs/commands. Validate at the edge, pass clean arrays inward.

Override per-resource hooks rather than branching. If the package exposes no hooks, add your own protected methods in your concrete service:

class PostService extends BaseCrudService
{
    protected $model = Post::class;

    public function create(array $data)
    {
        return DB::transaction(function () use ($data) {
            $post = parent::create($data);
            $post->tags()->sync($data['tags'] ?? []);
            $post->refresh();
            return $post;
        });
    }
}

This is the single most important pattern: wrap parent calls in transactions whenever you add relation writes. If the base service doesn't start a transaction itself, a failed sync() after a successful create() leaves you with orphaned rows.

Compose the service into console commands and jobs. The win over an active-record-only approach is that app(PostService::class)->create($payload) works identically from a queue worker, an artisan command, or a controller. Use this for imports and seeders where you want observers/events fired consistently.

Eager loading via a shared query hook. For index endpoints, override the query builder step (whatever the package names it — query(), newQuery(), baseQuery()) to apply with() and default ordering once:

protected function query()
{
    return parent::query()->with(['author:id,name'])->latest();
}

Type-hint the contract in constructors so you can swap in a fake for tests:

$this->app->bind(CategoryService::class, fn () => new CategoryService());
// or in a test: $this->mock(CategoryService::class, ...)

Because the base method signatures are array-based and loosely typed, mocking is easier than with repositories that return bespoke DTOs.

Pagination/filtering is where you'll diverge. Expect the package's built-in filtering to be minimal. Treat paginate() as a starting point and add your own applyFilters(Builder $q, array $filters) method that you call consistently from every index action, so filtering rules live in one place.

Gotchas and Tips

  • Zero stars, zero dependents, single maintainer. Pin the exact version in composer.json ("budgetdumpster/crud-service": "1.2.*") and be prepared to fork. Read composer.json for Laravel version constraints — an unmaintained package can silently block your framework upgrade. Budget for a composer why-not laravel/framework check before you commit to it.
  • Namespace/package name mismatch. The vendor is budgetdumpster but the repo lives under BD-StrategicTech, and BD suggests an in-house package published publicly. Namespaces in src/ may not match the Packagist name — trust the source, not the docs.
  • Read the source for the mass-assignment path. If the base service uses Model::create($data) or fill(), your $fillable/$guarded must be correct or fields silently vanish. Symptom: updates return 200 but nothing changes. Fix by checking $model->getChanges() after the call in a tinker session.
  • Model events and observers. Confirm whether create/update/delete go through Eloquent methods (events fire) or raw query-builder calls (events do not). This determines whether your Auditable trait and CacheObserver actually run. Test with an Event::fake() + Event::assertDispatched assertion.
  • Transactions are likely opt-in. Do not assume create wraps relation writes. Add DB::transaction() in your overrides; it's cheap insurance and makes the service safe to call from jobs.
  • Return types are the usual trap. Does update() return the model, the ID, or a bool? Does destroy() return bool or throw ModelNotFoundException? Normalise this in your concrete services (or an intermediate base class) so controllers aren't full of defensive checks.
  • findOrFail vs find. The package may return null where you expect a 404. If you want consistent 404s, prefer the OrFail variants or throw ModelNotFoundException yourself in the controller layer.
  • Soft deletes and global scopes. If your model uses SoftDeletes or a global tenant scope, verify destroy() respects it and that index() doesn't leak across tenants. This is the highest-risk area in a package with no test suite you control — write your own tenant-isolation feature test.
  • Extension points to prefer, in order: (1) override a method in your concrete service, (2) compose the service inside your own App\Services layer that wraps it, (3) fork. Avoid (4) modifying vendor/.
  • Debugging: \DB::listen(fn ($q) => logger($q->sql, $q->bindings)); in a service provider is the fastest way to see what the package actually emits. Combine with laravel/telescope on a local environment when a store returns an unexpected shape.
  • Don't build your domain around it. Keep your controllers depending on your own App\Contracts\*Service interfaces that your concrete classes implement (and that extend the package base). When the package stalls, swapping the internals is a one-file change instead of a codebase-wide rewrite.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc
animalyoung/laravel-influxdb
onix-solutions/sms-telera
lonely-lights/prosetta
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log