NextJS

Nextjs 프로젝트에 express를 분리 한 이유!!

한우코딩 2025. 3. 6. 20:57

Next.js와 Express 서버 분리하기: 더 나은 아키텍처를 위한 여정

안녕하세요! 오늘은 제가 개발한 DevStudy Mate 프로젝트에서 기존에 Next.js에 통합되어 있던 서버 기능을 별도의 Express 서버로 분리하게 된 이유와 과정을 공유하려고 합니다.

Express란 무엇인가?

Express는 Node.js 환경에서 실행되는 웹 애플리케이션 프레임워크입니다. 미니멀하고 유연한 특성을 가지고 있어, 웹 애플리케이션과 API를 개발하기 위한 강력한 기능을 제공합니다. Node.js의 http 모듈 위에 구축되어 있으며, 라우팅, 미들웨어 구성, 템플릿 엔진 통합 등 웹 서버 개발에 필요한 다양한 기능을 쉽게 사용할 수 있게 해줍니다.

서버 분리의 필요성

1. Next.js API 라우트의 타임아웃 문제

가장 결정적인 이유 중 하나는 Next.js의 API 라우트를 사용했을 때 발생한 타임아웃 이슈였습니다. 코드 분석을 위해 OpenAI API를 호출하는 과정에서 처리 시간이 길어지면 504(Gateway Timeout) 또는 502(Bad Gateway) 에러가 발생했습니다.

Vercel이나 다른 서버리스 플랫폼에서 호스팅되는 Next.js API 라우트는 일반적으로 10~60초 사이의 타임아웃 제한이 있습니다. 복잡한 코드 파일을 분석할 때는 이 제한 시간을 초과하는 경우가 빈번했고, 이로 인해 사용자 경험이 크게 저하되었습니다.

Express 서버를 별도로 배포함으로써

  • 더 긴 타임아웃 설정이 가능해졌습니다
  • 장시간 실행되는 작업을 안정적으로 처리할 수 있게 되었습니다
  • 필요한 경우 비동기 작업 큐를 구현할 수 있는 기반이 마련되었습니다

2. 관심사 분리 (Separation of Concerns)

초기에는 Next.js의 API 라우트 기능을 사용하여 프론트엔드와 백엔드 로직을 하나의 코드베이스에서 관리했습니다. 하지만 프로젝트가 성장하면서 두 영역의 책임이 점점 더 명확히 구분되어야 했습니다.

  • 프론트엔드: 사용자 인터페이스, 상태 관리, 클라이언트 사이드 로직
  • 백엔드: 데이터베이스 연동, 인증, API 로직, 외부 서비스 통합

3. Firebase 직접 접근의 보안 문제

초기에는 프론트엔드에서 Firebase SDK를 사용하여 데이터베이스에 직접 접근했습니다. 이 방식은 편리하지만 몇 가지 문제를 가지고 있었습니다

  • 클라이언트 측 API 키 노출 위험
  • 보안 규칙만으로 복잡한 접근 패턴 제어의 어려움
  • 데이터 검증 및 비즈니스 로직의 중복

4. 확장성과 성능 최적화

서버를 분리함으로써 각 부분을 독립적으로 확장할 수 있게 되었습니다

  • 프론트엔드는 정적 콘텐츠 제공에 최적화
  • 백엔드는 API 처리와 데이터베이스 연산에 집중
  • 각 서비스의 리소스 요구사항에 맞게 별도로 스케일링 가능

5. OpenAI API 호출의 보안

프로젝트에서 중요한 부분인 코드 분석 기능은 OpenAI API를 사용합니다. API 키를 안전하게 관리하기 위해 이 호출은 서버 측에서 이루어져야 했습니다.

마이그레이션 과정: Firebase와 Express 서버 통합

1. Firebase Admin SDK 도입

클라이언트 SDK에서 Admin SDK로 전환하면서 얻은 이점:

  • 보안 규칙 우회: 서버는 모든 데이터에 완전한 접근 권한을 가집니다.
  • 인증 토큰 검증: 사용자 인증을 서버 측에서 검증할 수 있습니다.
  • 관리 기능: 사용자 관리, 데이터베이스 관리 등의 관리 작업을 수행할 수 있습니다.

