nodes/database
Nodes Database is a Laravel package from Nodes that simplifies working with application data, providing a clean layer over schemas, migrations, and queries so you can build database-backed features with less boilerplate while staying close to Laravel's conventions and existing tooling.
nodes/database is a small utility package that ships base classes for the repository pattern on top of Eloquent. Before you install anything, note the reality check: it is archived, last released in February 2020, and has essentially no downstream users. That means it was built against Laravel 5.x/6.x-era APIs. It will still install on modern Laravel in many cases, but expect to either pin an older framework or fork and patch it.
Minimal steps:
composer require nodes/database
If the package registers a service provider via discovery, it will be picked up automatically; on older Laravel versions you may need to add Nodes\Database\DatabaseServiceProvider::class to config/app.php. Check for a config/nodes.php (or similar) and publish it if present:
php artisan vendor:publish --provider="Nodes\\Database\\DatabaseServiceProvider"
Where to look first: the src/ directory, specifically the base model and base repository classes, plus the README. There is no meaningful documentation ecosystem around this package, so the source is the documentation. Read the base repository's method list before writing anything — that tells you what you get for free (typically all, find, create, update, delete, paginate, and a query-builder passthrough) and what you'll need to add yourself.
First use case: take one existing Eloquent model that has accumulated query logic in a controller, and move that logic behind a repository. A typical first pass looks like:
namespace App\Repositories;
use Nodes\Database\Repository; // verify exact FQCN against src/
use App\Models\Order;
class OrderRepository extends Repository
{
public function __construct(Order $model)
{
parent::__construct($model);
}
public function openForCustomer(int $customerId)
{
return $this->model->where('customer_id', $customerId)
->where('status', 'open')
->latest()
->get();
}
}
Bind it in a service provider and inject the repository (not the model) into your controller. That single move is the package's entire value proposition — everything else is convention.
Repository-per-aggregate, interface-per-repository. The pattern that ages best is binding interfaces in a dedicated provider so tests can swap in fakes:
// app/Providers/RepositoryServiceProvider.php
public function register(): void
{
$this->app->bind(
\App\Contracts\OrderRepository::class,
\App\Repositories\EloquentOrderRepository::class
);
}
Keep the concrete class extending the package base; keep the interface hand-written. Do not try to put the package's base class in the interface signature — it leaks the dependency everywhere.
Thin repositories, fat models is fine; the inverse is also fine. The package's base classes are convenience, not architecture. The failure mode is building a repository for every table and ending up with UserRepository::findByEmail() wrapping User::where('email', $x)->first() — pure indirection with no payoff. Reserve repositories for aggregates with real query complexity: dashboards, search endpoints, reporting, anything with joins or conditional filters.
Query filter objects on top of the base repository. A workflow that scales well is passing a filter DTO into repository methods instead of exploding the method signature:
class OrderRepository extends Repository
{
public function search(OrderFilter $filter)
{
return $this->model->newQuery()
->when($filter->status, fn ($q, $s) => $q->where('status', $s))
->when($filter->from, fn ($q, $d) => $q->whereDate('created_at', '>=', $d))
->when($filter->customerId, fn ($q, $id) => $q->where('customer_id', $id))
->paginate($filter->perPage ?? 25);
}
}
Transactions belong in the service layer, not the repository. The base repository likely gives you raw model access; resist the urge to open transactions inside repository methods. Wrap multi-repository operations in a service or action class using DB::transaction(...), calling repositories as collaborators. This keeps the repository reusable inside larger units of work.
Eager loading as an explicit contract. Have repository methods accept array $with = [] and apply it, rather than hiding with() calls deep in the method body. Callers then control N+1 behaviour from the controller/API resource layer:
public function findWithRelations(int $id, array $with = []): ?Model
{
return $this->model->newQuery()->with($with)->find($id);
}
Integration tips for Laravel's ecosystem:
bind, not singleton, since they hold a model instance.Order in the controller for simple reads and still use the repository for complex writes.$model->wasRecentlyCreated.Testing. Point your test suite at the interface binding. With SQLite in-memory and RefreshDatabase, repository tests are just integration tests over real queries — which is where most repository bugs live anyway. For unit-level tests of services that consume repositories, bind a mock of the interface.
The archive status is the headline gotcha. Zero dependents and a 2020 release mean no security patches, no Laravel 9/10/11 compatibility work, and no fixes for PHP 8.x deprecations. Before adopting: run composer require in a scratch project on your target Laravel version and see if it resolves at all. If it does, immediately composer show nodes/database and read the actual source in vendor/ — the signatures in the repo are the truth, and any tutorial or guess about method names is likely wrong.
Fork if you're going to depend on it. With 3 stars and an archived repo, vcs-forking into your own GitHub org is cheap insurance. Pin to your fork with a specific commit, and you retain the ability to patch PHP 8 deprecations (dynamic properties on base models are a likely offender).
Configuration quirks. Older Laravel packages often rely on config/app.php provider registration and a config/ file that isn't publish-tagged with a namespaced key. If vendor:publish silently publishes nothing, check whether the service provider's boot() actually calls publishes() — in archived packages this is frequently missing or uses the old two-argument signature.
Eloquent leakage. The base repository almost certainly exposes its model instance (via a public/protected $model). Any code that reaches in and does $repo->model->where(...) from a controller has defeated the abstraction. Enforce the boundary with an interface and code review; otherwise the repository becomes a thin, unmaintained layer over Eloquent with extra indirection.
Watch for mass-assignment assumptions. Base repository create()/update() implementations often call $this->model->fill($data)->save() or ->create($data) with no filtering. If your model doesn't have $fillable set properly, you may silently expose attributes to mass assignment. Audit $fillable/$guarded on every model you route through the package.
Debugging. Because queries flow through the package's methods, DB::listen() and Laravel Telescope's query tab are your fastest tools — you'll see the real SQL and can catch missing where clauses or accidental select * on wide tables. Add a temporary ->dd() on $this->model->newQuery()->toSql() when a repository method returns nothing.
Extension points. The reliable way to customise is to subclass the base repository and override methods. Global changes (adding a default orderBy, soft-delete handling, caching) are better done in one intermediate BaseRepository of your own that all your repositories extend — that gives you a single file to patch when the upstream package inevitably breaks, and insulates the rest of your codebase from the dependency.
Honest recommendation. The package is a thin convenience wrapper. If you're starting fresh, Laravel's built-in Eloquent scopes, query builder macros, and action classes cover 90% of what this offers with zero dependency risk. Adopt nodes/database only where it already exists in a codebase you're maintaining — and in that case, plan a gradual extraction rather than expanding its footprint.
How can I help you explore Laravel packages today?