풀스택 개발 스킬 (UI & 백엔드)은(는) 무엇을 하나요?
이 스킬은 프론트엔드(UI)와 백엔드 모두에서 최신 TypeScript/JavaScript를 사용하여 프로덕션 등급의 웹 애플리케이션을 개발하기 위한 종단간 아키텍처 가이드, 워크플로 런북 및 코딩 표준을 제공합니다.
1. 핵심 아키텍처 원칙
- 종단간 타입 안전성(End-to-End Type Safety):
- 도메인 모델 및 데이터 전송 객체(DTO)에 대한 단일 진실 공급원.
- Zod와 같은 스키마 검증 라이브러리를 사용하여 런타임에 요청을 구문 분석 및 검증하고 컴파일 타임에 TypeScript 타입을 유추합니다.
- 계층형 관심사 분리(Layered Separation of Concerns):
- 프론트엔드: 프레젠테이션 기본 요소, 비즈니스/컨테이너 컴포넌트 및 데이터 페칭 훅을 엄격히 분리합니다.
- 백엔드: 라우팅/컨트롤러(HTTP 전송), 서비스 계층(비즈니스 로직) 및 데이터 액세스/리포지토리 계층(데이터베이스/ORM)을 엄격히 분리합니다.
- API 계약 우선(API Contract First):
- UI 또는 백엔드 세부 사항을 구현하기 전에 요청 및 응답 계약을 수립합니다.
- 통일된 응답 형식:
{ success: true, data: T }또는{ success: false, error: { message, code, details } }.
- 복원력 및 오류 처리(Resilience and Error Handling):
- 프론트엔드: 오류 경계, 스켈레톤 로딩 상태, 적절한 폴백(fallback) 및 사용자 친화적인 알림.
- 백엔드: 중앙 집중식 오류 미들웨어, 운영 오류 대 프로그래머 오류 분류, 구조화된 로깅 및 페일 세이프 기본값.
- 기본 보안(Security by Default):
- 보안 헤더(Helmet), 명시적인 CORS 정책, 입력 검증/삭제, 속도 제한 및 안전한 인증(HTTP 전용 쿠키 또는 베어러 토큰).
2. 권장 디렉토리 구조
풀스택 저장소(모노레포 또는 분할 구조)의 경우:
├── packages/ 또는 apps/
│ ├── client/ # 프론트엔드 UI (React / Next.js / Vite)
│ │ ├── src/
│ │ │ ├── assets/ # 정적 자산 (이미지, 아이콘, 폰트)
│ │ │ ├── components/ # UI 컴포넌트 (atoms, molecules, organisms)
│ │ │ │ ├── ui/ # 핵심 디자인 시스템 기본 요소 (Button, Input, Modal)
│ │ │ │ └── features/ # 도메인별 복합 컴포넌트
│ │ │ ├── hooks/ # 커스텀 React 훅 (useAuth, useDebounce 등)
│ │ │ ├── services/ # API 클라이언트 및 서비스 통합
│ │ │ ├── store/ # 전역 상태 관리 (Zustand, Context 등)
│ │ │ ├── types/ # 클라이언트별 타입
│ │ │ ├── utils/ # 헬퍼 함수 및 포맷팅 유틸리티
│ │ │ ├── App.tsx # 메인 애플리케이션 컴포넌트 및 라우트
│ │ │ ├── main.tsx # 진입점
│ │ │ └── index.css # 글로벌 CSS 및 디자인 토큰
│ │ └── package.json
│ │
│ ├── server/ # 백엔드 API (Node.js / Express / Fastify)
│ │ ├── src/
│ │ │ ├── config/ # 환경 변수 및 런타임 구성
│ │ │ ├── controllers/ # HTTP 라우트 핸들러 (파라미터 추출, 서비스 호출)
│ │ │ ├── middleware/ # 인증, 검증, 오류 처리기, 속도 제한기
│ │ │ ├── models/ # 데이터베이스 모델 / 스키마 (Prisma, Drizzle 등)
│ │ │ ├── routes/ # Express/Fastify 라우트 정의
│ │ │ ├── services/ # 비즈니스 로직 및 외부 API 상호작용
│ │ │ ├── utils/ # 백엔드 헬퍼 (jwt, logger, crypto)
│ │ │ ├── app.ts # Express/Fastify 앱 구성
│ │ │ └── server.ts # 서버 진입점 및 정상 종료
│ │ └── package.json
│ │
│ └── shared/ # 공유 스키마, DTO 및 TypeScript 계약
│ ├── src/
│ │ ├── schemas/ # Zod 스키마 (user.schema.ts, auth.schema.ts)
│ │ └── types/ # 유추된 타입 (export type User = z.infer<...>)
│ └── package.json
3. 단계별 개발 워크플로우
풀스택 기능을 구현할 때는 다음 단계를 순서대로 실행하십시오:
1단계: 공유 계약 및 스키마 정의
shared/또는common/에 요청 페이로드 및 응답 엔티티를 나타내는 Zod 스키마를 정의합니다.- 유추된 TypeScript 타입을 내보냅니다.
import { z } from 'zod';
export const CreateUserSchema = z.object({
email: z.string().email('잘못된 이메일 주소'),
name: z.string().min(2, '이름은 최소 2자 이상이어야 합니다'),
role: z.enum(['admin', 'user']).default('user'),
});
export type CreateUserInput = z.infer<typeof CreateUserSchema>;
export type UserResponse = CreateUserInput & { id: string; createdAt: string };
2단계: 백엔드 서비스 및 엔드포인트 구축
- 검증 미들웨어(Validation Middleware): Zod 스키마로 검증 미들웨어를 적용하여 잘못된 입력을 HTTP 400으로 조기에 거부합니다.
- 서비스 계층(Service Layer): 서비스 클래스 또는 모듈에 순수 비즈니스 로직을 작성합니다. 데이터베이스 쿼리를 HTTP 개념(
req,res)과 분리합니다. - 컨트롤러(Controller): HTTP 입력 추출을 처리하고, 서비스를 호출하며,
sendSuccess()를 사용하여 표준화된 응답을 반환합니다. - 오류 처리(Error Handling): 사용자 정의 오류(
NotFoundError,BadRequestError,UnauthorizedError)를 사용하고 중앙 집중식 오류 미들웨어가 응답을 캐치하고 포맷하도록 합니다.
3단계: 프론트엔드 UI 컴포넌트 구축
- 디자인 시스템 토큰(Design System Tokens): 색상, 타이포그래피, 간격 및 그림자에 대한 CSS 사용자 정의 속성 또는 디자인 시스템 변수가 존재하는지 확인합니다.
- 원자적 / UI 기본 요소(Atomic / UI Primitives): 키보드 탐색 및 ARIA 속성을 사용하여 기본 UI 컴포넌트(버튼, 입력 필드, 배지, 대화상자)를 구축하거나 재사용합니다.
- 기능 뷰 / 페이지(Feature View / Page): 깔끔한 시각적 계층 구조, 넉넉한 여백, 현대적인 타이포그래피 및 반응형 그리드/플렉스박스 레이아웃으로 복합 컴포넌트를 조립합니다.
- 상태 및 미세 상호작용(State & Micro-Interactions):
- 호버, 활성, 포커스-가시적 및 비활성화 상태에 대한 시각적 피드백을 제공합니다.
- 보류 중인 제출 중에 로딩 스켈레톤, 스피너 표시기 및 비활성화된 버튼을 포함합니다.
4단계: 통합 및 데이터 페칭
- API 클라이언트(API Client): 기본 URL, 타임아웃, 자격 증명 및 인터셉터로 구성된 중앙 집중식 fetch 래퍼 또는 Axios 인스턴스를 사용합니다.
- 서버 상태(Server State): TanStack Query (React Query) 또는 표준 훅을 사용하여 데이터를 쿼리하고 변경합니다.
- 성공적인 변형 시 캐시 무효화.
- 빠른 사용자 경험을 위해 적절한 경우 낙관적 업데이트.
- 폼 처리(Form Handling): React Hook Form + Zod 리졸버와 통합하여 클라이언트와 서버 간에 정확히 동일한 검증 규칙을 공유합니다.
5단계: 검증 및 강화
- 백엔드 검증(Backend Verification): 단위/통합 테스트 또는 curl/fetch를 사용하여 엣지 케이스(잘못된 입력, 누락된 토큰, 중복 키)를 검증하는 엔드포인트를 테스트합니다.
- 프론트엔드 검증(Frontend Verification): 데스크톱, 태블릿 및 모바일 뷰포트 너비에서 반응형을 확인합니다.
- 보안 확인(Security Check): CORS, 속도 제한 및 인증 가드가 활성화되어 있는지 확인합니다.
4. 빠른 코드 패턴
표준 API 응답 헬퍼 (백엔드)
// server/src/utils/response.ts
import { Response } from 'express';
export function sendSuccess<T>(res: Response, data: T, statusCode = 200, meta?: unknown) {
return res.status(statusCode).json({
success: true,
data,
...(meta ? { meta } : {}),
});
}
export function sendError(res: Response, message: string, statusCode = 500, details?: unknown) {
return res.status(statusCode).json({
success: false,
error: {
message,
statusCode,
details: details ?? null,
},
});
}
중앙 집중식 Express 오류 미들웨어
// server/src/middleware/errorHandler.ts
import { Request, Response, NextFunction } from 'express';
import { ZodError } from 'zod';
export class AppError extends Error {
constructor(public message: string, public statusCode = 500, public details?: unknown) {
super(message);
this.name = 'AppError';
}
}
export function errorHandler(err: unknown, req: Request, res: Response, next: NextFunction) {
if (err instanceof ZodError) {
return res.status(400).json({
success: false,
error: {
message: '검증 실패',
statusCode: 400,
details: err.flatten().fieldErrors,
},
});
}
if (err instanceof AppError) {
return res.status(err.statusCode).json({
success: false,
error: {
message: err.message,
statusCode: err.statusCode,
details: err.details ?? null,
},
});
}
console.error('처리되지 않은 서버 오류:', err);
return res.status(500).json({
success: false,
error: {
message: '내부 서버 오류',
statusCode: 500,
},
});
}
타입 안전 API 클라이언트 (프론트엔드)
// client/src/services/apiClient.ts
interface ApiResponse<T> {
success: boolean;
data: T;
error?: {
message: string;
statusCode: number;
details?: unknown;
};
}
export async function apiRequest<T>(url: string, options?: RequestInit): Promise<T> {
const res = await fetch(url, {
headers: {
'Content-Type': 'application/json',
...options?.headers,
},
...options,
});
const body: ApiResponse<T> = await res.json();
if (!res.ok || !body.success) {
throw new Error(body.error?.message || `HTTP ${res.status}: 요청 실패`);
}
return body.data;
}
5. 참조 및 심층 학습
심층적인 아키텍처 패턴을 확인하려면 다음을 참조하십시오: