[Firebase 웹] Cloud Function으로 RESTful API 만들기 (feat. Express)

2022. 3. 27. 02:30Firebase

728x90

이번 포스트에서는 Cloud Function과 Express 프레임워크로 RESTful API를 함께 설계해보도록 합시다.

이미 자사에서 사용하려고 만든 api가 있긴 하지만 해당 예제를 보면 더 헷갈릴 수도 있기 때문에 훨씬 간단한 예제 프로젝트를

만들어 진행하겠습니다. 

 

어떤 API를 만들 것인가?

 

예제는 사용자가 감상했던 영화를 추가, 삭제, 업데이트 등 (CRUD) 작업을 할 수 있도록 API를 만들 예정이고 해당 목록은 파이어 스토어에 저장을 할 예정입니다. 파이어 스토어에서 문서를 업데이트하기 위해서 굳이 Cloud Function이 필요하진 않지만 추후에 백엔드에서 처리해야 할 로직이 추가된다거나 외부에서 API를 통해 변경해야 하는 경우도 생길 수 있기 때문에 해당 방식으로 설계하는 방법에 대해 알아보고자 합니다.

 

최종적으로 해당 프로젝트에서는 파이어 베이스의 Cloud Function (클라우드 함수),  Firestore (파이어 스토어) , Hosting(호스팅) 그리고 Node js의 웹 프레임워크인 Express를 사용해서 진행을 할 예정입니다.


 

1. 파이어 베이스 프로젝트 생성 및 세팅

본격적으로 시작하기 앞서 프로젝트를 만들고 세팅을 해야 하는데 해당 내용 관련해서는 이전에 작성한 포스트가 있으니 참고하길 바랍니다.

 

 

[Firebase 웹] 파이어베이스 시작하기 - 웹 앱 초기설정

이번 포스트에서는 파이어 베이스에서 새 프로젝트를 생성하는 방법에 대해 알아보겠다. 마침 관리자 페이지를 만들어야하기 때문에 앞으로 Firebase 관련 예제는 React로 작성을 해보겠다. 일단

mingeesuh.tistory.com

 

2.  파이어 스토어 세팅 및 생성

이제 데이터베이스로 사용될 파이어 스토어를 생성합시다. 필자는 해당 프로젝트를 블로그 글 작성 용도를 만들고 있기 때문에 따로 규칙을 생성하지 않고 테스트 모드로 진행하겠습니다. 아직 규칙에 관해서 작성 한글은 따로 없기 때문에 자세히 알고 싶으신 분들은 공식문서를 참고 바랍니다.

 

테스트 모드로 시작시 30일간 공개모드로 사용

 

그다음으로 파이어 스토어 위치를 설정해주면 되는데 현재 위치로 부터 가장 가까운곳을 선택해주면 됩니다. 필자는 서울이기 때문에 northeast3을 사용하겠습니다. 정확한 파이어스토어 위치 관련해서는 해당 문서를 참고 바랍니다.

 

현재 위치에서 가장 가까운 곳으로 설정 (한국일시 northeast3 사용 권장)

 

3.  Cloud Function & Hosting 설정

프로젝트를 새로 생성하고 세팅까지 완료했다면 이제 Cloud Function과 호스팅 설정을 해주면 되는데 이 작업은 Firebase CLI(명령줄)로 손쉽게 가능합니다. 호스팅은 세팅을 안 해줘도 크게 상관은 없지만 커스텀 api 엔드포인트를 위해서 세팅을 해놓도록 하겠습니다. 

 

아직 CLI를 설치하지 않았다면 아래 명령어로 전역에 설치부터 해줍시다.

npm install -g firebase-tools

이제 웹앱의 루트 디렉터리에서 로그인을 해주면 됩니다.

firebase login // 웹에서 로그인할수 있는 창이 뜨면 로그인정보 입력

로그인이 완료됐으면 본격적으로 프로젝트를 초기화 및 연동시켜 줍시다.

firebase init

 

위 명령어를 입력하면 초기 세팅을 위해 몇 가지를 물어보는데 아래와 같이 입력해주면 됩니다.

Which Firebase features do you want to set up for this directory? Press Space to select features, then Enter to confirm your choices (파이어 베이스의 어떤 기능들을 해당 디렉터리에 설정할 것인가? 스페이스바로 기능들을 선택하고 엔터로 확인) 

 

Cloud Functions, Hosting 체크 후 선택

 

Please select an option (옵션을 선택해주세요)

 

Use an existing project -> 첫 번째 Step에서 생성해둔 프로젝트를 찾아서 선택

 

What language would you like to use to write Cloud Functions? (사용할 언어를 선택해주세요)

 

Javascript 또는 Typescript 둘 중 사용하고자 하는 언어 선택. 해당 포스트에서는 자바스크립트로 진행하도록 하겠습니다.

 

Do you want to use ESLint to catch probable bugs and enforce style? (ESLinst 사용 여부)

 

필자는 이미 사용 중인 eslint 설정이 있기 때문에 N(No) 입력하였습니다.

 

Do you want to install dependencies with npm now? (관련 dependency를 설치할 건지 firebase-function, firebase-admin 설치)

 

관련 패키지를 사용할 것이므로 Y(Yes)로 설치해줍시다

 

여기까지 입력을 완료했으면 functions 폴더가 생성되는데 해당 디렉터리에 express 프레임워크를 추가로 설치해주면서 마무리합시다.

 

cd functions // functions 디렉토리로 이동
npm install express

 

express v4.16부터는 body-parser의 기능이 내장되어있어 별도로 설치해줄 필요는 없지만 만약에 이전 버전을 사용한다면 body-parser도 설치해주도록 합시다.

 

 


