Next.js에서의 데이터 통신 - tRPC vs REST API

조민수·2025년 8월 7일
0

개발

목록 보기
11/12

Next.JS 환경에서 데이터를 가져오고, 처리하는 과정 중
oracledb 모듈을 직접 import해서 api 단을 구성하고 있다.

그렇다면 이렇게 API 단과 통신하는 방식에 대해 두가지를 고려해볼 수 있다.

  • 전통적이지만 강력한 REST API 기반
    : /api/data?action=??? 을 통해 URL 문자열을 조합해 API를 호출하고 JSON타입의 데이터를 받는다.
  • tRPC라우터를 통한 명시적 프로시저 정의
    : zod를 통한 입력값 검증 및 react-query에 의존하는 데이터 fetching

REST API 방식

현재 개발 방식은 REST API 구조에 맞춰져있다.
아무래도 친숙한 방식이기도 하고, 단순 데이터 조회 쿼리(SELECT)를 통해 가져온 데이터를
route 단을 통해 전달해주면 되기 때문.

현재 작성중인 코드 중 대표적인 예시는 다음과 같다.

// src/app/api/data/route.ts
export async function GET(request: NextRequest) {
  const { searchParams } = new URL(request.url);
  const action = searchParams.get('action');

  switch (action) {
    case 'GET_NOTIFICATION':
      return await getNotification();
    case 'GET_MODULE':
      return await getModules();
    case 'GET_SENDINFO':
      return await getSendInfo();
    default:
      return NextResponse.json({ error: 'Invalid GET action' }, { status: 400 });
  }
}
  • route.tsget Action에 대해 전달할 함수 로직을 담는다.
// src/app/api/data/handler/sendInfoHandler.ts
import { executeQuery } from '@/lib/oracle';
import { NextRequest, NextResponse } from 'next/server';
import { getConnection } from '@/lib/oracle';
import oracledb from 'oracledb';

export async function getSendInfo() {
    let connection;
    try {
        connection = await getConnection();
        const result = await executeQuery(`
            select
               	...
            from ...
            where 1 = 1 
        `);
        return NextResponse.json(result);
    
    } catch(error){
        return NextResponse.json({ error: 'Failed to fetch notifications' }, { status: 500 });
    } finally {
    if (connection) {
      try {
        await connection.close();
      } catch (err) {
        console.error('Oracle DB 연결 종료 오류:', err);
      }
    }
  }
}
  • 각 핸들러는 역할에 맞는 쿼리를 통해 oracledb로 부터 데이터를 가져온다.
// app/check/page.tsx
'use client';

import { useState, useEffect } from 'react';
...

const fetchSendInfo = async (): Promise<SendInfo[]> => {
  const res = await axios.get('/api/data?action=GET_SENDINFO');
  return res.data;
};


const CheckPage = () => {
  const { data, isLoading, error, refetch } = useQuery<SendInfo[]>({
    queryKey: ['sendInfo'],
    queryFn: fetchSendInfo,
  });

  ...
  
  return (
    <Container>
      <Header>
        ...
        
        <SearchButtonContainer>
          <Button variant="outlined" color="primary" onClick={() => refetch()}>
            조회
          </Button>
        </SearchButtonContainer>
      </Header>
      
      ...
    </Container>
  );
};

export default CheckPage;

  • client단에서는 axios.get을 통해 요구 데이터를 요청하고 데이터를 fetching 해 받아온다.

해당 방식은 POST Method에서바인드 변수를 수동으로 관리하고,
별도의 타입 유효성검사 함수를 작성해야 한다는 번거로움이 있지만

아무래도 익숙한 프로토콜이며 개발방식이라는 점에서 러닝커브가 낮다는 장점이 있다.


tRPC : typeScript Remote Procedure Call

tRPCend-to-end API 라이브러리 중 하나로,
Client ↔ Server 간 통신에서 런타임 타입 건증 없이 타입 안정성을 보장하는 프로토콜이다.

