3.SW개발/GraphQL 배우기

7편. GraphQL에서 Enum 타입 설계 전략

쿼드큐브 2025. 12. 1. 08:50
반응형
반응형

 

7편. GraphQL에서 Enum 타입 설계 전략

 

📚 목차
1. Enum 정의 및 제약 처리
2. 프론트 연동 시 고려점
3. Enum 기반 조건 분기
4. 실무 예제: Role / Status Enum 설계
✔ 마무리 - Enum 기반 안정성 확보

 

Enum(열거형)은 제한된 값 집합을 코드로 명확하게 정의하여, 타입 안정성, 데이터 무결성, 가독성, 유지보수성을 동시에 확보할 수 있는 도구입니다.


이번 에서는 GraphQL API에서 사용자 권한(Role)과 계정 상태(Status)를 Enum으로 설계·활용하는 방법을 다룹니다.

GraphQL Enum 타입 설계 전략 삽화 이미지
GraphQL Enum 타입 설계 전략 삽화 이미지


📂 [GitHub 실습 코드 보러가기] (https://github.com/cericube/graphql-tutorial-server)

 

1. Enum 정의 및 제약 처리

GraphQL과 TypeScript 모두에서 Enum(열거형)을 지원하지만, 정의 방식과 사용 목적이 조금 다릅니다.

 

이번 예제에서는 GraphQL SDL과 TypeScript enum을 함께 사용하여, API 요청·응답과 서버 내부 로직에서 동일한 값 집합을 강제합니다.

 

🔷 1) GraphQL Enum 정의

GraphQL에서는 스키마(Schema) SDL에서 enum 키워드를 사용합니다.

# src/modules/user/user.schema.ts 일부
  
# 사용자 역할을 정의하는 Enum
# - ADMIN: 관리자 권한
# - MANAGER: 매니저 권한
# - USER: 일반 사용자
enum UserRole {
  ADMIN
  MANAGER
  USER
}

# 사용자 계정 상태를 정의하는 Enum
# - ACTIVE: 활성 상태 (서비스 이용 가능)
# - INACTIVE: 비활성 상태 (로그인 불가)
# - SUSPENDED: 정지 상태 (관리자에 의해 차단)
enum UserStatus {
  ACTIVE
  INACTIVE
  SUSPENDED
}

클라이언트가 전송할 수 있는 값과 서버가 반환할 수 있는 값을 스키마 수준에서 강제합니다.
GraphQL Playground 또는 GraphiQL 환경에서 허용 가능한 값 목록을 자동완성으로 제공합니다.
값은 문자열 따옴표 없이 정의하며, 요청·응답 시에도 동일한 형식으로 사용합니다.

 

🔷 TypeScript Enum 정의

서버 내부 로직(service.ts)에서는 TypeScript의 enum 문법을 사용합니다

// src/modules/user/user.service.ts 일부

// 사용자 역할(Role) Enum
// - ADMIN: 관리자
// - MANAGER: 매니저
// - USER: 일반 사용자
export enum UserRole {
  ADMIN = 'ADMIN',
  MANAGER = 'MANAGER',
  USER = 'USER',
}

// 사용자 상태(Status) Enum
// - ACTIVE: 활성
// - INACTIVE: 비활성
// - SUSPENDED: 정지
export enum UserStatus {
  ACTIVE = 'ACTIVE',
  INACTIVE = 'INACTIVE',
  SUSPENDED = 'SUSPENDED',
}

IDE 자동완성과 타입 검사를 지원하여 개발 효율과 코드 안정성을 높입니다.

값 비교 시 오타를 방지하고, 허용되는 값 집합을 코드에서 직접 확인할 수 있습니다.

GraphQL SDL과 동일한 값으로 유지하여 프론트엔드와 백엔드 간 데이터 불일치를 예방합니다.

 

🔷 설계 포인트

1. 변경 가능성 검토

Role, Status처럼 시스템 전역에서 공통으로 사용되고 변경 가능성이 낮은 값은 Enum으로 정의합니다.

외부 요인이나 비즈니스 정책에 따라 자주 변하는 값은 Enum 대신 DB 테이블이나 설정 파일로 관리하는 것이 안전합니다.

 

2. 명명 규칙 통일
Enum 값은 대문자 스네이크 케이스(ADMIN, ACTIVE)를 사용하면 GraphQL, 서버, 프론트 간에 표기 방식이 일관됩니다.

 

3. GraphQL ↔ TypeScript 일치
GraphQL SDL과 TypeScript Enum 정의를 동일한 값 집합으로 유지해 요청·응답 데이터 무결성을 확보합니다.

4. IDE & Playground 지원 활용
TypeScript에서는 자동완성과 타입 검사로 개발 생산성을 높이고, GraphQL에서는 요청 시 값 자동완성과 유효성 검사를 활용하여 잘못된 값 입력을 방지합니다.

 

