본문 바로가기

Frontend is 실전

[Next] API Routes를 이용해 사이드 서버 구축 해보기 with Typescript

최근 이곳저곳 면접을 다니면서 포트폴리오를 조금 손봐야겠다는 생각이 문득 들었다.

가장 큰 이유는 역시 종료된 프로젝트 관리 미흡. 

나는 프론트엔드 개발자이기 때문에 프론트엔드 쪽에서 문제가 발생하면 바로 고칠 수 있지만,

백엔드 쪽에서 발생한 문제는 거의 손대기가 불가능 했기에 

백엔드 API가 사라졌다거나, 서버가 닫혀버리는 경우에는 꼼짝없이 프로젝트 하나를 버리게 되는 것이었다.

하지만 공들여 진행한 프로젝트를 '없던 일'로 쳐버리는건 너무 아쉽기 때문에, 스스로 백엔드 서버를 만들어 프로젝트를 살려보기로 했다.


1. Next.js API Routes와 서버 생성

Next.js 설치 및 적용은 나보다 더 상세하게 작성 해 주신 분들이 많기에 생략한다.

Next.js를 설치 후 프로젝트를 만들면 src 폴더 내의 app 폴더 아래에는 page, layout 파일이 생길 것이다.

그 친구들은 우리가 전~혀 건들 일이 없으니 놔두고, app 폴더 내에 새로운 'api' 폴더를 생성한다.

 

API Routes는 Next.js에서 API를 만들 수 있게 해주는 기능으로, api 폴더 내의 모든 파일은 페이지가 아니라 api 엔드포인트로 취급된다. 

게다가 클라이언트 번들에 속하지 않는 서버 사이드 번들이기 때문에, 클라이언트 리소스 크기에 전혀 영향을 주지 않는다.

 

Next.js의 app 폴더 아래 있는 폴더는 '라우팅 경로'가 되는 것은 Next를 접해본 사람이라면 누구나 알 것이다.

라우팅 경로 = url이 되고, 눈치 빠른 사람이라면 이 url이 서버의 api url이 될 수 있을 것이란걸 알 수 있을 거고.

정답. Next.js로 만든 서버에서는 라우팅 경로가 api url이 된다. 

그러므로 app 폴더 아래에 api 폴더를 만들고 내가 만들고 싶은 경로 이름을 만든 뒤 route 파일을 만들어 주면 된다.

 

 

난 일단 간단하게 api 폴더를 만들고, 그 아래 각 기능을 따서 address, info, news 폴더를 만들어준 뒤

각 폴더 안에 route.tsx 파일을 만들어 주었다.

 


2. 데이터 통신 구현하기

가장 기본적인 HTTP 프로토콜인 GET을 먼저 구현해 보자.

export const GET = async (): Promise<Response> => {
  try {
    return new Response(JSON.stringify(보낼 데이터), {
      headers: {
        헤더
      },
    });
  } catch (error) {
    return new Response(JSON.stringify(에러 메시지), {
      headers: {
        헤더
      },
      status: 500,
    });
  }
};

 

가장 기본적인 골자이다.

GET 함수는 비동기 작업을 위해 async로 비동기 함수로 만들어 주었기 때문에, Promise 객체를 리턴하게 된다.

그렇기 때문에 Response 타입을 갖는 Promise 객체를 리턴하도록 타입 지정을 해준다.

자바스크립트같은 경우에는 안해도 된다!!!

 

참고로, Response나 Request 모두 node_modules의 @types/node에 기본으로 지정되어 있는 타입인데, 이는 타입스크립트 환경에서 Node.js를 사용할 경우 자동으로 설치 된다고 한다. 

내 생각엔 타입스크립트를 설치할 때 자동으로 설치가 되는 듯 하다. 만약 설치되어 있지 않다면 설치해 주자

npm install --save-dev @types/node
yarn add --dev @types/node

 

암튼 덕분에 고맙게도 Request와 Response의 타입을 가져다 쓸 수 있게 되었다.

그러니까 매개변수로 응답 데이터를 담은 requset도 추가해 주자.

 

export const GET = async (req: Request): Promise<Response> => {
  try {
    return new Response(JSON.stringify(보낼 데이터), {
      headers: {
        헤더
      },
    });
  } catch (error) {
    return new Response(JSON.stringify(에러 메시지), {
      headers: {
        헤더
      },
      status: 500,
    });
  }
};

 

그 다음은 헤더를 구성해 보자.

사실 헤더의 구성요소는 여러가지가 있지만, 모든 구성 요소가 필요하지는 않다. 

당연히 헤더를 빈 객체로 채워 보내도 무방하지만, 실제 서비스에서는 어떤 에러가 발생할지 모르기 때문에, 우선 가장 기본적인 요소들로 헤더를 채워준다.

 

export const GET = async (req: Request): Promise<Response> => {
  try {
    return new Response(JSON.stringify(보낼 데이터), {
      headers: {
        "Content-Type": "application/json",
        "Access-Control-Allow-Origin": "*",
      },
    });
  } catch (error) {
    return new Response(JSON.stringify(에러 메시지), {
      headers: {
        "Content-Type": "application/json",
        "Access-Control-Allow-Origin": "*",
      },
      status: 500,
    });
  }
};

 

내가 응답하는 컨텐츠의 타입과, CORS 에러를 방지하기 위한 Allow Origin 헤더를 추가해 준다.

필요에 따라 다른 헤더를 추가 또는 삭제 하면 된다.

 

참고로 Allow Origin 헤더는 실 서비스를 개시할 경우, 클라이언트 배포 url만 허용하는게 안전하다고 하다.

하지만 지금은 테스트용이니 일단은 모든 출처를 허용하는것으로.

 

