[week18] 프로젝트 : 오픈소스 기반의 웹 파이프라인 구축 (10) - 05/12

Kyulee·2026년 5월 17일

TIL 

목록 보기
86/93
post-thumbnail

이번 시간에는 웹 기반 문서 편집기 프로젝트의 FE 구조 설계서, BE 구조 설계서, 데이터베이스 설계, 개발 환경 셋업, 그리고 세부 UI 컴포넌트 구현까지 진행했습니다.


1. FE 구조 설계서

컴포넌트 구조 설계

프론트엔드는 페이지 단위가 아닌 컴포넌트 단위로 설계합니다. 재사용 가능한 단위로 쪼개두면 나중에 화면이 추가되거나 수정될 때 훨씬 유연하게 대응할 수 있습니다.

components/
├── common/
│   ├── Button.tsx         # 공통 버튼
│   ├── Input.tsx          # 공통 인풋
│   ├── Modal.tsx          # 공통 모달
│   └── Toast.tsx          # 알림 토스트
├── layout/
│   ├── Header.tsx         # 헤더
│   ├── Sidebar.tsx        # 사이드바 (문서 목록)
│   └── Layout.tsx         # 전체 레이아웃 래퍼
└── editor/
    ├── Editor.tsx          # 마크다운 에디터
    ├── Preview.tsx         # 실시간 미리보기
    └── Toolbar.tsx         # 서식 도구 모음

상태 관리 설계

전역 상태와 서버 상태를 명확히 구분하여 관리합니다.

상태 종류관리 도구주요 데이터
서버 상태TanStack Query문서 목록, 문서 상세, 사용자 정보
전역 클라이언트 상태Context API로그인 여부, 테마 설정
로컬 상태useState에디터 입력값, 모달 열림 여부

라우팅 설계

const router = createBrowserRouter([
  { path: '/',          element: <Home /> },
  { path: '/login',     element: <Login /> },
  { path: '/signup',    element: <SignUp /> },
  { path: '/editor',    element: <Editor /> },       // 새 문서
  { path: '/editor/:id', element: <Editor /> },      // 기존 문서 편집
  { path: '/view/:id',  element: <Viewer /> },       // 공개 문서 읽기
]);

2. 개발 환경 셋업 (FE)

프로젝트 초기 세팅

# Vite + React + TypeScript 프로젝트 생성
npm create vite@latest document-editor -- --template react-ts
cd document-editor
npm install

주요 패키지 설치

# 라우팅
npm install react-router-dom

# 서버 상태 관리
npm install @tanstack/react-query

# 스타일링
npm install styled-components
npm install -D @types/styled-components

# 폼 관리
npm install react-hook-form

# HTTP 통신
npm install axios

# 마크다운
npm install react-markdown

절대 경로 설정

vite.config.ts 에 alias를 설정하면 상대 경로 대신 절대 경로로 임포트할 수 있어 코드가 훨씬 깔끔해집니다.

// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
    },
  },
});

3. 세부 UI 컴포넌트 설계와 구현

공통 Button 컴포넌트

interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  size?: 'small' | 'medium' | 'large';
  scheme?: 'primary' | 'secondary' | 'danger';
  isLoading?: boolean;
}

function Button({ size = 'medium', scheme = 'primary', isLoading, children, ...props }: ButtonProps) {
  return (
    <ButtonStyled size={size} $scheme={scheme} {...props}>
      {isLoading ? '처리 중...' : children}
    </ButtonStyled>
  );
}

에디터 레이아웃

마크다운 에디터는 좌측 편집창 / 우측 미리보기 의 2단 레이아웃으로 구성합니다.

function EditorPage() {
  const [content, setContent] = useState('');

  return (
    <EditorLayout>
      <EditorPane>
        <textarea
          value={content}
          onChange={(e) => setContent(e.target.value)}
          placeholder="마크다운으로 작성하세요..."
        />
      </EditorPane>
      <PreviewPane>
        <ReactMarkdown>{content}</ReactMarkdown>
      </PreviewPane>
    </EditorLayout>
  );
}

