OPEN HYPER STEP
← 목록으로 (stack-analysis)
STACK-ANALYSIS · 31 / 90
stack-analysis
CHAPTER 31 / 90
읽기 약 2
FUNCTION

Express 프로덕션 아키텍처: 3레이어


핵심 개념

Controller·Service·Repository — 테스트 가능한 구조 + 의존성 주입.

본문

3레이어 아키텍처

📋 코드 (7줄)
src/
├── controllers/    # HTTP 요청/응답 변환
├── services/       # 비즈니스 로직
├── repositories/   # 데이터 액세스
├── middlewares/    # 인증·로깅·에러
├── routes/         # URL → controller 매핑
└── utils/

Controller (얇게)

TYPESCRIPT📋 코드 (25줄)
// controllers/post.controller.ts
import { Request, Response, NextFunction } from 'express';
import { PostService } from '@/services/post.service';

export class PostController {
  constructor(private postService: PostService) {}

  list = async (req: Request, res: Response, next: NextFunction) => {
    try {
      const { page = 1, limit = 20 } = req.query;
      const result = await this.postService.list({
        page: Number(page), limit: Number(limit),
      });
      res.json(result);
    } catch (err) { next(err); }
  };

  create = async (req: Request, res: Response, next: NextFunction) => {
    try {
      const userId = req.user!.id;  // 인증 미들웨어가 주입
      const post = await this.postService.create(userId, req.body);
      res.status(201).json(post);
    } catch (err) { next(err); }
  };
}

Service (비즈니스 로직)

TYPESCRIPT📋 코드 (36줄)
// services/post.service.ts
import { z } from 'zod';
import { PostRepository } from '@/repositories/post.repository';
import { ForbiddenError, ValidationError } from '@/utils/errors';

const CreatePostSchema = z.object({
  title: z.string().min(1).max(200),
  content: z.string().min(1),
  tags: z.array(z.string()).max(10),
});

export class PostService {
  constructor(private repo: PostRepository) {}

  async list({ page, limit }: { page: number; limit: number }) {
    const offset = (page - 1) * limit;
    const [items, total] = await Promise.all([
      this.repo.findMany({ offset, limit }),
      this.repo.count(),
    ]);
    return { items, total, page, limit, totalPages: Math.ceil(total / limit) };
  }

  async create(userId: string, data: unknown) {
    const parsed = CreatePostSchema.safeParse(data);
    if (!parsed.success) throw new ValidationError(parsed.error);
    return this.repo.create({ ...parsed.data, authorId: userId });
  }

  async update(userId: string, postId: string, data: unknown) {
    const post = await this.repo.findById(postId);
    if (!post) throw new NotFoundError('Post');
    if (post.authorId !== userId) throw new ForbiddenError();
    return this.repo.update(postId, data);
  }
}

Repository (데이터 액세스)

TYPESCRIPT📋 코드 (19줄)
// repositories/post.repository.ts
import { PrismaClient } from '@prisma/client';

export class PostRepository {
  constructor(private prisma: PrismaClient) {}

  findMany({ offset, limit }: { offset: number; limit: number }) {
    return this.prisma.post.findMany({
      skip: offset, take: limit,
      orderBy: { createdAt: 'desc' },
      include: { author: { select: { id: true, name: true } } },
    });
  }

  count() { return this.prisma.post.count(); }
  findById(id: string) { return this.prisma.post.findUnique({ where: { id } }); }
  create(data: any) { return this.prisma.post.create({ data }); }
  update(id: string, data: any) { return this.prisma.post.update({ where: { id }, data }); }
}

DI 컨테이너 (간단 버전)

TYPESCRIPT📋 코드 (22줄)
// container.ts
import { PrismaClient } from '@prisma/client';
import { PostRepository } from './repositories/post.repository';
import { PostService } from './services/post.service';
import { PostController } from './controllers/post.controller';

const prisma = new PrismaClient();

const postRepo = new PostRepository(prisma);
const postService = new PostService(postRepo);
export const postController = new PostController(postService);


// routes/post.routes.ts
import { Router } from 'express';
import { postController } from '@/container';
import { authenticate } from '@/middlewares/auth';

const router = Router();
router.get('/', postController.list);
router.post('/', authenticate, postController.create);
export default router;

다음 챕터

CH.32 "인증 시스템: Passport + JWT + OAuth2".


AI 프롬프트
🤖 AI에게 잘 물어보는 법 — 모델·전략별 프롬프트
Claude

무료: Sonnet 4.6 / Pro $20/mo: Opus 4.6

내 코드의 Express 아키텍처 부분을 분석해서
실전 분석 + 개선 우선순위를 알려줘.
ChatGPT

무료: GPT-5.5 / Plus $20/mo: GPT-5.5 Pro

Express 아키텍처 관련 인기 라이브러리/패턴 5개를
비교 분석해서 패턴 추출를 알려줘.
Gemini

무료: 2.5 Flash / Pro $19.99/mo: 3.1 Pro

내 프로젝트 전체에서 Express 아키텍처
최적화 가능 위치를 보고해줘.
Grok

무료: Grok 4.1 / SuperGrok $30/mo

2026년 한국 백엔드 시장의
Express 아키텍처 트렌드를 솔직히 알려줘.

⭐ 이것만 기억하세요
Express 프로덕션 아키텍처: 3레이어 이 3가지만 확실히 잡으세요
1.3레이어 = Controller(얇게) → Service(로직) → Repository(데이터) — 테스트 가능
2.DI(의존성 주입)로 Mock 교체 가능 — 단위 테스트 표준
3.Controller는 try/catch + next(err) — 에러 미들웨어가 처리


공유하기
진행도 31 / 90