API versioning in Moonlight
Who this is for
Northwind Studio is shipping DeskFlow 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 DeskFlow 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.
Define a switch
namespace Application\Switches;
use Application\Services\MemberService;
use Application\Services\V2\TaskService as TaskServiceV2;
use Pionia\Collections\Arrayable;
use Pionia\Http\Switches\ApiSwitch;
class V2Switch extends ApiSwitch
{
public static function registerServices(): Arrayable
{
return arr([
'task' => TaskServiceV2::class,
'member' => MemberService::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": "task", "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 — Alex’s mobile app keeps calling /api/v1/ until Northwind 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 DeskFlow 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.