본문으로 건너뛰기
버전: 10.x

Eloquent: API 리소스 (Eloquent: API Resources)

소개

API를 구축할 때, Eloquent 모델과 애플리케이션 사용자에게 실제로 반환되는 JSON 응답 사이에 위치하는 변환 계층이 필요할 때가 있습니다. 예를 들어, 특정 사용자 그룹에게만 일부 속성을 노출하거나, 모델의 JSON 표현에 항상 특정 연관관계를 포함하고 싶을 수 있습니다. Eloquent의 리소스 클래스는 이러한 모델과 모델 컬렉션을 JSON으로 손쉽고 명확하게 변환할 수 있도록 도와줍니다.

물론, Eloquent 모델이나 컬렉션의 toJson 메서드를 이용해 직접 JSON으로 변환할 수도 있지만, Eloquent 리소스는 모델과 그 연관관계를 JSON으로 직렬화하는 과정을 더 세밀하고 강력하게 제어할 수 있도록 해줍니다.

리소스 생성하기

리소스 클래스를 생성하려면 make:resource 아티즌 명령어를 사용할 수 있습니다. 기본적으로 생성된 리소스 파일은 애플리케이션의 app/Http/Resources 디렉터리에 위치하게 됩니다. 리소스 클래스는 Illuminate\Http\Resources\Json\JsonResource 클래스를 확장합니다.

php artisan make:resource UserResource

리소스 컬렉션

개별 모델을 변환하는 리소스 외에도, 모델 컬렉션을 변환하는 역할을 하는 리소스도 생성할 수 있습니다. 이를 사용하면 JSON 응답에 컬렉션 전체에 해당하는 링크나 기타 메타 정보를 포함할 수 있습니다.

리소스 컬렉션을 생성하려면 리소스를 만들 때 --collection 플래그를 사용하면 됩니다. 또는 리소스 이름에 Collection이라는 단어를 포함시키면 라라벨이 컬렉션 리소스를 생성해야 함을 인식합니다. 컬렉션 리소스는 Illuminate\Http\Resources\Json\ResourceCollection 클래스를 확장하게 됩니다.

php artisan make:resource User --collection

php artisan make:resource UserCollection

개념 개요

[!NOTE] 이 부분은 리소스 및 리소스 컬렉션에 대한 개괄적인 내용입니다. 리소스의 맞춤화 및 기능에 대해 더 깊이 이해하고 싶다면 아래 문서의 다른 섹션도 반드시 함께 읽어보시기 바랍니다.

리소스 작성시 제공되는 다양한 옵션을 알아보기 전에, 우선 라라벨에서 리소스가 어떻게 활용되는지 전반적인 흐름을 살펴보겠습니다. 리소스 클래스는 JSON 구조로 변환이 필요한 단일 모델을 대표합니다. 예를 들어, 다음은 간단한 UserResource 리소스 클래스입니다.

<?php

namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class UserResource extends JsonResource
{
/**
* 리소스를 배열로 변환합니다.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
}

각 리소스 클래스는 toArray 메서드를 정의하며, 이 메서드가 라우트 혹은 컨트롤러에서 해당 리소스가 응답으로 반환될 때 JSON으로 변환될 속성의 배열을 반환합니다.

모델의 속성에는 $this를 통해 직접 접근할 수 있습니다. 이는 리소스 클래스가 속성 및 메서드 접근을 내부 모델로 자동 위임해주기 때문입니다. 이렇게 정의한 리소스는 라우트나 컨트롤러에서 바로 반환할 수 있으며, 생성자에 모델 인스턴스를 전달해줍니다.

use App\Http\Resources\UserResource;
use App\Models\User;

Route::get('/user/{id}', function (string $id) {
return new UserResource(User::findOrFail($id));
});

리소스 컬렉션

여러 개의 리소스 또는 페이지네이션된 응답을 반환할 경우, 라우트나 컨트롤러에서 리소스 클래스의 collection 메서드를 사용하여 리소스 인스턴스를 생성하는 것이 좋습니다.

use App\Http\Resources\UserResource;
use App\Models\User;

Route::get('/users', function () {
return UserResource::collection(User::all());
});

이 방법은 컬렉션에 추가적인 메타 데이터를 포함할 수 없다는 점에 유의하세요. 컬렉션 응답에 별도의 메타데이터 등을 포함하려면, 전용 리소스 컬렉션 클래스를 만들어야 합니다.

php artisan make:resource UserCollection

리소스 컬렉션 클래스가 생성된 후에는, 응답에 포함하고자 하는 메타데이터를 쉽게 정의할 수 있습니다.

<?php

namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;

class UserCollection extends ResourceCollection
{
/**
* 리소스 컬렉션을 배열로 변환합니다.
*
* @return array<int|string, mixed>
*/
public function toArray(Request $request): array
{
return [
'data' => $this->collection,
'links' => [
'self' => 'link-value',
],
];
}
}