핵심 특징으론
1. 서버에서 정의한 API 스키마가 Client에 전파됨
2. Compile time에 type error를 검출함
이 있다.

서버 단에선 프로시저 기반 접근을 통해

export const appRouter = router({
 getUser: publicProcedure
   .input(z.object({ id: z.string() }))
   .query(async ({ input }) => {
     // Oracle DB 조회
     const user = await db.execute(`
       SELECT ...
       FROM ..
       WHERE ...
     `, { userId: input.id });
     return user;
   })
});

다음과 같이 접근할 수 있고

클라이언트 단에선

const UserProfile = () => {
 const { data: user, isLoading } = trpc.getUser.useQuery({ id: '123' });
 const createUser = trpc.createUser.useMutation();

 if (isLoading) return <div>Loading...</div>;
 
 return (
   <UserCard>
     <UserName>{user?.USER_NAME}</UserName>
     <Email>{user?.EMAIL}</Email>
   </UserCard>
 );
};

react-query 훅 기반으로 API 함수를 호출해 데이터를 fetching하고, post할 수 있다.

당연히, react-query가 제공하는 캐싱 및 최적화를 하나의 장점으로 가져갈 수 있다.


tRPC vs REST API

비교 항목tRPCREST API
성능• JSON-RPC 기반 최적화된 페이로드
• React Query 내장 캐싱
• 추가 라이브러리 필요 (~50KB)
• WebSocket 지원, Subscription 내장
• HTTP 헤더 오버헤드 존재
• HTTP 캐싱, CDN, 브라우저 캐싱 지원
• 표준 fetch API 사용
• 별도 WebSocket 구현 필요
러닝 커브• TypeScript 필수 (고급 지식 필요)
• 새로운 RPC 패러다임
• 제한적 학습 자료
• 신규 개발자 적응 시간 필요
• TypeScript 선택사항
• 표준화된 HTTP methods 개념
• 풍부한 자료와 예제
• 대부분 개발자가 익숙함
유지보수성• 컴파일 타임 타입 체크
• 자동 타입 오류 감지
• 강제적 타입 일관성
• IDE 지원 안전한 리팩토링
• 타입 오류로 사전 방지
• 런타임 검증 필요
• 수동 테스트 및 확인
• 개발자 규칙 의존
• 런타임 오류 발생 가능
• 수동 리팩토링 확인
개발 기간• 복잡한 초기 설정
• 스키마 정의로 빠른 API 개발
• 자동 타입 생성
• 타입 검증으로 테스트 간소화
• 코드 자체가 문서 역할
• 간단한 초기 설정
• 개별 엔드포인트 구현
• 수동 타입 정의
• 전체 API 테스트 필요
• 별도 API 문서 작성
확장성• Monolithic 적합
• TypeScript 환경에 제한
• 제한적 써드파티 지원
• 중소규모 팀에 적합
• 복잡한 비즈니스 로직에 유리
• MSA 분리에 유리
• 모든 플랫폼 지원
• 표준 HTTP로 광범위 지원
• 대규모 팀 협업에 유리
• 단순한 CRUD에 적합
종합 평가TypeScript 프로젝트, 복잡한 비즈니스 로직,
빠른 개발, 타입 안전성 중요한 경우
다양한 클라이언트 지원, 마이크로서비스,
대규모 팀, 표준 준수 필요한 경우

마치며...

현재 작성중인 REST API 방식에서 tRPC로 리팩토링을 진행해 볼 예정이다.
성능 상 큰 차이가 있진 않지만 애초에 react-query기반으로 Client단을 작성해놔서 리팩토링 비용이 작기 때문이다.

특히 Next.js 만을 가져가는 Monolithic 구조이기 때문에 더더욱 그렇다.

개발하면서 서비스 구조와 아키텍처 패턴을 더 중요시 여기게 되는거 같다.

profile
Being a Modern Software Engineer

0개의 댓글