2. API 엔드포인트 구현

Express 서버에 다음과 같은 주요 API 엔드포인트를 구현했습니다:

  • 노트 관리 API: 생성, 조회, 수정, 삭제 기능
  • 코드 분석 API: OpenAI를 활용한 코드 분석
  • 통계 API: 사용자 학습 통계 제공
  • 인증 콜백: GitHub OAuth 인증 처리

3. 타임아웃 제어 및 오류 처리 개선

Express 서버에서는 OpenAI API 호출에 대한 타임아웃을 더 효과적으로 제어할 수 있었습니다:

// 타임아웃 설정
const timeoutPromise = new Promise(
  (_, reject) => setTimeout(() => reject(new Error("분석 시간 초과")), 60000) // 60초 타임아웃
);

// 분석 요청과 타임아웃 경쟁
const analysis = await Promise.race([
  analyzeCode(apiKey, fileName, fileContent),
  timeoutPromise,
]);

또한 적절한 오류 처리와 사용자 피드백을 추가하여 장시간 실행되는 작업에 대한 사용자 경험을 크게 개선했습니다.

5. 클라이언트 코드 수정

프론트엔드 코드를 수정하여 Firebase 직접 접근 대신 서버 API를 호출하도록 변경했습니다:

// 변경 전: Firebase SDK 직접 사용
const docRef = await addDoc(collection(db, NOTES_COLLECTION), {
  ...note,
  createdAt: serverTimestamp(),
  updatedAt: serverTimestamp(),
});

// 변경 후: 서버 API 호출
const response = await fetch(`${API_URL}/api/note`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(note),
});

학습 및 이점

이 마이그레이션을 통해 얻은 주요 이점과 학습 포인트

1. 향상된 안정성과 사용자 경험

  • 타임아웃 문제 해결로 긴 처리 시간이 필요한 분석 작업도 안정적으로 수행
  • 504, 502 에러 발생 X
  • 사용자에게 더 정확한 진행 상황과 피드백 제공 가능

2. 향상된 보안

  • 민감한 API 키와 서비스 계정 키가 서버에만 존재
  • 모든 데이터 접근이 서버를 통해 이루어짐
  • 인증 및 권한 부여의 중앙화

3. 명확한 코드 구조

  • 프론트엔드: UI 렌더링, 사용자 상호작용, 상태 관리에 집중
  • 백엔드: 데이터 처리, 외부 API 통합, 비즈니스 로직 처리에 집중

4. 유지보수성 향상

  • 각 영역이 독립적으로 발전 가능
  • 백엔드 로직 변경이 프론트엔드에 영향을 미치지 않음
  • 테스트 용이성 증가

5. 성능 최적화

  • 프론트엔드는 빠른, 정적 콘텐츠 서빙에 집중
  • 백엔드는 데이터 처리와 계산 집약적 작업 처리
  • CDN과의 더 나은 통합 가능성

결론

Next.js 애플리케이션에서 Express 서버를 분리하는 과정은 간단하지 않았지만, 이러한 아키텍처 변경은 프로젝트의 확장성, 보안, 유지보수성을 크게 향상시켰습니다. 특히 OpenAI와 같은 처리 시간이 긴 외부 API를 호출할 때 발생하는 타임아웃 문제를 해결함으로써 사용자 경험을 크게 개선할 수 있었습니다.

이 과정에서 "가장 간단한 것이 항상 최선은 아니다"라는 교훈을 얻었습니다. 초기 개발 속도를 위해 모든 것을 Next.js에 통합했지만, 장기적인 관점에서는 관심사를 명확히 분리하는 것이 더 나은 선택이었습니다.

서버리스 기능은 많은 장점을 제공하지만, 실행 시간 제한과 같은 제약을 이해하고 프로젝트 요구사항에 맞게 아키텍처를 설계하는 것이 중요합니다. 복잡하거나 시간이 많이 소요되는 작업의 경우, 전통적인 서버 구성이 여전히 더 나은 선택일 수 있는거 같습니다 !!