4.  Cloud Function 작성하기

이제 functions 폴더의 index 파일에 들어가 보면 주석 처리된 보일러 플레이트 코드가 있을 텐데 삭제하거나 수정해서 최종적으로 아래와 같이 express를 세팅해봅시다.

 

// index.js

const functions = require("firebase-functions");
const admin = require("firebase-admin");
const express = require("express");

admin.initializeApp(); // 어드민 초기화. 클라우드 함수, 호스팅만 사용할 경우 따로 설정파일을 넘겨주지 않아도 됨
const app = express(); 
const movieApp = express.Router();

// 해당 부분에 영화 CRUD 라우트 설정

app.use(express.json()); // body-parser 설정
app.use("/api", movieApp); 

exports.movieAPI = functions.region("asia-northeast3").https.onRequest(app);

 

API의 CRUD 라우트는 아래와 같은 방식으로 작성을 해주면 됩니다. 일단은 post, patch, get 요청에 대한 응답만 적어뒀으니 더 자세한 건 해당 레포를 확인해주시길 바랍니다.

 

// 새로운 영화 리뷰 추가
movieApp.post("/reviews", async (req, res) => {
  try {
    const review = {
      title: req.body.title,
      rating: req.body.rating,
      comment: req.body.comment,
    };
    const reviewDoc = await db.collection(reviewCollection).add(review);
    res.status(200).send(`새로운 영화 추가 ID: ${reviewDoc.id}`);
  } catch (error) {
    res.status(400).send(error.message);
  }
});

// 기존 영화 리뷰 수정
movieApp.patch("/reviews/:reviewId", async (req, res) => {
  try {
    const updatedReviewDoc = await db
      .collection(reviewCollection)
      .doc(req.params.reviewId)
      .update(req.body);
    res.status(204).send(`${updatedReviewDoc} 영화 리뷰 수정 완료`);
  } catch (error) {
    res.status(400).send(`영화 리뷰를 수정하는 도중 오류가 발생했습니다`);
  }
});

// ID로 영화 리뷰 불러오기
movieApp.get("/reviews/:reviewId", async (req, res) => {
  try {
    const reviewDoc = await db
      .collection(reviewCollection)
      .doc(req.params.reviewId)
      .get();
    res.status(200).send({ id: reviewDoc.id, ...reviewDoc.data() });
  } catch (error) {
    res.status(400).send("영화 리뷰를 불러오는데 실패하였습니다");
  }
});

 


5.  Emulator로 Cloud Function 테스트해보기

이제 배포를 바로 해도 되지만 배포를 하기까지 몇 분의 시간이 소요될 수 있으므로 항상 잘 작동하는지 먼저 로컬 환경에서 테스트를 해보는 것이 좋습니다. 

 

에뮬레이터를 통해 함수를 실행해줍시다.

firebase emulators:start --only functions

정상적으로 실행이 되면 아래와 같이 로컬 환경에서 실행해볼 수 있는 API URL 엔드포인트를 찾을 수 있을 것입니다.

 

 

해당 url을 통해 테스트를 진행하기 위해 Postman을 사용해보도록 하겠습니다

아래와 같이 최근에 봤었던 이상한 나라의 수학자라는 영화의 리뷰를 title, comment, rating 전부 입력했을 때 status 200과 함께 정상적으로 파이어 스토어에 추가됩니다.

 

내친김에 반환된 id값으로 get 요청까지 해봅시다.

 

 

마찬가지로 잘 되는 걸 확인할 수 있습니다.

 


5.  마무리 배포하기

이제 배포 작업으로 마무리를 지으면 되는데 그전에 루트 폴더에 있는 firebase.json 파일에 rewrites를 아래와 같이 추가해줍시다.

 

{
  // ...생략
  {
  "hosting": {
    "public": "public",
    "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
     "rewrites": [
    {
      "source": "/api/**",
      "function": "movieAPI"
    }
  ]
  }
}
}

해당 작업은 /api로 시작하는 요청에 한해서 위에 작성해뒀던 movieAPI 함수가 실행될 수 있도록 설정을 해두는 것입니다. rewrites 옵션에 대해 더 자세히 알고 싶다면 공식문서를 참고하시길 바랍니다.

 

이제 배포를 해주면 되는데 Cloud Function을 배포해서 사용하려면 무료 (Spark) 요금제가 아닌 사용한 만큼 지불하는 (Blaze) 요금제로 업그레이드가 필요합니다. 

 

Cloud Function의 경우 월 2,000,000건 호출까지는 무료이기 때문에 카드를 등록해둔다고 해서 비용이 청구되진 않겠지만 카드 등록이 꺼려지신다면 여기까지만 진행하시길 바랍니다.

 

프로젝트 콘솔 사이드바에서 업그레이드로 카드 등록

 

프로세스를 따라 요금제 업그레이드 완료!

 

요금제 변경이 완료됐다면 아래 명령어를 입력해서 Cloud function 배포와 호스팅을 한 번에 해결합시다.

 

firebase deploy

 

추후에 함수만 따로 배포해야 할 일이 생긴다면 아래 명령어를 사용하면 됩니다.

 

firebase deploy --only functions

 

배포가 완료되면  커스텀 호스팅 url이 생성되는데 위와 같이 postman을 통해 테스트를 진행하시면 끝이 납니다.

 


마무리

이로써 클라우드 함수와 express 프레임워크로 서버리스 RESTful API를 구현해보았습니다. 실제로 production용 api를 구현하게 되면 인증이라던지 firestore 규칙 같은 세부 설정이 더 필요하겠지만 백엔드 서버를 따로 관리하지 않아도 된다는 점은 아주 매력적인 것 같습니다.