TypeScript 주니어 시스템 설계 기술면접
새 면접Q. 사용자 게시글 CRUD 기능을 제공하는 RESTful API를 설계한다고 가정합니다. 각 엔드포인트의 HTTP 메서드, URL 패턴, 요청/응답 타입을 TypeScript 인터페이스로 어떻게 정의하시겠습니까? 또한 에러 응답 구조도 함께 설계해주세요.
게시글 CRUD는 GET /posts (목록 조회), GET /posts/:id (단건 조회), POST /posts (생성), PUT /posts/:id (수정), DELETE /posts/:id (삭제)로 구성합니다. 요청/응답 타입은 interface로 정의하며, CreatePostRequest, UpdatePostRequest, PostResponse 등으로 명확히 분리합니다. 에러 응답은 status, message, code 필드를 포함한 ErrorResponse 인터페이스로 통일하여 일관성을 유지합니다. 모든 API 응답은 제네릭 타입 ApiResponse<T>로 래핑하여 성공/실패를 타입 안전하게 처리합니다.
- • RESTful 규칙에 따른 HTTP 메서드와 URL 패턴 설계 (GET, POST, PUT, DELETE)
- • 요청/응답 데이터를 TypeScript 인터페이스로 명확히 정의
- • 에러 응답 구조 통일 및 제네릭을 활용한 타입 안전성 확보
만약 게시글에 댓글 기능을 추가한다면, 중첩 리소스 URL을 어떻게 설계하시겠습니까? /posts/:id/comments 형태가 적절한지 설명해주세요.
Q. 전자상거래 시스템에서 사용자, 상품, 주문, 주문상세 테이블을 설계해야 합니다. 각 테이블의 주요 컬럼과 관계(1:N, N:M)를 설명하고, TypeScript에서 이를 표현하기 위한 타입 정의 방법을 제시해주세요. 특히 외래키 관계를 타입으로 어떻게 표현할 수 있을까요?
User 테이블(id, email, name), Product 테이블(id, name, price, stock), Order 테이블(id, userId, totalAmount, createdAt), OrderItem 테이블(id, orderId, productId, quantity, price)로 구성합니다. User와 Order는 1:N, Order와 OrderItem은 1:N, Product와 OrderItem은 1:N 관계입니다. TypeScript에서는 각 테이블을 interface로 정의하고, 외래키는 해당 엔티티의 id 타입(예: userId: User['id'])으로 표현하거나, 관계 데이터를 포함할 때는 중첩 객체로 정의합니다. 조인 결과는 별도의 DTO 인터페이스(예: OrderWithItems)로 정의하여 명확히 구분합니다.
- • 핵심 엔티티와 테이블 간 관계(1:N, N:M) 파악
- • 외래키를 타입 안전하게 표현하는 방법 (indexed access type 활용)
- • 조인 결과와 엔티티를 별도 타입으로 분리하여 명확성 확보
만약 상품에 여러 카테고리를 지정할 수 있다면(N:M 관계), 중간 테이블을 어떻게 설계하고 TypeScript 타입으로 어떻게 표현하시겠습니까?
Q. JWT 기반 인증 시스템을 구현할 때, 액세스 토큰과 리프레시 토큰을 어떻게 관리해야 하나요? TypeScript에서 토큰 페이로드의 타입을 정의하는 방법과 함께, 토큰 저장 위치(localStorage vs Cookie)의 보안적 차이점을 설명해주세요.
액세스 토큰은 짧은 만료 시간(15분~1시간)으로 설정하고, 리프레시 토큰은 긴 만료 시간(7~30일)으로 설정하여 액세스 토큰 갱신에 사용합니다. 토큰 페이로드는 interface JwtPayload { userId: string; email: string; exp: number; iat: number } 형태로 정의합니다. localStorage는 XSS 공격에 취약하므로, HttpOnly 쿠키에 저장하는 것이 더 안전합니다. 액세스 토큰은 메모리나 상태 관리에, 리프레시 토큰은 HttpOnly 쿠키에 저장하는 방식을 권장합니다.
- • 액세스 토큰과 리프레시 토큰의 역할 분리 및 만료 시간 차별화
- • JWT 페이로드를 TypeScript 인터페이스로 타입 안전하게 정의
- • HttpOnly 쿠키 사용으로 XSS 공격 방어 및 보안 강화
CSRF 공격을 방어하기 위해 쿠키 기반 인증에서 추가로 적용해야 할 보안 조치는 무엇인가요?
Q. 자주 조회되지만 변경이 드문 상품 목록 API의 성능을 개선하기 위해 캐싱을 도입하려고 합니다. 메모리 캐시(Redis 등)를 사용할 때 고려해야 할 캐시 무효화(Cache Invalidation) 전략과 TTL 설정 방법을 설명하고, TypeScript에서 캐시 레이어를 추상화하는 방법을 제시해주세요.
캐시 무효화 전략으로는 TTL 기반 자동 만료, 데이터 변경 시 명시적 삭제(Write-Through), 태그 기반 그룹 무효화를 사용할 수 있습니다. 상품 목록은 TTL을 5~10분으로 설정하고, 상품 수정/삭제 시 해당 캐시를 즉시 삭제합니다. TypeScript에서는 interface CacheService { get<T>(key: string): Promise<T | null>; set<T>(key: string, value: T, ttl?: number): Promise<void>; delete(key: string): Promise<void> } 형태로 추상화하여 구현체(Redis, Memcached)를 교체 가능하게 합니다. 제네릭을 활용해 타입 안전성을 보장합니다.
- • TTL 기반 자동 만료와 명시적 무효화 전략 조합
- • 데이터 특성에 따른 적절한 TTL 설정
- • 제네릭을 활용한 타입 안전한 캐시 인터페이스 추상화
캐시 스탬피드(Cache Stampede) 문제는 무엇이며, 이를 방지하기 위한 방법은 무엇인가요?
Q. 대량의 데이터를 조회하는 API에서 페이지네이션을 구현할 때, Offset 기반 방식과 Cursor 기반 방식의 차이점과 장단점을 설명해주세요. 각 방식을 TypeScript로 구현할 때 요청/응답 타입을 어떻게 정의하시겠습니까?
Offset 기반 방식은 page, limit 파라미터로 구현하며 특정 페이지로 바로 이동 가능하지만, 데이터 추가/삭제 시 중복/누락이 발생할 수 있습니다. Cursor 기반 방식은 마지막 항목의 고유 식별자를 기준으로 다음 데이터를 조회하여 실시간 데이터 변경에 안정적이지만 특정 페이지로 직접 이동이 불가능합니다. Offset 방식은 interface OffsetPaginationRequest { page: number; limit: number }, Cursor 방식은 interface CursorPaginationRequest { cursor?: string; limit: number }로 정의하고, 응답은 제네릭 interface PaginatedResponse<T> { data: T[]; hasNext: boolean; total?: number; nextCursor?: string }로 통일합니다.
- • Offset 방식의 직관성과 Cursor 방식의 데이터 일관성 트레이드오프
- • 각 방식의 사용 시나리오 구분 (정적 데이터 vs 실시간 피드)
- • 제네릭을 활용한 재사용 가능한 페이지네이션 타입 정의
무한 스크롤 UI를 구현할 때는 어떤 페이지네이션 방식이 더 적합하며, 그 이유는 무엇인가요?
아직 댓글이 없습니다. 첫 번째 댓글을 남겨보세요!