2. 프론트 연동 시 고려점

프론트엔드에서는 API 응답에서 전달된 Enum 값을 그대로 화면에 노출하지 않고, 표시용 라벨 매핑을 별도로 두는 것이 바람직합니다.

이 방식은 다국어 지원, UI 유지보수, 사용자 친화적인 표시 측면에서 큰 이점을 제공합니다.

 

🔷 1) 라벨 매핑 구조

// 프론트 공용 Enum 라벨 매핑
export const UserRoleLabels = {
  ADMIN: '관리자',
  MANAGER: '매니저',
  USER: '사용자',
};

export const UserStatusLabels = {
  ACTIVE: '활성',
  INACTIVE: '비활성',
  SUSPENDED: '정지',
};

 

🔷 2) 요청문 예시 (Request)

사용자가 계정 상태를 변경하는 화면에서, Enum 값을 그대로 서버에 전송합니다.

# GraphQL Mutation 요청
mutation {
  changeUserStatus(userId: "2", newStatus: ACTIVE) {
    id
    email
    role
    status
  }
}

newStatus에 "ACTIVE"를 전달합니다.
문자열 따옴표 없이 GraphQL Enum 값 그대로 사용합니다.

 

🔷 3) 응답문 예시 (Response)

서버는 변경된 계정 정보를 Enum 값으로 반환합니다.

{
  "data": {
    "changeUserStatus": {
      "id": "2",
      "email": "manager@test.com",
      "role": "MANAGER",
      "status": "ACTIVE"
    }
  }
}

 

🔷 4) 프론트 변환 로직 예시

프론트에서는 응답받은 Enum 값을 라벨 매핑을 통해 사용자 친화적인 형태로 변환합니다.

import { UserRoleLabels, UserStatusLabels } from './enum-labels';

// GraphQL 응답 예시
const user = {
  id: '2',
  email: 'manager@test.com',
  role: 'MANAGER',
  status: 'ACTIVE'
};

// 변환 후 UI 표시
console.log(UserRoleLabels[user.role]);   // "매니저"
console.log(UserStatusLabels[user.status]); // "활성"

이렇게 변환된 값은 UI에 표시됩니다.

 

✔️ 실무 팁 - 프론트에서 Enum 활용 최적화

1. 요청 시
Enum 값을 그대로 전송하여 서버에서 값 검증이 용이하도록 합니다.
예: newStatus: ACTIVE

 

2. 응답 시
서버에서 Enum 값을 그대로 수신하고, 화면 표시 시 라벨 매핑을 통해 사용자 친화적인 형태로 변환합니다.

 

3. 다국어 지원
라벨 데이터만 교체하면 UI 전체 언어를 손쉽게 변경할 수 있습니다.
예: ADMIN → 한국어: "관리자", 영어: "Administrator"

4. UI 수정 최소화
Enum 값이 변경되더라도 매핑 데이터만 수정하면 모든 화면에 자동 반영됩니다.

5. 재사용성 확보
React/Vue에서는 라벨 변환 로직을 헬퍼 함수나 공용 컴포넌트로 만들어, 요청·응답 처리 흐름을 단순화하고 유지보수성을 높입니다.

 

3. Enum 기반 조건 분기

비즈니스 로직에서 문자열 대신 Enum을 사용하면, 코드 가독성과 유지보수성을 동시에 높일 수 있습니다.

특히 GraphQL API에서는 요청 값이 이미 Enum으로 제한되므로, 서버 로직에서 안전하게 조건 분기를 처리할 수 있습니다.

// src/modules/user/user.service.ts 일부
canAccessAdminPanel: (userId: string) => {
  const user = users.find(u => u.id === userId);
  return !!user && user.role === UserRole.ADMIN && user.status === UserStatus.ACTIVE;
}

'ADMIN' 같은 문자열 비교 대신 UserRole.ADMIN을 사용하면, 오타나 잘못된 값 입력을 방지할 수 있습니다.

▸ Enum을 사용하면 IDE에서 자동완성과 타입 검사가 가능해 개발 효율이 높아집니다

▸ 허용되는 값 집합을 코드만 보아도 직관적으로 파악할 수 있습니다.

▸ GraphQL 스키마에서 Enum 값 집합이 이미 정의되어 있으므로, 서버 로직과 클라이언트 요청 값이 불일치할 가능성이 줄어듭니다.

 

✔️ GraphQL 요청·응답 흐름

요청 시에는 userId만 전달하고, 서버에서는 UserRole.ADMIN과 UserStatus.ACTIVE 조건을 만족하는지 판단합니다.

# 요청 예시
query {
  canAccessAdminPanel(userId: "1")
}