이렇게 정의한 리소스 컬렉션은 라우트나 컨트롤러에서 바로 반환할 수 있습니다.

use App\Http\Resources\UserCollection;
use App\Models\User;

Route::get('/users', function () {
return new UserCollection(User::all());
});

컬렉션 키 보존하기

라우트에서 리소스 컬렉션을 반환할 때, 라라벨은 컬렉션의 키를 숫자 순서로 자동 재정렬합니다. 하지만 컬렉션의 원래 키를 유지하고 싶다면, 리소스 클래스에 preserveKeys 속성을 추가해서 원래 키를 지킬 것인지 지정할 수 있습니다.

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class UserResource extends JsonResource
{
/**
* 리소스의 컬렉션 키를 보존할지 여부를 나타냅니다.
*
* @var bool
*/
public $preserveKeys = true;
}

preserveKeys 속성을 true로 설정하면, 컬렉션을 라우트나 컨트롤러에서 반환할 때 컬렉션의 키가 보존됩니다.

use App\Http\Resources\UserResource;
use App\Models\User;

Route::get('/users', function () {
return UserResource::collection(User::all()->keyBy->id);
});

기본 리소스 클래스 커스터마이즈하기

일반적으로 리소스 컬렉션의 $this->collection 속성은 컬렉션의 각 항목을 단수형 리소스 클래스로 매핑한 결과가 자동으로 할당됩니다. 이때 단수형 리소스 클래스는 컬렉션 클래스 이름에서 끝의 Collection을 뗀 이름으로 추정됩니다. 또한, 개인의 스타일에 따라 단수형 리소스 클래스 이름에 Resource가 붙거나 안 붙는 경우도 허용됩니다.

예를 들어, UserCollection은 주어진 user 인스턴스를 UserResource 리소스로 매핑하려 시도합니다. 이 동작을 변경하려면 리소스 컬렉션 클래스의 $collects 속성을 오버라이드하면 됩니다.

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\ResourceCollection;

class UserCollection extends ResourceCollection
{
/**
* 이 리소스가 수집하는 리소스를 지정합니다.
*
* @var string
*/
public $collects = Member::class;
}

리소스 작성하기

[!NOTE] 아직 개념 개요 섹션을 읽지 않았다면, 이 문서를 계속 읽기 전에 꼭 읽어보는 것을 권장합니다.

리소스는 주어진 모델을 배열로 변환하는 역할만 하면 됩니다. 따라서 각 리소스는 toArray 메서드를 포함하는데, 이 메서드는 모델의 속성들을 API에 적합한 배열 형태로 변환해서 라우트나 컨트롤러로부터 응답으로 반환할 수 있도록 합니다.

<?php

namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class UserResource extends JsonResource
{
/**
* 리소스를 배열로 변환합니다.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
}

이렇게 정의한 리소스는 라우트나 컨트롤러에서 다음과 같이 바로 반환할 수 있습니다.

use App\Http\Resources\UserResource;
use App\Models\User;

Route::get('/user/{id}', function (string $id) {
return new UserResource(User::findOrFail($id));
});

연관관계

응답에 관련 리소스를 포함하고 싶다면, 해당 리소스의 toArray 메서드에서 반환하는 배열에 관련 리소스를 추가하면 됩니다. 다음 예제는 PostResource 리소스의 collection 메서드를 사용해 사용자의 블로그 게시글 목록을 응답에 포함하는 방법입니다.

use App\Http\Resources\PostResource;
use Illuminate\Http\Request;

/**
* 리소스를 배열로 변환합니다.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'posts' => PostResource::collection($this->posts),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}

[!NOTE] 연관관계를 이미 로드한 경우에만 포함하고 싶다면, 조건부 연관관계 섹션을 참고하세요.

리소스 컬렉션

리소스는 단일 모델을 배열로 변환하지만, 리소스 컬렉션은 모델 컬렉션 전체를 배열로 변환합니다. 하지만 모든 모델에 대해 별도의 리소스 컬렉션 클래스를 반드시 정의할 필요는 없습니다. 왜냐하면 모든 리소스에는 "임시" 컬렉션 리소스를 즉시 생성해 주는 collection 메서드가 있기 때문입니다.

use App\Http\Resources\UserResource;
use App\Models\User;

Route::get('/users', function () {
return UserResource::collection(User::all());
});

하지만 컬렉션과 함께 반환되는 메타데이터를 커스터마이즈해야 할 경우, 직접 리소스 컬렉션 클래스를 정의해야 합니다.

<?php

namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;

class UserCollection extends ResourceCollection
{
/**
* 리소스 컬렉션을 배열로 변환합니다.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'data' => $this->collection,
'links' => [
'self' => 'link-value',
],
];
}
}

단수형 리소스와 마찬가지로, 리소스 컬렉션도 라우트나 컨트롤러에서 바로 반환할 수 있습니다.

use App\Http\Resources\UserCollection;
use App\Models\User;

Route::get('/users', function () {
return new UserCollection(User::all());
});

데이터 래핑

기본적으로, 가장 바깥의 리소스는 리소스 응답이 JSON으로 변환될 때 data 키로 감싼 형태로 반환됩니다. 예를 들어, 일반적인 리소스 컬렉션 응답은 다음과 같이 보입니다.

{
"data": [
{
"id": 1,
"name": "Eladio Schroeder Sr.",
"email": "[email protected]"
},
{
"id": 2,
"name": "Liliana Mayert",
"email": "[email protected]"
}
]
}

외부 리소스의 wrapping(래핑)을 비활성화하고 싶다면, 기본 Illuminate\Http\Resources\Json\JsonResource 클래스의 withoutWrapping 메서드를 호출해야 합니다. 일반적으로 이 메서드는 애플리케이션의 모든 요청에서 동작하는 AppServiceProvider 또는 다른 서비스 프로바이더에서 호출하는 것이 좋습니다.

<?php

namespace App\Providers;

use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
/**
* 애플리케이션 서비스를 등록합니다.
*/
public function register(): void
{
// ...
}

/**
* 애플리케이션 부트스트랩 서비스를 수행합니다.
*/
public function boot(): void
{
JsonResource::withoutWrapping();
}
}

[!WARNING] withoutWrapping 메서드는 가장 바깥쪽 응답에만 영향을 주며, 직접 추가한 리소스 컬렉션의 data 키에는 영향을 주지 않습니다.

중첩 리소스의 래핑

리소스의 연관관계를 어떻게 감쌀 것인지는 전적으로 자유롭게 선택할 수 있습니다. 모든 리소스 컬렉션을 data 키로 감싸고 싶다면, 각 리소스에 대해 별도의 리소스 컬렉션 클래스를 정의하고 컬렉션을 data라는 키에 반환하면 됩니다.

혹시 이렇게 하면 바깥 리소스에 data 키가 두 번 중첩되는 것 아니냐고 궁금하실 수 있는데, 걱정하지 않으셔도 됩니다. 라라벨은 절대로 리소스가 실수로 이중 래핑되지 않도록 알아서 처리해 주기 때문에, 리소스 컬렉션의 중첩 수준을 신경 쓰지 않아도 됩니다.

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\ResourceCollection;

