Eloquent: API 리소스

번역일: 2026년 7월 2일

Eloquent: API 리소스

소개

API를 만들 때, Eloquent 모델과 실제로 사용자에게 반환하는 JSON 응답 사이에 변환 레이어가 필요할 때가 많습니다. 예를 들어, 특정 필드는 일부 사용자에게만 보여주거나, 모델 관계를 항상 JSON 응답에 포함하고 싶을 수 있습니다. Laravel의 리소스 클래스를 사용하면 모델(또는 모델 컬렉션)을 JSON으로 명확하고 일관성 있게 변환할 수 있습니다.

물론, Eloquent 모델의 toArray 메서드나 toJson 메서드를 직접 사용해 변환할 수도 있습니다. 하지만 Eloquent 리소스는 모델과 그 관계의 JSON 직렬화를 훨씬 세밀하게 제어할 수 있게 해줍니다.

리소스 생성

리소스 클래스는 make:resource Artisan 명령어로 생성합니다. 생성된 리소스 클래스는 기본적으로 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 --collectionphp artisan make:resource UserCollection

개념 개요

NOTE

이 섹션은 리소스와 리소스 컬렉션에 대한 전체적인 개요를 다룹니다. 리소스가 제공하는 다양한 기능과 커스터마이징 방법을 깊이 이해하려면 이후 섹션들도 꼭 읽어보세요.

리소스를 작성할 때 사용 가능한 모든 옵션에 들어가기 전에, 먼저 Laravel에서 리소스가 어떻게 사용되는지 큰 그림을 살펴보겠습니다. 하나의 리소스 클래스는 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()); });

컬렉션 키 유지

라우트에서 리소스 컬렉션을 반환하면 Laravel은 컬렉션의 키를 0부터 시작하는 숫자로 재설정합니다. 원래 키를 유지하려면 리소스 클래스에 preserveKeys 프로퍼티를 true로 설정하세요.

<?php namespace App\Http\Resources; use Illuminate\Http\Resources\Json\JsonResource; class UserResource extends JsonResource { /** * 리소스의 컬렉션 키를 유지할지 여부를 나타냅니다. * * @var bool */ public bool $preserveKeys = true; }

preserveKeystrue이면, 컬렉션을 라우트나 컨트롤러에서 반환할 때 원래 키가 그대로 유지됩니다.

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은 주어진 사용자 인스턴스를 UserResource로 매핑하려 시도합니다. 이 동작을 변경하려면 리소스 컬렉션의 $collects 프로퍼티를 오버라이드하세요.

<?php namespace App\Http\Resources; use Illuminate\Http\Resources\Json\ResourceCollection; class UserCollection extends ResourceCollection { /** * 이 리소스가 수집하는 리소스 클래스를 지정합니다. * * @var string */ public string $collects = Member::class; }

Eloquent: API 리소스

소개

API를 개발할 때, Eloquent 모델과 실제로 클라이언트에 반환되는 JSON 응답 사이에 변환 레이어가 필요한 경우가 많습니다. 예를 들어, 특정 사용자에게만 일부 속성을 노출하거나, 모델의 JSON 표현에 항상 특정 연관 관계를 포함시키고 싶을 수 있습니다. Eloquent의 리소스 클래스(Resource Class) 를 사용하면 모델과 모델 컬렉션을 JSON으로 변환하는 작업을 명확하고 간결하게 처리할 수 있습니다.

물론 Eloquent 모델이나 컬렉션에서 toJson 메서드를 직접 호출해 JSON으로 변환하는 것도 가능합니다. 하지만 Eloquent 리소스를 사용하면 모델과 연관 관계의 JSON 직렬화를 훨씬 세밀하고 안정적으로 제어할 수 있습니다.

NOTE

리소스 클래스는 "응답 전용 DTO(Data Transfer Object)"라고 생각하면 이해하기 쉽습니다. 모델의 모든 데이터를 그대로 노출하는 대신, API 응답에 맞게 가공된 구조를 정의하는 역할을 합니다.

Eloquent: API 리소스

리소스 생성

리소스 클래스는 make:resource Artisan 명령어로 생성합니다. 생성된 파일은 기본적으로 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 --collectionphp artisan make:resource UserCollection

개념 개요

NOTE

이 섹션은 리소스와 리소스 컬렉션의 전반적인 개요를 다룹니다. 리소스가 제공하는 다양한 커스터마이징 기능을 깊이 이해하려면 문서의 다른 섹션도 함께 읽어보시길 권장합니다.

리소스의 세부 옵션을 살펴보기 전에, 먼저 Laravel에서 리소스가 어떻게 동작하는지 전체적인 흐름을 파악해 봅시다.

리소스 클래스는 하나의 모델을 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()); });

컬렉션 키 유지

라우트에서 리소스 컬렉션을 반환하면, Laravel은 기본적으로 컬렉션의 키를 숫자 순서로 재정렬합니다. 원래 키를 그대로 유지하고 싶다면 리소스 클래스에 preserveKeys 프로퍼티를 추가하면 됩니다.

