When exposing related collections through Laravel API Resources, the default order is whatever the database returns — usually insertion order. For user-facing lists, that's rarely what you want.
The Problem
A resource with a belongsToMany relation would return items in an arbitrary order:
'items' => ItemResource::collection($this->whenLoaded('items')),
Items named "Item 2" and "Item 10" would sort lexicographically: Item 10, Item 2 — not what a user expects.
The Fix
whenLoaded accepts a callback that runs only when the relation is loaded. Use it to sort the collection before handing it off to the resource:
'items' => ItemResource::collection(
$this->whenLoaded('items', fn () => $this->items->sortBy('name', SORT_NATURAL | SORT_FLAG_CASE))
),
SORT_NATURAL gives you human-friendly ordering (Item 1, Item 2, Item 10). SORT_FLAG_CASE makes it case-insensitive. The callback is only invoked when the relation is loaded, so lazy-loading is never triggered accidentally.
Testing It
The key assertion is that numeric suffixes sort numerically, not lexicographically:
$item10 = Item::factory()->create(['name' => 'Item 10']);
$item2 = Item::factory()->create(['name' => 'Item 2']);
$item1 = Item::factory()->create(['name' => 'Item 1']);
$model->items()->attach([$item10->id, $item2->id, $item1->id]);
$model->load('items');
$result = (new ModelResource($model))->toArray(Request::create('/'));
$names = collect($result['items'])->pluck('name')->values()->all();
$this->assertSame(['Item 1', 'Item 2', 'Item 10'], $names);
Attach them out of order, load, assert the right order comes out. Simple and robust.
If this post was enjoyable or useful for you, please share it! If you have comments, questions, or feedback, you can email my personal email. To get new posts, subscribe use the RSS feed.