class CommentsCollection extends ResourceCollection
{
/**
* 리소스 컬렉션을 배열로 변환합니다.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return ['data' => $this->collection];
}
}

데이터 래핑과 페이지네이션

페이지네이션된 컬렉션을 리소스 응답으로 반환할 때는, withoutWrapping 메서드를 호출했더라도 라라벨이 리소스 데이터를 항상 data 키로 감쌉니다. 페이지네이션 응답에는 항상 paginator의 상태 정보를 담은 metalinks 키가 포함되기 때문입니다.

{
"data": [
{
"id": 1,
"name": "Eladio Schroeder Sr.",
"email": "[email protected]"
},
{
"id": 2,
"name": "Liliana Mayert",
"email": "[email protected]"
}
],
"links":{
"first": "http://example.com/users?page=1",
"last": "http://example.com/users?page=1",
"prev": null,
"next": null
},
"meta":{
"current_page": 1,
"from": 1,
"last_page": 1,
"path": "http://example.com/users",
"per_page": 15,
"to": 10,
"total": 10
}
}

페이지네이션

라라벨의 paginator 인스턴스를 리소스의 collection 메서드나 직접 정의한 리소스 컬렉션에 전달할 수 있습니다.

use App\Http\Resources\UserCollection;
use App\Models\User;

Route::get('/users', function () {
return new UserCollection(User::paginate());
});

페이지네이션된 응답에는 항상 paginator의 상태 정보를 담은 metalinks 키가 포함됩니다.

{
"data": [
{
"id": 1,
"name": "Eladio Schroeder Sr.",
"email": "[email protected]"
},
{
"id": 2,
"name": "Liliana Mayert",
"email": "[email protected]"
}
],
"links":{
"first": "http://example.com/users?page=1",
"last": "http://example.com/users?page=1",
"prev": null,
"next": null
},
"meta":{
"current_page": 1,
"from": 1,
"last_page": 1,
"path": "http://example.com/users",
"per_page": 15,
"to": 10,
"total": 10
}
}

페이지네이션 정보 커스터마이즈

페이지네이션 응답의 linksmeta에 포함되는 정보를 맞춤화하고 싶다면, 리소스 클래스에 paginationInformation 메서드를 정의할 수 있습니다. 이 메서드는 $paginated 데이터와 기본 정보를 포함하는 $default 배열(여기엔 linksmeta가 포함됨)을 전달받게 됩니다.

/**
* 리소스의 페이지네이션 정보를 커스터마이즈합니다.
*
* @param \Illuminate\Http\Request $request
* @param array $paginated
* @param array $default
* @return array
*/
public function paginationInformation($request, $paginated, $default)
{
$default['links']['custom'] = 'https://example.com';

return $default;
}

조건부 속성

특정 조건이 충족될 때만 리소스 응답에 속성을 포함하고 싶을 때가 있습니다. 예를 들어, 현재 사용자가 "관리자"인 경우에만 값을 포함하고 싶은 경우가 있을 수 있죠. 이런 상황을 위해 라라벨은 다양한 헬퍼 메서드를 제공합니다. 대표적으로 when 메서드를 사용하면 조건에 따라 리소스 응답에 속성을 포함할 수 있습니다.

/**
* 리소스를 배열로 변환합니다.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'secret' => $this->when($request->user()->isAdmin(), 'secret-value'),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}

이 예시에서 secret 키는 인증된 사용자의 isAdmin 메서드가 true를 반환할 때에만 최종 리소스 응답에 포함됩니다. 만약 false라면, secret 키는 응답에서 제거됩니다. when 메서드를 사용하면 조건문을 직접 작성하지 않고도 유연하게 리소스를 만들 수 있습니다.

when 메서드의 두 번째 인자로 클로저를 전달하여, 조건이 true일 때에만 값을 계산하는 것도 가능합니다.

'secret' => $this->when($request->user()->isAdmin(), function () {
return 'secret-value';
}),

속성이 실제 모델에 존재할 때에만 응답에 포함하려면 whenHas 메서드를 사용할 수 있습니다.

'name' => $this->whenHas('name'),

또한, 속성 값이 null이 아닐 때에만 응답에 포함하려면 whenNotNull 메서드를 사용할 수 있습니다.

'name' => $this->whenNotNull($this->name),

조건부 속성 병합하기

같은 조건에 따라 여러 속성을 동시에 리소스 응답에 포함하고 싶은 경우가 있습니다. 이럴 때는 mergeWhen 메서드를 활용할 수 있습니다. 주어진 조건이 true일 때만 여러 속성을 응답에 포함시켜줍니다.

/**
* 리소스를 배열로 변환합니다.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
$this->mergeWhen($request->user()->isAdmin(), [
'first-secret' => 'value',
'second-secret' => 'value',
]),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}

마찬가지로, 조건이 false일 때는 해당 속성들이 응답에서 제외됩니다.

[!WARNING] mergeWhen 메서드는 문자열 키와 숫자 키가 혼합된 배열 또는 숫자 키가 연속적으로 정렬되지 않은 배열 내에서는 사용하지 않아야 합니다.

조건부 연관관계

속성을 조건부로 포함하는 것 외에도, 모델에서 연관관계가 이미 로드되어 있는 경우에만 해당 연관관계를 리소스 응답에 포함할 수 있습니다. 이렇게 하면 각각의 컨트롤러에서 어떤 연관관계를 로드해야 할지 선택하고, 리소스에서는 실제로 로드된 연관관계만 포함하게 할 수 있습니다. 결과적으로 리소스에서 "N+1" 쿼리 문제를 더 쉽게 방지할 수 있습니다.

whenLoaded 메서드는 연관관계의 이름을 인자로 받아, 해당 연관관계가 이미 로드된 경우에만 포함시켜줍니다. 이 메서드는 연관관계 인스턴스 자체가 아닌, 연관관계의 이름을 전달한다는 점에 주의하세요.

use App\Http\Resources\PostResource;

/**
* 리소스를 배열로 변환합니다.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'posts' => PostResource::collection($this->whenLoaded('posts')),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}

이 예시에서는, 만약 해당 연관관계가 로드되지 않았다면, posts 키가 최종 리소스 응답에서 제외됩니다.

조건부 연관관계 카운트

연관관계를 조건부로 포함하는 것뿐 아니라, 연관관계의 "count(개수)"도 모델에 미리 로드되어 있다면 리소스 응답에 조건부로 포함할 수 있습니다.

new UserResource($user->loadCount('posts'));

whenCounted 메서드를 사용하면, 연관관계의 count 값이 존재할 때에만 응답에 포함시킬 수 있습니다. count가 없는 경우에는 해당 속성이 포함되지 않습니다.

/**
* 리소스를 배열로 변환합니다.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'posts_count' => $this->whenCounted('posts'),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}

이 예시에서, 만약 posts 연관관계의 count가 로드되지 않았다면, posts_count 키는 응답에서 빠지게 됩니다.

그 외에 avg, sum, min, max와 같은 기타 집계 값도 whenAggregated 메서드를 통해 조건부로 포함할 수 있습니다.

'words_avg' => $this->whenAggregated('posts', 'words', 'avg'),
'words_sum' => $this->whenAggregated('posts', 'words', 'sum'),
'words_min' => $this->whenAggregated('posts', 'words', 'min'),
'words_max' => $this->whenAggregated('posts', 'words', 'max'),

조건부 Pivot 정보

연관관계 정보 이외에도, 다대다(many-to-many) 관계의 중간 테이블에서 가져온 데이터도 whenPivotLoaded 메서드를 이용해 조건부로 리소스 응답에 포함할 수 있습니다. 이 메서드는 pivot 테이블 이름을 첫 번째 인자로 받고, 두 번째 인자는 pivot 정보를 사용할 수 있을 때 반환할 값을 리턴하는 클로저입니다.

/**
* 리소스를 배열로 변환합니다.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'expires_at' => $this->whenPivotLoaded('role_user', function () {
return $this->pivot->expires_at;
}),
];
}

관계가 커스텀 중간 테이블 모델을 사용하는 경우, whenPivotLoaded의 첫 번째 인자로 중간 테이블 모델 인스턴스를 전달할 수 있습니다.

'expires_at' => $this->whenPivotLoaded(new Membership, function () {
return $this->pivot->expires_at;
}),

만약 중간 테이블이 기본 pivot이 아닌 다른 접근자(accessor)를 사용하는 경우에는, whenPivotLoadedAs 메서드를 사용할 수 있습니다.

/**
* 리소스를 배열로 변환합니다.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'expires_at' => $this->whenPivotLoadedAs('subscription', 'role_user', function () {
return $this->subscription->expires_at;
}),
];
}

메타 데이터 추가하기

몇몇 JSON API 표준에서는 리소스 및 리소스 컬렉션 응답에 메타 데이터를 추가할 것을 요구하기도 합니다. 여기에는 리소스나 관련 리소스에 대한 links 정보, 또는 리소스 자체에 대한 기타 메타데이터 등이 포함될 수 있습니다. 리소스에 추가 메타데이터를 반환하려면, toArray 메서드 내부에 해당 값을 포함시키면 됩니다. 예를 들어, 리소스 컬렉션 변환 시 links 정보를 포함할 수 있습니다.

/**
* 리소스를 배열로 변환합니다.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'data' => $this->collection,
'links' => [
'self' => 'link-value',
],
];
}

리소스에서 추가 메타데이터를 반환할 때는, 페이지네이션된 응답으로 인해 라라벨이 자동으로 추가하는 linksmeta 키와 충돌할 것을 걱정하지 않아도 됩니다. 직접 정의한 links값은 페이지네이터가 제공하는 링크와 자동으로 병합됩니다.

최상위 메타 데이터

경우에 따라, 리소스가 가장 바깥 리소스일 때에만 특정 메타데이터를 응답에 포함하고 싶을 수 있습니다. 보통은 응답 전체에 대한 메타 정보를 포함할 때 이에 해당합니다. 이러한 최상위 메타데이터를 정의하려면 리소스 클래스에 with 메서드를 추가하세요. 이 메서드는 바깥(최상위) 리소스가 변환될 때에만 응답에 포함될 메타데이터 배열을 반환해야 합니다.

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\ResourceCollection;

class UserCollection extends ResourceCollection
{
/**
* 리소스 컬렉션을 배열로 변환합니다.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return parent::toArray($request);
}

/**
* 리소스 배열과 함께 반환할 추가 데이터를 가져옵니다.
*
* @return array<string, mixed>
*/
public function with(Request $request): array
{
return [
'meta' => [
'key' => 'value',
],
];
}
}