자동 저장 커스텀 훅

문서를 일정 시간마다 자동 저장하는 기능은 커스텀 훅으로 분리합니다.

function useAutoSave(content: string, docId: number) {
  useEffect(() => {
    const timer = setTimeout(async () => {
      await saveDocument(docId, content);
    }, 2000); // 2초 뒤 자동 저장

    return () => clearTimeout(timer);
  }, [content, docId]);
}

4. BE 구조 설계서

백엔드 폴더 구조

src/
├── controllers/   # 요청을 받아 서비스 호출 후 응답 반환
├── services/      # 비즈니스 로직 처리
├── repositories/  # 데이터베이스 접근 레이어
├── models/        # 데이터 모델 정의
├── middlewares/   # 인증, 에러 처리 등 미들웨어
├── routes/        # API 엔드포인트 정의
└── utils/         # 유틸리티 함수

Controller → Service → Repository 의 3계층 구조로 분리하면 각 레이어의 역할이 명확해지고 테스트 작성이 쉬워집니다.


5. 데이터베이스 설계

ERD (Entity Relationship Diagram)

Users
├── id          INT (PK, AUTO_INCREMENT)
├── email       VARCHAR(255) UNIQUE NOT NULL
├── password    VARCHAR(255) NOT NULL
├── name        VARCHAR(100) NOT NULL
└── created_at  DATETIME

Documents
├── id          INT (PK, AUTO_INCREMENT)
├── title       VARCHAR(255) NOT NULL
├── content     TEXT
├── is_public   BOOLEAN DEFAULT FALSE
├── author_id   INT (FK → Users.id)
├── created_at  DATETIME
└── updated_at  DATETIME

주요 설계 결정 사항

  • 소프트 삭제(Soft Delete) — 문서를 바로 삭제하지 않고 deleted_at 컬럼으로 삭제 여부를 관리합니다. 복구 가능성을 열어두기 위함입니다.
  • 인덱스 설정author_id, is_public 컬럼에 인덱스를 추가해 조회 성능을 향상시킵니다.
  • 비밀번호 암호화 — 비밀번호는 bcrypt 로 해싱하여 저장합니다. 평문으로 저장하지 않습니다.

6. 개발 환경 셋업 (BE) & 사용자 인증 및 인가

BE 개발 환경 셋업

# Express + TypeScript 프로젝트 초기화
npm init -y
npm install express
npm install -D typescript ts-node @types/express @types/node nodemon

# 인증 관련 패키지
npm install bcrypt jsonwebtoken
npm install -D @types/bcrypt @types/jsonwebtoken

# 데이터베이스
npm install mysql2

사용자 인증 구조

인증은 JWT(JSON Web Token) 방식으로 구현합니다.

1. 로그인 요청 → 서버에서 이메일·비밀번호 검증
2. 검증 성공 → Access Token 발급
3. 이후 요청 시 헤더에 토큰 포함 (Authorization: Bearer <token>)
4. 서버에서 토큰 유효성 검사 후 요청 처리
// 인증 미들웨어
const authMiddleware = (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1];

  if (!token) {
    return res.status(401).json({ message: '인증이 필요합니다.' });
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch {
    return res.status(401).json({ message: '유효하지 않은 토큰입니다.' });
  }
};

인가 처리

인증(Authentication) 은 사용자가 누구인지 확인하는 것이고, 인가(Authorization) 는 해당 사용자가 특정 리소스에 접근할 권한이 있는지 확인하는 것입니다. 예를 들어 로그인한 사용자가 자신의 문서에만 수정·삭제 권한을 가지도록 제어합니다.

// 문서 수정 전 소유자 확인
const doc = await documentService.findById(docId);

if (doc.authorId !== req.user.id) {
  return res.status(403).json({ message: '권한이 없습니다.' });
}
profile
안녕하세요 매일의 배움을 기록으로 자산화하는 개발자 이규현입니다 😊

0개의 댓글