bagisto/legacy-api
A compatibility layer for Bagisto that restores legacy API endpoints and response behavior, helping existing integrations keep working after upgrades. Bridges older API clients with modern Bagisto while preserving expected routes and payloads.
Pin the version matrix before you run composer require. This package's only release is v1.0.0 (2021-11-19) with zero dependents, so nothing on Packagist is proving it works against your stack. Note three numbers — your Bagisto core version, Laravel version, and PHP version — then open composer.json at the 1.0.0 tag and read the constraints on bagisto/*/webkul/* and on the JWT library. If the Bagisto constraint does not admit your major, stop: you are forking, not installing, and you want to learn that in hour one.
Install pinned, not floating.
composer require bagisto/legacy-api:1.0.0 --with-all-dependencies
Use an exact version. There is no upstream release cadence to gain from a caret range, and v1.0.0 shipped auth-adjacent code (it moved JWT out of Bagisto core and into the package), so a silent bump on composer update is a risk you do not need.
Register and publish. v1.0.0 dropped the module service-provider dependency, so registration is package discovery plus whatever the package's own provider does at boot. Clear stale caches immediately — a stale config cache is the most common "it's installed but no routes exist" cause:
php artisan vendor:publish --provider="Webkul\LegacyApi\Providers\LegacyApiServiceProvider" --tag=config
php artisan config:clear && php artisan route:clear && php artisan optimize:clear
php artisan route:list | grep -i legacy
Confirm the routes actually materialised, and note the prefix you got versus the prefix you want.
Configure three things, in this order:
legacy/api, api/v1-legacy) so it can never shadow, or be shadowed by, the current API routes.config/auth.php and its own secret, not the app default guard.APP_KEY.First use case: prove one read endpoint end-to-end before touching carts. Take the simplest catalogue endpoint (/products or a category listing), authenticate a client, and capture the raw response body to a fixture file. That fixture is the first entry in the contract suite you will live or die by. If the envelope, field names, and pagination block do not match what your existing mobile client parses, you have found the real cost of this package on day one — which is the right day.
Coexistence, not replacement. The package's whole value is running the legacy surface alongside the current one. Give each its own route prefix, middleware group, and rate limiter:
// config/legacy-api.php (published — confirm key names against your tag)
'prefix' => env('LEGACY_API_PREFIX', 'legacy/api'),
'middleware' => ['api', 'throttle:legacy', 'legacy.auth'],
Register throttle:legacy in RouteServiceProvider with a bucket sized for actual legacy traffic, so a runaway mobile client cannot degrade the primary API. Then assert no shadowing in CI:
$this->artisan('route:list', ['--json' => true]);
// assert no duplicate URIs across the legacy and current route collections
Enable only the endpoints you have consumers for. Treat the package's full surface as a menu, not a contract. Keep an inventory table — endpoint → consuming client → named owner — and gate route registration on it. Every route you leave on is an endpoint you have promised not to break and an auth surface you must audit.
Golden-response contract fixtures in CI. Upstream will not contract-test this against current Bagisto for you, and backward compatibility fails silently in pagination envelopes, null handling, and error codes. Freeze real responses:
it('keeps the legacy product listing envelope stable', function () {
$this->getJson('/legacy/api/products?page=1', $this->legacyHeaders())
->assertOk()
->assertJsonStructure([
'data' => [['id', 'name', 'price', 'sku']],
'meta' => ['current_page', 'per_page', 'total'],
]);
});
Run those on every Bagisto core bump. This suite is the artifact that makes the bridge safe, and it is yours to own.
Auth as a contained guard. Route legacy traffic through a dedicated guard so a token rejection cannot leak into the primary API and a primary-API auth migration cannot silently break legacy clients:
'guards' => [
'legacy-api' => ['driver' => 'jwt', 'provider' => 'customers'],
],
If you are also migrating to Sanctum, keep the two guards mutually exclusive and land one migration before starting the other.
Cart and checkout flows need sticky token handling. Legacy cart sequences (create cart → add item → set shipping → payment → order) rely on the client echoing a cart identifier in a header or path segment. Wrap those in a thin integration test that walks the whole sequence, because per-endpoint tests pass while the sequence breaks on a changed identifier.
Vendor or fork deliberately. MIT plus zero upstream cadence means "buy, then own." The cheapest mechanism is a VCS path repository pointing at your fork, so you can patch the JWT constraint or a serializer without waiting for a release:
"repositories": [{ "type": "vcs", "url": "[email protected]:your-org/legacy-api.git" }],
"require": { "bagisto/legacy-api": "dev-main as 1.0.0" }
Ship a kill switch. A single env-var-guarded config flag that stops registering legacy routes lets you turn the surface off during an incident, and it makes the sunset date executable rather than aspirational.
Silent response drift is the main hazard, not broken code. Legacy payloads are almost certainly rendered from live core models and serializers, so a core upgrade that renames a field or changes a cast breaks clients without a deploy on your side. Detection is only possible if you own the fixtures. Make a contract-suite run a mandatory step of every Bagisto upgrade, even when nothing looks broken.
Route shadowing cuts both ways. If the legacy prefix is api, the current API either loses routes or wins them. Inspect php artisan route:list -v after every config change, and prefer a prefix that names the legacy surface explicitly.
JWT divergence produces blanket 401s and looks like a client bug. Check, in order: is the legacy guard's secret the one the package signs with; is the JWT library version the one the package's composer constraint resolved; is the TTL within what your clients assume; did a secret rotation miss the legacy guard. Alert on 401 rate per legacy endpoint — that signal distinguishes auth divergence from genuine client errors.
Composer resolution failures come from the JWT constraint. If a dependency bump fails, run composer why-not tymon/jwt-auth <version> (or whichever fork the package pulls) before unpicking anything else. The fix is usually a fork with a loosened constraint, not a different package.
Provider boot order bites after the module-provider removal. v1.0.0 dropped the dependency on the module service provider. If route registration, config merge, or middleware aliases appear inconsistently — working in one environment and not another — it is boot order or a stale cached config, not a missing feature. Run php artisan optimize:clear first, php artisan about second.
Read the config file before assuming key names. The published config is the source of truth for prefix, middleware, and auth keys, and it is a 2021 file. Where the package is silent, wrap it: a small middleware that reshapes responses or injects headers is a cheaper extension point than editing vendor code, and it survives your next fork rebase.
Log legacy traffic on its own channel. Tag requests by prefix in a middleware and route them to a separate log channel and metrics dashboard. When the bridge is underperforming or under attack you want the blast radius visible without querying primary API logs, and the caller dashboard is also what keeps the sunset honest.
Write the runbook the day you install. Secret rotation procedure, per-endpoint kill switch, contract-fixture update process, and an escalation path for when upstream stays quiet — which its release history suggests it will. A bridge with no named owner degrades into a permanently unsupported second API surface, and by then removing it is a client-release coordination problem rather than a config change.
How can I help you explore Laravel packages today?