Nest.js는 웹 애플리케이션 개발을 위한 모듈식 아키텍처를 제공하는 프레임워크입니다. GraphQL과 함께 사용하면 데이터 관리를 유연하고 효율적으로 할 수 있는데요. 이번 글에서는 카테고리 분류 기능을 구현하기 위한 GraphQL API 개발을 소개하겠습니다.
카테고리 분류 기능을 구현하기 위해 다음 네 가지 핵심 요소가 필요합니다.
엔티티(Entity)는 데이터베이스에 저장될 데이터의 구조를 정의합니다. 예를 들어, Category
엔티티는 다음과 같이 구현됩니다.
// category.entity.ts
@InputType('CategoryInputType', { isAbstract: true })
@ObjectType()
@Entity()
export class Category extends CoreEntity {
@Field(type => String)
@Column({ unique: true })
name: string;
@Field(type => String, { nullable: true })
@Column({ nullable: true })
coverImg?: string;
@Field(type => String)
@Column({ unique: true })
slug: string;
@Field(type => [Restaurant], { nullable: true })
@OneToMany(type => Restaurant, restaurant => restaurant.category)
restaurants?: Restaurant[];
}
DTO(Data Transfer Object)는 데이터 요청과 응답의 형식을 정의합니다. GraphQL API에서 CategoryInput
, CategoryOutput
, AllCategoriesOutput
DTO는 다음과 같이 작성됩니다.
// category.dto.ts
@ArgsType()
export class CategoryInput {
@Field(type => String)
slug: string;
}
@ObjectType()
export class CategoryOutput extends CoreOutput {
@Field(type => Category, { nullable: true })
category?: Category;
}
@ObjectType()
export class AllCategoriesOutput extends CoreOutput {
@Field(type => [Category], { nullable: true })
categories?: Category[];
}
slug
필드category
필드를 포함서비스(Service)는 데이터베이스에 접근하고, 필요한 비즈니스 로직을 처리합니다. 카테고리 관련 로직은 CategoriesService
에서 처리합니다.
// categories.service.ts
async findCategoryBySlug({ slug }: CategoryInput): Promise<CategoryOutput> {
try {
const category = await this.categories.findOne({ slug });
if (!category) {
return { ok: false, error: 'Category not found' };
}
return { ok: true, category };
} catch {
return { ok: false, error: 'Could not load category' };
}
}
async allCategories(): Promise<AllCategoriesOutput> {
try {
const categories = await this.categories.find();
return { ok: true, categories };
} catch {
return { ok: false, error: 'Could not load categories' };
}
}
countRestaurants(category: Category): Promise<number> {
return this.restaurants.count({
where: { category: { id: category.id } },
});
}
리졸버(Resolver)는 GraphQL 쿼리를 처리하고 서비스를 호출해 데이터를 반환합니다. CategoryResolver
는 다음과 같이 작성됩니다.
// category.resolver.ts
import { Resolver, Query, Args, ResolveField, Parent, Int } from '@nestjs/graphql';
import { CategoryInput, CategoryOutput, AllCategoriesOutput } from './dtos/category.dto';
import { CategoriesService } from './categories.service';
import { Category } from '../entities/category.entity';
@Resolver(of => Category)
export class CategoryResolver {
constructor(private readonly categoriesService: CategoriesService) {}
@Query(returns => AllCategoriesOutput)
async allCategories(): Promise<AllCategoriesOutput> {
return this.categoriesService.allCategories();
}
@Query(returns => CategoryOutput)
async category(
@Args('input') categoryInput: CategoryInput,
): Promise<CategoryOutput> {
return this.categoriesService.findCategoryBySlug(categoryInput);
}
@ResolveField(type => Int)
async restaurantCount(@Parent() category: Category): Promise<number> {
return this.categoriesService.countRestaurants(category);
}
}
allCategories
쿼리를 통해 모든 카테고리를, category
쿼리를 통해 특정 카테고리를 반환합니다.restaurantCount
필드를 추가합니다.@ResolveField
데코레이터를 사용하면 매 요청마다 계산된 필드를 가져올 수 있습니다. 예를 들어, 각 카테고리에 속한 식당 수를 계산하는 restaurantCount
필드를 구현할 수 있습니다.
@ResolveField(type => Int)
async restaurantCount(@Parent() category: Category): Promise<number> {
return this.categoriesService.countRestaurants(category);
}
Category
)를 받아 해당 데이터를 기반으로 필드를 계산합니다.이번 글에서는 GraphQL API에서 카테고리 분류 기능을 어떻게 구현하는지 살펴보았습니다. @ResolveField
를 활용해 계산된 필드를 각 카테고리마다 추가하고, 엔티티(Entity), DTO, 서비스(Service), 리졸버(Resolver)의 핵심 구조를 이해해 효율적인 카테고리 분류 기능을 구현할 수 있습니다.