API Platform GraphQL Symfony: 스키마, 뮤테이션, 면접 질문 2026
Symfony에서 API Platform GraphQL을 구현하는 방법을 설명합니다. 자동 스키마 생성, 커스텀 리졸버, 보안 설정, 실시간 구독, 기술 면접 질문과 답변을 다룹니다.

API Platform GraphQL은 Symfony 애플리케이션을 REST에 내재된 과다 취득(over-fetching) 및 과소 취득(under-fetching) 문제를 해결하는 타입 안전하고 강력한 API로 변환합니다. PHP 속성에서 자동 스키마 생성과 Relay 사양의 완전한 지원을 통해 API Platform 4.x는 최소한의 설정으로 프로덕션 환경에 적합한 GraphQL 구현을 제공합니다.
GraphQL은 단일 쿼리에서 필요한 필드만 정확하게 요청하지만, REST는 고정된 응답 구조를 반환합니다. API Platform은 동일한 리소스 정의에서 두 엔드포인트를 모두 생성하여 클라이언트가 사용 사례에 맞는 프로토콜을 선택할 수 있습니다.
Symfony에서 GraphQL 지원 설치 및 활성화
API Platform은 GraphQL 기능을 전용 패키지로 분리합니다. 이 모듈식 접근 방식은 REST만 필요한 프로젝트에서 코어를 가볍게 유지합니다.
# Install GraphQL support
composer require api-platform/graphql설치 후 /graphql 엔드포인트가 자동으로 사용 가능해집니다. 스키마는 추가 설정 없이 기존 #[ApiResource] 속성에서 생성됩니다.
namespace App\Entity;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\GraphQl\Query;
use ApiPlatform\Metadata\GraphQl\QueryCollection;
use ApiPlatform\Metadata\GraphQl\Mutation;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ApiResource(
graphQlOperations: [
new Query(),
new QueryCollection(),
new Mutation(name: 'create'),
new Mutation(name: 'update'),
new Mutation(name: 'delete'),
]
)]
class Book
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
private string $title;
#[ORM\Column(length: 13)]
private string $isbn;
#[ORM\Column]
private \DateTimeImmutable $publishedAt;
// Getters and setters...
}이 단일 엔티티 정의로 ID 또는 컬렉션으로 도서를 가져오는 쿼리와 생성, 업데이트, 삭제 작업을 위한 뮤테이션이 노출됩니다. GraphQL 스키마는 PHP 타입을 직접 반영합니다: string은 String!이 되고, nullable 타입은 선택적 필드가 됩니다.
GraphQL 쿼리와 뮤테이션 작성
GraphQL 쿼리는 반환할 필드를 정확하게 지정합니다. 이 정밀도는 대역폭 낭비를 제거하고 클라이언트 측 데이터 변환을 줄입니다.
# Fetch a single book with specific fields
query GetBook {
book(id: "/books/42") {
title
isbn
publishedAt
}
}
# Fetch a collection with pagination
query ListBooks {
books(first: 10, after: "cursor123") {
edges {
node {
id
title
}
cursor
}
pageInfo {
hasNextPage
endCursor
}
}
}뮤테이션은 input 객체와 요청 추적을 위한 clientMutationId를 사용하여 Relay 사양을 따릅니다.
# Create a new book
mutation CreateBook {
createBook(input: {
title: "Domain-Driven Design"
isbn: "9780321125217"
publishedAt: "2003-08-30"
clientMutationId: "create-1"
}) {
book {
id
title
}
clientMutationId
}
}
# Update an existing book
mutation UpdateBook {
updateBook(input: {
id: "/books/42"
title: "Updated Title"
clientMutationId: "update-1"
}) {
book {
id
title
}
}
}clientMutationId는 배치 시나리오에서 클라이언트가 요청과 응답을 연관시키는 데 도움이 됩니다. API Platform은 이를 응답에서 변경 없이 반환합니다.
복잡한 비즈니스 로직을 위한 커스텀 리졸버 구현
기본 CRUD 작업은 기본적인 경우를 처리하지만, 실제 애플리케이션에는 커스텀 비즈니스 로직이 필요합니다. API Platform은 쿼리와 뮤테이션을 위한 리졸버 인터페이스를 제공합니다.
namespace App\Resolver;
use ApiPlatform\GraphQl\Resolver\QueryCollectionResolverInterface;
use App\Repository\BookRepository;
final class BookBestSellerResolver implements QueryCollectionResolverInterface
{
public function __construct(
private readonly BookRepository $bookRepository
) {}
/**
* @param iterable<Book> $collection
* @return iterable<Book>
*/
public function __invoke(iterable $collection, array $context): iterable
{
// Access GraphQL arguments from context
$limit = $context['args']['limit'] ?? 10;
$period = $context['args']['period'] ?? 'month';
return $this->bookRepository->findBestSellers($limit, $period);
}
}커스텀 리졸버를 엔티티 설정에 등록합니다:
#[ApiResource(
graphQlOperations: [
new QueryCollection(
name: 'bestSellers',
resolver: BookBestSellerResolver::class,
args: [
'limit' => ['type' => 'Int', 'default_value' => 10],
'period' => ['type' => 'String', 'default_value' => 'month'],
]
),
]
)]
class Book
{
// ...
}이렇게 하면 limit과 period 인수를 받는 bestSellers 쿼리가 노출되며, 기본 Doctrine 쿼리 대신 커스텀 리포지토리 로직이 실행됩니다.
Symfony 면접 준비가 되셨나요?
인터랙티브 시뮬레이터, flashcards, 기술 테스트로 연습하세요.
Voter와 Expression을 사용한 GraphQL 작업 보안
GraphQL의 보안 설정은 REST와 독립적으로 작동합니다. 각 작업은 Symfony의 표현식 언어를 사용하여 자체 액세스 규칙을 정의할 수 있습니다.
use ApiPlatform\Metadata\GraphQl\Query;
use ApiPlatform\Metadata\GraphQl\Mutation;
#[ApiResource(
graphQlOperations: [
new Query(
security: "is_granted('ROLE_USER')"
),
new QueryCollection(
security: "is_granted('ROLE_USER')"
),
new Mutation(
name: 'create',
security: "is_granted('ROLE_EDITOR')"
),
new Mutation(
name: 'update',
security: "is_granted('ROLE_EDITOR') and object.getAuthor() == user",
securityMessage: "Only the author can update this book."
),
new Mutation(
name: 'delete',
security: "is_granted('ROLE_ADMIN')"
),
]
)]
class Book
{
// ...
}보안 표현식의 object 변수는 액세스되는 엔티티를 참조합니다. 이를 통해 세밀한 소유권 검사가 가능합니다. 복잡한 인가 로직의 경우 인라인 표현식보다 Symfony Security voters가 더 깔끔한 솔루션을 제공합니다.
namespace App\Security\Voter;
use App\Entity\Book;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\User\UserInterface;
class BookVoter extends Voter
{
public const EDIT = 'BOOK_EDIT';
public const DELETE = 'BOOK_DELETE';
protected function supports(string $attribute, mixed $subject): bool
{
return in_array($attribute, [self::EDIT, self::DELETE])
&& $subject instanceof Book;
}
protected function voteOnAttribute(
string $attribute,
mixed $subject,
TokenInterface $token
): bool {
$user = $token->getUser();
if (!$user instanceof UserInterface) {
return false;
}
/** @var Book $book */
$book = $subject;
return match($attribute) {
self::EDIT => $book->getAuthor() === $user,
self::DELETE => in_array('ROLE_ADMIN', $user->getRoles()),
default => false,
};
}
}GraphQL 구독을 통한 실시간 업데이트
API Platform은 서버 전송 이벤트를 위한 프로토콜인 Mercure를 통해 GraphQL 구독을 구현합니다. 구독은 리소스가 변경될 때 클라이언트에 데이터를 푸시합니다.
use ApiPlatform\Metadata\GraphQl\Subscription;
#[ApiResource(
mercure: true,
graphQlOperations: [
new Query(),
new Mutation(name: 'update'),
new Subscription(),
]
)]
class Book
{
// ...
}클라이언트는 표준 GraphQL 구독 구문을 사용하여 변경 사항을 구독합니다:
subscription BookUpdates {
updateBookSubscribe(input: { id: "/books/42" }) {
book {
id
title
updatedAt
}
}
}뮤테이션이 도서를 업데이트하면 Mercure가 구독 중인 모든 클라이언트에 변경 사항을 브로드캐스트합니다. 이 패턴은 협업 애플리케이션, 라이브 대시보드, 실시간 알림에 적합합니다. 높은 처리량 시나리오에서는 구독을 Symfony Messenger와 결합하여 뮤테이션 처리와 알림 발송을 분리합니다.
면접 질문: API Platform GraphQL
Symfony 포지션 기술 면접에서 GraphQL 통합에 관한 질문이 증가하고 있습니다. 이러한 질문은 사양과 API Platform 구현 모두에 대한 이해를 테스트합니다.
Q: API Platform은 GraphQL 스키마를 어떻게 생성합니까?
API Platform은 #[ApiResource] 속성과 PHP 타입 선언을 검사하여 스키마를 구축합니다. 엔티티 속성이 필드가 되고 PHP 타입이 GraphQL 타입에 매핑됩니다. 개발 모드에서는 요청마다 스키마가 재생성되고 프로덕션에서는 캐시됩니다.
Q: Query와 QueryCollection 작업의 차이점은 무엇입니까?
Query는 식별자로 단일 항목을 가져오며 id 인수가 필요합니다. QueryCollection은 선택적 필터링, 페이지네이션, 정렬과 함께 여러 항목을 반환합니다. 둘 다 커스텀 리졸버를 가질 수 있지만 인터페이스가 다릅니다: QueryItemResolverInterface와 QueryCollectionResolverInterface입니다.
Q: API Platform GraphQL에서 N+1 쿼리 문제를 어떻게 처리합니까?
DataLoader 패턴은 여러 데이터베이스 쿼리를 하나로 배치 처리합니다. API Platform은 쿼리 확장에서 페치 조인을 통한 Doctrine의 즉시 로딩과 통합됩니다. 복잡한 경우 Doctrine의 addSelect()를 사용하여 초기 쿼리에서 연관 관계를 가져오는 커스텀 리졸버를 구현합니다.
// Custom query extension for eager loading
public function applyToCollection(
QueryBuilder $queryBuilder,
QueryNameGeneratorInterface $queryNameGenerator,
string $resourceClass,
?Operation $operation = null,
array $context = []
): void {
$queryBuilder
->addSelect('author')
->leftJoin('o.author', 'author');
}Q: 동일한 리소스에서 REST와 GraphQL 보안 규칙을 다르게 할 수 있습니까?
네. REST 작업은 #[Get], #[Post] 등에서 security를 사용하고, GraphQL 작업은 #[Query], #[Mutation] 등에서 security를 사용합니다. 이 분리로 한 프로토콜에 더 엄격한 규칙을 설정할 수 있습니다. 일반적인 패턴은 퍼블릭 클라이언트용으로 읽기 전용 GraphQL을 노출하고 REST 뮤테이션에는 인증을 요구하는 것입니다.
Q: GraphQL 스키마에 커스텀 스칼라 타입을 추가하려면 어떻게 해야 합니까?
config/packages/api_platform.yaml에서 커스텀 타입을 등록하고 직렬화 로직을 구현합니다:
# config/packages/api_platform.yaml
api_platform:
graphql:
enabled: true
graphiql:
enabled: truenamespace App\GraphQL\Type;
use GraphQL\Type\Definition\ScalarType;
final class DateTimeType extends ScalarType
{
public string $name = 'DateTime';
public function serialize($value): string
{
return $value->format(\DateTimeInterface::RFC3339);
}
public function parseValue($value): \DateTimeImmutable
{
return new \DateTimeImmutable($value);
}
public function parseLiteral($valueNode, ?array $variables = null): \DateTimeImmutable
{
return new \DateTimeImmutable($valueNode->value);
}
}페이지네이션 전략: 커서 vs 페이지 기반
API Platform은 Relay Connection 사양을 따라 기본적으로 커서 기반 페이지네이션을 사용합니다. 삽입이 결과를 이동시키지 않으므로 이 접근 방식은 오프셋 페이지네이션보다 실시간 데이터를 더 잘 처리합니다.
# Cursor-based (default)
query {
books(first: 10, after: "YXJyYXljb25uZWN0aW9uOjk=") {
edges {
cursor
node {
title
}
}
pageInfo {
endCursor
hasNextPage
}
}
}페이지 기반 페이지네이션은 클라이언트가 직접 페이지 액세스가 필요한 간단한 사용 사례에 적합합니다:
// Enable page-based pagination
#[ApiResource(
paginationType: 'page',
graphQlOperations: [
new QueryCollection(paginationType: 'page'),
]
)]
class Book {}# Page-based
query {
books(page: 2, itemsPerPage: 20) {
collection {
title
}
paginationInfo {
totalCount
lastPage
}
}
}커서 페이지네이션은 OFFSET 쿼리를 피하므로 규모에서 더 나은 성능을 발휘합니다. 트레이드오프는 클라이언트가 임의의 페이지로 점프할 수 없다는 것입니다.
Symfony에서 GraphQL 엔드포인트 테스트
기능 테스트는 Symfony의 테스트 클라이언트를 사용하여 GraphQL 동작을 검증합니다. API Platform은 GraphQL 전용 테스트 트레이트를 제공합니다.
namespace App\Tests\GraphQL;
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
use App\Entity\Book;
class BookTest extends ApiTestCase
{
public function testQueryBook(): void
{
$client = static::createClient();
// Create test data
$book = new Book();
$book->setTitle('Test Book');
$book->setIsbn('1234567890123');
$book->setPublishedAt(new \DateTimeImmutable());
$em = static::getContainer()->get('doctrine')->getManager();
$em->persist($book);
$em->flush();
// Execute GraphQL query
$response = $client->request('POST', '/graphql', [
'json' => [
'query' => '
query GetBook($id: ID!) {
book(id: $id) {
title
isbn
}
}
',
'variables' => [
'id' => '/books/' . $book->getId(),
],
],
]);
$this->assertResponseIsSuccessful();
$data = $response->toArray();
$this->assertEquals('Test Book', $data['data']['book']['title']);
$this->assertEquals('1234567890123', $data['data']['book']['isbn']);
}
public function testMutationRequiresAuthentication(): void
{
$client = static::createClient();
$response = $client->request('POST', '/graphql', [
'json' => [
'query' => '
mutation CreateBook {
createBook(input: {
title: "Unauthorized Book"
isbn: "0000000000000"
clientMutationId: "test"
}) {
book { id }
}
}
',
],
]);
$data = $response->toArray();
$this->assertArrayHasKey('errors', $data);
}
}이러한 테스트는 성공적인 작업과 보안 적용을 모두 검증합니다. php bin/phpunit tests/GraphQL/로 실행하여 API 동작의 회귀를 감지합니다.
연습을 시작하세요!
면접 시뮬레이터와 기술 테스트로 지식을 테스트하세요.
API Platform GraphQL in Symfony 핵심 요점
api-platform/graphql을 설치하면#[ApiResource]속성에서 자동 스키마 생성으로/graphql엔드포인트가 활성화됩니다Query,QueryCollection,Mutation작업을 사용하여 각 리소스가 노출하는 GraphQL 작업을 제어합니다- CRUD 이상의 커스텀 비즈니스 로직에는
QueryItemResolverInterface또는MutationResolverInterface를 구현합니다 - GraphQL 작업의 보안 표현식은 REST와 독립적으로 작동하여 프로토콜별로 다른 액세스 규칙을 허용합니다
- 리소스가 업데이트될 때 클라이언트에 변경 사항을 푸시하는 실시간 구독을 위해 Mercure를 활성화합니다
- 커서 기반 페이지네이션은 페이지 기반보다 실시간 데이터를 더 잘 처리하지만 직접 페이지 액세스를 희생합니다
ApiTestCase와/graphql에 대한 JSON POST 요청을 사용하여 GraphQL 엔드포인트를 테스트합니다- 면접 질문은 스키마 생성, N+1 문제, 보안 분리, 커스텀 리졸버에 초점을 맞춥니다
Symfony 코드의 버그를 찾을 수 있나요
실제 코드 한 조각, 숨은 버그 하나, 하루 한 번. 계정 없이 바로 도전할 수 있습니다.

