experteam/api-laravel-base
Experteam API Laravel Base is a foundational package for building APIs in Laravel, providing base classes, shared conventions, and reusable scaffolding to help you create consistent, maintainable REST endpoints with less boilerplate. It standardizes common patterns so teams can ship reliable APIs fa...
This is still a low-adoption package (0 stars, 0 dependents), so treat the source as the documentation. Budget the first 30 minutes for reading, not coding. The good news from 2.3.0: it is actively maintained and now spans Laravel 9 → 12 and PHP 8.3.
Install and orient
composer require experteam/api-laravel-base:2.3.0
First confirm your app's Laravel and PHP versions fall inside the published matrix (Laravel 9/10/11/12, PHP 8.3+). If you're on Laravel 12, 2.3.0 is the only release that supports you — there is no fallback.
Before writing any application code, read these in order:
composer.json — the constraints should read roughly illuminate/support ^9|^10|^11|^12 and illuminate/http likewise. Note whether it pulls in Sanctum, Passport, or firebase/php-jwt; that dependency tells you what "authentication" means here more than the README does.vendor/ vs. the GitHub compare link). Release notes say "add support" with no behaviour changes — verify that yourself, because a version-guarded code path is still a code change.src/ directory tree — find the service provider, the config file, and any routes/ or database/migrations/ directories.register() and boot() — this is where guards, middleware aliases, and container bindings are wired. Note the exact alias strings and config keys; you will need them verbatim.config/api-laravel-base.php) — this is the real contract of the package.First use case: a token-issuing login endpoint
The day-one job is almost always "POST email + password, get back a token I can put in an Authorization: Bearer header." Get that working end to end before touching anything else:
php artisan vendor:publish --provider=...), then php artisan migrate.App\Models\User.php artisan route:list) or call its action/service directly from your own controller.If the package's assumptions about your User model don't match (different table, UUID keys, multi-tenant), decide immediately whether to adapt your model or wrap the package. Deciding late is expensive.
Protect route groups, not individual routes. Once the middleware alias is known, apply it at the group level so new endpoints inherit auth by default:
Route::middleware(['api', 'auth:api'])->group(function () {
Route::get('/me', fn (Request $r) => $r->user());
});
Wrap, don't scatter. Put every call into the package behind one or two thin application-level classes — a TokenService or an AuthController. When (not if) you need to replace this package, you swap one class instead of grepping 40 controllers. This matters more now, not less: 2.3.0 is a compatibility release, so the package's public surface is stable enough to hide behind a wrapper but still unproven in the wild.
final class TokenService
{
public function issue(string $email, string $password): string { /* package call */ }
public function revoke(User $user): void { /* package call */ }
}
Keep the package's guard as a named guard. Define it explicitly in config/auth.php rather than relying on package-side defaults, so the behaviour is visible to anyone reading your config:
'guards' => [
'api' => ['driver' => 'sanctum', 'provider' => 'users'], // or whatever driver the package registers
],
Separate human auth from machine auth. Use the package for user sessions/tokens; use signed URLs or a separate token type for webhooks and service-to-service calls. Mixing them is the most common source of "why is this route authenticated as the wrong principal" bugs.
Test at the HTTP boundary. Feature tests that hit /api/login and assert on the JSON shape survive an internal swap; unit tests that mock the package's internals break on every patch release.
$this->postJson('/api/login', ['email' => $u->email, 'password' => 'secret'])
->assertOk()
->assertJsonStructure(['token']);
Pin the exact version, then upgrade deliberately. Use "2.3.0" rather than ^2.3, and commit composer.lock. With a single release line and no dependents, there's no crowd to catch regressions. The upside of the new release is that you now have a real upgrade path when you move your app to Laravel 12 — take it as one deliberate composer update with the full test suite, not as a floating constraint.
Run CI against the framework versions you actually deploy. If you deploy Laravel 11 today and plan to move to 12, add both to your matrix. The package claims 9/10/11/12 support from one codebase, which means version-guarded branches — a green build on 12 does not prove the 11 path works.
Zero dependents is still the headline risk. 2.3.0 is a maintenance release with no new auth features and no downstream users vouching for it, which means you are the QA team. Before building on it, read the auth path end to end — token lifetime, revocation, password hashing, and whether tokens survive a php artisan config:cache. If the logic is thin (a few hundred lines), consider vendoring or forking it into app/ so you control it outright.
Widened illuminate/* constraints can desync from your framework. illuminate/support ^12.0 in a package is a split component, not the framework. If your app is on Laravel 11, Composer may satisfy this package with an 11.x split while your framework is 11.y — normally fine, occasionally not. After installing, run:
composer why illuminate/support
composer why illuminate/http
composer outdated --direct
and check you don't end up with two copies of an illuminate package in vendor/. If you see odd Class not found or method-signature errors after upgrading to 2.3.0, this is the first place to look.
"Backward compatible with 9–12" means conditional code. Grep the source for version_compare, method_exists, class_exists, and function_exists. Every one of those is a branch that was tested on one version and maybe not others. The path your app takes is the only one that matters — find it and mentally (or literally) step through it.
PHP 8.3 support ≠ modern PHP idioms. Supporting 9/10 back to older PHP constrains the package to a lowest common denominator: expect no readonly classes, no typed class constants, no json_validate(). Don't plan your own code style around what you see in src/; write modern PHP in your wrapper and let the package stay boring.
Auto-discovery can surprise you. Laravel's package discovery will register providers and any route files automatically. If routes appear that you didn't define, check bootstrap/cache/packages.php and the provider's boot() for loadRoutesFrom. Disable discovery in composer.json (extra.laravel.dont-discover) if you want explicit control.
Config cache bites on first deploy. php artisan config:cache in production ignores any config() calls made at runtime. If the package reads config in a service provider's register(), that value is frozen — good — but if it reads it in a middleware constructor, behaviour can diverge between local and prod. Test with config:cache enabled before shipping, and re-test immediately after the 2.3.0 upgrade.
Migration collisions. If the package ships migrations, they may create or alter users/personal_access_tokens. Run php artisan migrate --pretend on a fresh database first. Publish and edit the migration rather than letting it run blind, especially on an existing schema.
Namespace and model assumptions. Packages like this typically hardcode App\Models\User or config('auth.providers.users.model'). Check which. If it's hardcoded and you use a custom user model, you'll need a container binding or a config override — find the resolution point and override there rather than subclassing wildly.
Don't stack it with Fortify/Breeze/Sanctum's own routes. Overlapping login routes produce confusing 419/401 results. Pick one owner for /login and disable the other's route registration.
Debugging checklist when a request 401s unexpectedly:
php artisan route:list --path=api # which middleware is actually attached?
php artisan about # confirm provider loaded
php artisan tinker --execute="dump(config('auth.guards'));"
Then add a temporary Log::info() in the package's guard/authenticate method (or dd($request->bearerToken())) to see whether the token arrives, whether it's parsed, and where it's rejected. Delete it after.
Extension points to look for: a bound interface or abstract class in the container you can rebind ($this->app->bind(AuthContract::class, MyAuth::class)), an events system for login/logout, and config flags for token TTL. If none of those exist — and a compatibility-only release is not the moment they'd appear — that's your signal to wrap rather than extend.
Maintenance posture. 2.3.0 shows a maintainer tracking upstream Laravel releases, which is the most useful signal a package like this can give and materially lowers the "abandoned dependency" risk you'd otherwise price in. It does not lower the "no production users" risk. Keep the fork option open — a vcs repository entry pointing at your own fork costs nothing and lets you patch a stuck release without waiting on the maintainer. Watch for the follow-up release when Laravel 13 lands; if 2.3.0 stays the latest for a year, treat that as the abandonment signal.
How can I help you explore Laravel packages today?