# 응답 예시
{
  "data": {
    "canAccessAdminPanel": true
  }
}

 

✔️ 실무 활용 팁

조건 분기가 복잡해지는 경우, Enum-to-Function 매핑 객체를 사용하면 가독성을 높일 수 있습니다.

const roleAccessMap = {
  [UserRole.ADMIN]: () => [...adminMenus],
  [UserRole.MANAGER]: () => [...managerMenus],
  [UserRole.USER]: () => [...userMenus],
};
return roleAccessMap[user.role]?.() || [];

 

4. 실무 예제: Role / Status Enum 설계

이번 예제는 GraphQL Yoga + TypeScript 환경에서 UserRole과 UserStatus Enum을 정의하고, 사용자 목록 조회, 관리자 페이지 접근 여부 확인, 상태 변경 기능을 구현한 통합 코드입니다.

코드를 실행하면 GraphiQL에서 직접 요청과 응답을 테스트할 수 있습니다.

 

1. src/modules/user/user.schema.ts

// GraphQL SDL 정의
export const userTypeDefs = /* GraphQL */ `
  # 사용자 역할을 정의하는 Enum
  # - ADMIN: 관리자 권한
  # - MANAGER: 매니저 권한
  # - USER: 일반 사용자
  enum UserRole {
    ADMIN
    MANAGER
    USER
  }

  # 사용자 계정 상태를 정의하는 Enum
  # - ACTIVE: 활성 상태 (서비스 이용 가능)
  # - INACTIVE: 비활성 상태 (로그인 불가)
  # - SUSPENDED: 정지 상태 (관리자에 의해 차단)
  enum UserStatus {
    ACTIVE
    INACTIVE
    SUSPENDED
  }

  # User 타입 정의
  # - id: 사용자 고유 ID
  # - email: 사용자 이메일
  # - role: 사용자 권한 (UserRole Enum 사용)
  # - status: 계정 상태 (UserStatus Enum 사용)
  type User {
    id: ID!
    email: String!
    role: UserRole!
    status: UserStatus!
  }

  # 조회(Query) 타입 정의
  type Query {
    # 모든 사용자 목록 조회
    users: [User!]!

    # 특정 사용자가 관리자 페이지에 접근할 수 있는지 여부 확인
    canAccessAdminPanel(userId: ID!): Boolean!
  }

  # 변경(Mutation) 타입 정의
  type Mutation {
    # 사용자 상태 변경
    # - userId: 상태를 변경할 사용자 ID
    # - newStatus: 새로운 계정 상태 (UserStatus Enum 값)
    # - 반환값: 변경된 User 정보
    changeUserStatus(userId: ID!, newStatus: UserStatus!): User!
  }
`;

GraphQL SDL에서 Enum과 타입을 정의합니다.

UserRole과 UserStatus Enum은 API 요청·응답에서 허용되는 값 집합을 명확히 제한합니다.

GraphiQL에서 자동완성을 지원해 잘못된 값 입력을 예방합니다.

 

2. src/modules/user/user.service.ts

//src/modules/user/user.service.ts

// 사용자 역할(Role) Enum
// - ADMIN: 관리자
// - MANAGER: 매니저
// - USER: 일반 사용자
export enum UserRole {
  ADMIN = 'ADMIN',
  MANAGER = 'MANAGER',
  USER = 'USER',
}

// 사용자 상태(Status) Enum
// - ACTIVE: 활성
// - INACTIVE: 비활성
// - SUSPENDED: 정지
export enum UserStatus {
  ACTIVE = 'ACTIVE',
  INACTIVE = 'INACTIVE',
  SUSPENDED = 'SUSPENDED',
}

// User 데이터 구조 정의
// - role: UserRole Enum 타입 사용
// - status: UserStatus Enum 타입 사용
export interface User {
  id: string;
  email: string;
  role: UserRole;
  status: UserStatus;
}

// 메모리 기반 샘플 사용자 목록
// (실무에서는 DB에서 조회하는 부분)
const users: User[] = [
  { id: '1', email: 'admin@test.com', role: UserRole.ADMIN, status: UserStatus.ACTIVE },
  { id: '2', email: 'manager@test.com', role: UserRole.MANAGER, status: UserStatus.INACTIVE },
  { id: '3', email: 'user@test.com', role: UserRole.USER, status: UserStatus.SUSPENDED },
];

