API versioning in Moonlight
Who this is for
Pionia Shop is shipping Pionia Shop v2 with breaking payload changes. You need two API versions running in parallel — /api/v1/ for existing clients and /api/v2/ for the new contract.
What you will learn
- Creating a
V2Switchclass and registering it insettings.ini - Which Pionia Shop changes warrant a new version vs a new action on
v1 - How each version gets its own ping and service catalog
Before you start
- Moonlight overview — switches and service aliases
- Services —
MainSwitchwithtask,member,project
How it works
Each switch is bound to one API version path (/api/v1/, /api/v2/, …). A switch lists which services are exposed on that version.
flowchart LR INI["[app_switches]"] --> V1["/api/v1/ MainSwitch"] INI --> V2["/api/v2/ V2Switch"] V1 --> TaskV1["task / member / project"] V2 --> TaskV2[task v2 contract]
Define a switch
namespace Application\Switches;
use Application\Services\CustomerService;
use Application\Services\V2\ProductService as ProductServiceV2;
use Pionia\Collections\Arrayable;
use Pionia\Http\Switches\ApiSwitch;
class V2Switch extends ApiSwitch
{
public static function registerServices(): Arrayable
{
return arr([
'task' => ProductServiceV2::class,
'member' => CustomerService::class,
]);
}
}Scaffold with:
php pionia make:switch V2SwitchRegister versions
Declare switches in environment/settings.ini:
[app_switches]
v1=Application\Switches\MainSwitch
v2=Application\Switches\V2SwitchThe framework registers routes at boot — no bootstrap/routes.php required. See HTTP routing for provider-based registration and advanced cases.
Clients POST to the version prefix:
POST http://127.0.0.1:8000/api/v2/
{ "service": "product", "action": "list" }Each version has its own ping endpoint:
curl -s http://127.0.0.1:8000/api/v1/ping
curl -s http://127.0.0.1:8000/api/v2/pingWhen to add a version
- Breaking action contracts or payload shapes (e.g. renaming
assignee_idtomember_id) - Removing services from the public surface
- Running old and new implementations in parallel during migration
Non-breaking additions (new actions on existing services) usually stay on the current version — Ada’s mobile app keeps calling /api/v1/ until Pionia Shop sunsets it.
Common mistakes
- Creating v2 for every new action —
task.archivecan live onv1if existing clients ignore unknown actions. - Forgetting to register the switch in
settings.ini— the class alone does not create a route. - Different service aliases per version without documentation — if
v2removesproject, update/docsand notify frontend teams. - Hardcoding
/api/v1/in the SPA — useapiVersionPath()or env config so Pionia Shop can migrate gradually.
What’s next
Moonlight overview
One URL per version explained.
Documenting your API
Tag actions per version.
HTTP routing
Provider routes and route cache.