리소스 생성 시 메타 데이터 추가하기

라우트나 컨트롤러에서 리소스 인스턴스를 만들 때에도, 최상위 데이터를 추가할 수 있습니다. 모든 리소스에 사용할 수 있는 additional 메서드는 리소스 응답에 포함할 추가 데이터 배열을 받습니다.

return (new UserCollection(User::all()->load('roles')))
->additional(['meta' => [
'key' => 'value',
]]);

리소스 응답

앞서 본 것처럼, 리소스는 라우트나 컨트롤러에서 바로 반환할 수 있습니다.

use App\Http\Resources\UserResource;
use App\Models\User;

Route::get('/user/{id}', function (string $id) {
return new UserResource(User::findOrFail($id));
});

그러나, 때로는 실제로 클라이언트에 응답을 보내기 전에 HTTP 응답을 커스터마이즈해야 할 수도 있습니다. 이를 위해 두 가지 방법이 있습니다. 첫 번째는 리소스에 response 메서드를 체이닝하는 방법입니다. 이 메서드는 Illuminate\Http\JsonResponse 인스턴스를 반환하며, 이를 통해 응답 헤더 등 모든 부분을 자유롭게 제어할 수 있습니다.

use App\Http\Resources\UserResource;
use App\Models\User;

Route::get('/user', function () {
return (new UserResource(User::find(1)))
->response()
->header('X-Value', 'True');
});

또 다른 방법은, 리소스 클래스 내부에 withResponse 메서드를 정의하는 것입니다. 이 메서드는 리소스가 응답의 최상위(바깥) 리소스로 반환될 때 호출됩니다.

<?php

namespace App\Http\Resources;

use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class UserResource extends JsonResource
{
/**
* 리소스를 배열로 변환합니다.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
];
}

/**
* 리소스의 응답을 커스터마이즈합니다.
*/
public function withResponse(Request $request, JsonResponse $response): void
{
$response->header('X-Value', 'True');
}
}