// 사용자 관련 서비스 로직 집합
export const userService = {
  // 모든 사용자 목록 반환
  getUsers: () => users,

  // 관리자 페이지 접근 가능 여부 확인
  // 조건: 역할이 ADMIN이고 상태가 ACTIVE여야 true 반환
  canAccessAdminPanel: (userId: string) => {
    const user = users.find((u) => u.id === userId);
    return !!user && user.role === UserRole.ADMIN && user.status === UserStatus.ACTIVE;
  },

  // 사용자 상태 변경
  // - userId로 대상 사용자 조회
  // - 정지(SUSPENDED) 상태에서 활성(ACTIVE)로 바로 변경하려고 하면 예외 발생
  changeUserStatus: (userId: string, newStatus: UserStatus) => {
    const user = users.find((u) => u.id === userId);
    if (!user) throw new Error('User not found');
    if (user.status === UserStatus.SUSPENDED && newStatus === UserStatus.ACTIVE) {
      throw new Error('Suspended accounts cannot be reactivated directly.');
    }
    user.status = newStatus;
    return user;
  },
};

TypeScript Enum을 정의해 서버 내부 로직에서 안전하게 값 비교를 수행합니다.

GraphQL SDL의 Enum과 동일한 값 집합을 유지해 데이터 불일치를 방지합니다.

 

3. src/modules/user/user.resolver.ts

// src/modules/user/user.resolver.ts

// GraphQL SDL에서 작성한 User 타입 정의 가져오기
import { userTypeDefs } from './user.schema';

// 사용자 관련 서비스 로직 모듈
import { userService, UserStatus } from './user.service';

// GraphQL Resolver 정의
// - Query: 데이터 조회용
// - Mutation: 데이터 변경용
export const userResolvers = {
  Query: {
    // 모든 사용자 목록 반환
    users: () => userService.getUsers(),

    // 특정 사용자가 관리자 페이지에 접근할 수 있는지 여부 확인
    // args:
    //  - userId: 확인할 사용자 ID
    canAccessAdminPanel: (_: undefined, { userId }: { userId: string }) =>
      userService.canAccessAdminPanel(userId),
  },
  Mutation: {
    // 사용자 상태 변경
    // args:
    //  - userId: 상태를 변경할 대상 사용자 ID
    //  - newStatus: 새로 적용할 UserStatus Enum 값
    changeUserStatus: (
      _: undefined,
      { userId, newStatus }: { userId: string; newStatus: UserStatus }
    ) => userService.changeUserStatus(userId, newStatus),
  },
};

// SDL 정의도 함께 export하여 스키마 병합 시 사용
export { userTypeDefs };

Query와 Mutation 요청을 처리하는 Resolver를 정의합니다.

canAccessAdminPanel은 특정 사용자가 관리자 페이지 접근이 가능한지 Boolean 값으로 반환합니다.

changeUserStatus는 계정 상태를 변경하며, 특정 조건(SUSPENDED → ACTIVE)은 제한합니다.

 

4. src/schema.ts, src/resolvers.ts, src/index.ts,

참조: 6편. GraphQL Scalar 타입 완전 정복

 

✔️ GraphiQL 테스트 예시

GraphQL Yoga 서버를 실행하고 /graphql 엔드포인트에서 요청을 처리합니다.
실행 후 브라우저에서 http://localhost:4000/graphql로 접속해 GraphiQL로 테스트할 수 있습니다.

 

1) 사용자 목록 조회

사용자 목록 조회 테스트 화면
사용자 목록 조회 테스트 화면

2) 관리자 페이지 접근 여부 확인

관리자 페이지 접근 여부 확인 테스트 화면
관리자 페이지 접근 여부 확인 테스트 화면

3) 사용자 상태 변경

사용자 상태 변경 확인 테스트 화면
사용자 상태 변경 확인 테스트 화면

✔️ 실무 적용 포인트

🔸 GraphQL SDL과 TypeScript Enum 동기화 → 프론트·백 간 데이터 불일치 방지.

🔸 GraphiQL 자동완성 → 잘못된 Enum 값 요청 예방.

🔸 서버 로직에서 Enum 직접 비교 → 오타 방지 및 가독성 향상.

🔸 중앙 관리 구조 → Enum 값 변경 시 한 곳만 수정하면 전체 로직과 API에 반영.

 

✔ 마무리 - Enum 기반 안정성 확보

GraphQL과 TypeScript에서 Enum을 활용하면 데이터 무결성과 유지보수성을 동시에 확보할 수 있습니다.

스키마에서 값 집합을 강제하고, 서버 로직에서는 타입 검사를 통해 안전하게 조건 분기를 처리합니다.

또한, 프론트에서는 라벨 매핑으로 다국어 지원과 UI 변경 최소화를 구현할 수 있습니다.


결국 Enum은 단순 상수가 아닌, 비즈니스 규칙을 코드로 보장하는 핵심 도구입니다.


📂 [GitHub 실습 코드 보러가기] (https://github.com/cericube/graphql-tutorial-server)


 

※ 게시된 글 및 이미지 중 일부는 AI 도구의 도움을 받아 생성되거나 다듬어졌습니다.

반응형

 

 

반응형