자 그렇다면 이제 통신은 구현 완료 되었으니, 간단한 메시지를 보내보자.

 

export const GET = async (req: Request): Promise<Response> => {
  const message = {msg: 'Next 서버 구축 잘 됐습니다.'}
  try {
    return new Response(JSON.stringify(보낼 데이터), {
      headers: {
        "Content-Type": "application/json",
        "Access-Control-Allow-Origin": "*",
      },
    });
  } catch (error) {
    return new Response(JSON.stringify(에러 메시지), {
      headers: {
        "Content-Type": "application/json",
        "Access-Control-Allow-Origin": "*",
      },
      status: 500,
    });
  }
};

 

npm run dev를 통해 서버를 실행시키면 보통 localhost:3000으로 실행될 것이다. 

localhost:3000/ 아래에 원하는 경로를 넣어 api url을 완성해 주고, get 메서드를 사용해 통신을 보내보자.

나 같은 경우에는 api/address로 데이터를 보냈다.

 

 

데이터가 아주 잘 넘어오는 것을 확인 할 수 있었다.


3. 쿼리 적용하기

api 요청을 보낼 때, 조건에 따라 간간히 쿼리를 사용 해야 할 때가 있다.

이런 경우에 쿼리를 어떻게 사용할지 알아보도록 하자.

 

먼저 쿼리란, url 뒤에 붙여 조건을 나타내주는 일종의 매개변수이다.

http://localhost:3000/api/address?조건1=값&조건2=값

여기서 ? 을 통해 쿼리를 작성 할 것이라고 알려주고, ? 뒤에 &로 구분하여 쿼리를 나타내 준다.

 

예를 들어 http://localhost:3000/api/address?id=1 이라는 url의 경우에는,

http://localhost:3000/api/address에서 들어오는 데이터 중, id가 1인 데이터를 달라! 고 말하는 것과 같다.

클라이언트에서는 url 뒤에 쿼리를 붙여 보내면 되지만, 과연 서버에서는 이를 어떻게 적용할 수 있을까?

 

export const GET = async (req: Request): Promise<Response> => {
  const { searchParams } = new URL(req.url);
  const query = searchParams.get("search") || "";
  const message = [
    { id: 0, msg: "Next 서버 구축 시작합니다." },
    { id: 1, msg: "Next 서버 구축 잘 됐습니다." },
    { id: 2, msg: "Next 서버 구축 종료하겠습니다." },
  ]
  try {
    return new Response(JSON.stringify(message), {
      headers: {
        "Content-Type": "application/json",
        "Access-Control-Allow-Origin": "*",
      },
    });
  } catch (error) {
    return new Response(JSON.stringify({ error: (error as Error).message }), {
      headers: {
        "Content-Type": "application/json",
        "Access-Control-Allow-Origin": "*",
      },
      status: 500,
    });
  }
};

 

이때는 URL 객체의 searchParams를 사용하여 쿼리를 추출해내면 된다.

URL 객체 역시 @types/node에 포함되어 있으므로 별도 설치 없이 사용 할 수 있다.

new URL(req.url)

 

클라이언트로부터 들어온 요청의 url을 매개변수로 넣어 만들 수 있으며, 다양한 정보를 담고 있다.

 

 

이 중에서, serchParams는 URLSearchParams 객체로, 쿼리 매개변수를 쉽게 다룰 수 있도록 하는 객체이다.

보는 것 처럼 조건=>값 형태로 나타낼 수 있으며, get을 통해 가져올 수 있다.

const query = searchParams.get("search") || "";

 

현재 query에는 조건인 id에 부합하는 값인 '2'가 담겨져 있다.

 

 

참고로 쿼리가 여러개인 경우에는 각 쿼리별로 get 해주면 된다.

 

여튼 이 query를 가지고 현재 데이터에서 조건에 맞는 데이터만 뽑아 전달해 보자.

 

export const GET = async (req: Request): Promise<Response> => {
  const { searchParams } = new URL(req.url);
  const query = searchParams.get("id") || "";
  const message = [
    { id: 0, msg: "Next 서버 구축 시작합니다." },
    { id: 1, msg: "Next 서버 구축 잘 됐습니다." },
    { id: 2, msg: "Next 서버 구축 종료하겠습니다." },
  ];
  const filteredMessage = message.filter((el) => el.id === Number(query));
  try {
    return new Response(JSON.stringify(filteredMessage), {
      headers: {
        "Content-Type": "application/json",
        "Access-Control-Allow-Origin": "*",
      },
    });
  } catch (error) {
    return new Response(JSON.stringify({ error: (error as Error).message }), {
      headers: {
        "Content-Type": "application/json",
        "Access-Control-Allow-Origin": "*",
      },
      status: 500,
    });
  }
};

 

id를 2로 전달한 경우에는, id:2인 '구축 종료' 메시지가 떠야 한다.

 

 

아주아주 잘 넘어오는 모습이다.


4. 배포

배포는 진짜 별게 없는게, Next.js라는 프레임워크에 딸린 기본 기능이기 때문에 Node.js나 Express.js처럼 static한 사이트에서도 동작한다.

그러므로 우리가 흔히 사용하는 netlify나 vercel 등의 배포 툴로 배포만 해줘도 서버가 동작하는 모습을 볼 수 있다...!

 

진짜 별 거 없던거임


그동안 방치되어 있던 프로젝트를 살려냄과 동시에 새로운 기술을 또 하나 알게 되었다.

Amazon S3로 얼레벌레 만들었던 다른 프로젝트를 다시 리팩토링 해보던가

아님 GET 뿐만 아니라 POST, PATCH, DELETE 등의 메서드도 구현을 해봐야겠다.

그건 그렇고...서버 구현하면서 느낀게

풀스택 하시는 분들...대단한 분들이셨다....