작성자
Anthony Fillion-MailletSharpSkill 창업자
10년 이상 풀스택 개발을 해왔습니다. SharpSkill을 운영하며 이곳에 게시되는 모든 내용에 책임을 집니다.
2026년 8월 27일 업데이트
공유
관련 기사

2026년 Symfony REST API 보안 완벽 가이드: OAuth2, 속도 제한, 면접 대비
Symfony 7.3에서 구현하는 REST API 보안 실무 가이드. OAuth2 토큰 인트로스펙션, RateLimiter 컴포넌트, Voter 인가, 기술 면접 대비를 다룹니다.

2026년 API Platform과 Symfony: 아키텍처 설계 및 기술 면접 완벽 가이드
API Platform 4.2와 Symfony 7.4를 활용한 REST API 개발 최신 기법을 상세히 설명합니다. State Provider, State Processor, Object Mapper, JSON Streamer를 통한 32% 성능 향상까지, 기술 면접에서 자주 묻는 핵심 개념을 다룹니다.

Symfony REST API 보안: JWT 인증과 면접 질문 2026년 완벽 가이드
Symfony REST API 보안 구현에 대한 종합 가이드. JWT 인증, 방화벽 설정, 리프레시 토큰, 그리고 2026년 기술 면접에서 자주 출제되는 질문과 답변을 다룬다.