<?php namespace App\Http\Resources; use Illuminate\Http\Resources\Json\JsonResource; class UserResource extends JsonResource { /** * 컬렉션의 원래 키를 유지할지 여부를 나타냅니다. * * @var bool */ public $preserveKeys = true; }

preserveKeystrue로 설정하면, 라우트나 컨트롤러에서 컬렉션을 반환할 때 원래 키가 그대로 유지됩니다.

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은 각 사용자 인스턴스를 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 메서드가 반환하는 배열에 추가하면 됩니다. 아래 예시에서는 PostResourcecollection 메서드를 사용해 사용자의 블로그 게시글을 함께 반환합니다.

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": "홍길동", "email": "hong@example.com" }, { "id": 2, "name": "김철수", "email": "kim@example.com" } ] }

최상위 리소스의 data 래핑을 비활성화하려면 기본 클래스인 Illuminate\Http\Resources\Json\JsonResourcewithoutWrapping 메서드를 호출하면 됩니다. 이 메서드는 모든 요청에서 실행되는 AppServiceProvider 또는 다른 서비스 프로바이더boot 메서드에서 호출하는 것이 일반적입니다.

<?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 키로 이중 감싸지지 않을까 걱정할 수 있습니다. Laravel은 리소스가 실수로 이중 래핑되는 것을 방지하므로 중첩 수준을 신경 쓸 필요가 없습니다.

<?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 메서드를 호출했더라도 Laravel이 리소스 데이터를 data 키로 감쌉니다. 페이지네이션 응답에는 페이지네이터 상태 정보를 담은 metalinks 키가 항상 포함되어야 하기 때문입니다.

{ "data": [ { "id": 1, "name": "홍길동", "email": "hong@example.com" }, { "id": 2, "name": "김철수", "email": "kim@example.com" } ], "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 } }

페이지네이션

Laravel 페이지네이터 인스턴스를 리소스의 collection 메서드나 커스텀 리소스 컬렉션에 직접 전달할 수 있습니다.

use App\Http\Resources\UserCollection; use App\Models\User; Route::get('/users', function () { return new UserCollection(User::paginate()); });

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

{ "data": [ { "id": 1, "name": "홍길동", "email": "hong@example.com" }, { "id": 2, "name": "김철수", "email": "kim@example.com" } ], "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 데이터와 links, meta 키를 담은 $default 배열을 인자로 받습니다.

/** * 리소스의 페이지네이션 정보를 커스터마이징합니다. * * @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; }

조건부 속성

특정 조건을 충족할 때만 속성을 응답에 포함하고 싶을 때가 있습니다. 예를 들어 현재 사용자가 관리자일 때만 특정 값을 노출하는 경우가 대표적입니다. Laravel은 이런 상황을 위해 다양한 헬퍼 메서드를 제공합니다. 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이면 클라이언트로 전송되기 전에 해당 키가 제거됩니다. 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 연관관계가 로드되지 않았다면, posts 키는 클라이언트로 전송되기 전에 응답에서 제거됩니다.

조건부 연관관계 카운트

연관관계 자체뿐만 아니라, 연관관계의 "카운트"도 모델에 로드된 경우에만 조건부로 응답에 포함할 수 있습니다.

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

whenCounted 메서드를 사용하면 연관관계 카운트가 로드된 경우에만 응답에 포함됩니다. 카운트가 없으면 해당 속성이 불필요하게 포함되지 않습니다.

/** * 리소스를 배열로 변환합니다. * * @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 연관관계의 카운트가 로드되지 않았다면, 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'),

조건부 피벗 정보

연관관계 정보를 조건부로 포함하는 것 외에도, whenPivotLoaded 메서드를 사용해 다대다 연관관계의 중간 테이블 데이터를 조건부로 포함할 수 있습니다. whenPivotLoaded의 첫 번째 인자는 피벗 테이블 이름이며, 두 번째 인자는 피벗 정보가 모델에 존재할 때 반환할 값을 정의하는 클로저입니다.

/** * 리소스를 배열로 변환합니다. * * @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이 아닌 다른 이름을 사용하는 경우에는 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', ], ]; }

리소스에서 추가 메타 데이터를 반환할 때, 페이지네이션 응답에서 Laravel이 자동으로 추가하는 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 헤더 등을 추가로 설정해야 할 때가 있습니다. 이런 경우 두 가지 방법을 사용할 수 있습니다.

방법 1: response() 메서드 체이닝

리소스 인스턴스에 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'); });

방법 2: 리소스 클래스 내부에 withResponse() 정의

리소스 클래스 안에 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'); } }

NOTE

withResponse()는 리소스가 컬렉션 내부의 개별 항목으로 포함될 때는 호출되지 않습니다. 최상위 리소스로 직접 반환될 때만 동작한다는 점에 주의하세요.

이 문서는 Laravel 공식 문서(MIT)를 한국 개발자를 위해 번역·재구성한 것입니다.

번역일: 2026년 7월 2일