본문 바로가기
Back-End

Firebase function 으로 Nest 백엔드 프로젝트 배포하기

by Junmannn 2024. 10. 18.
반응형

저희 회사에서 사이트를 하나 더 만들 것을 고려하면서, 여러 프론트엔드 프로젝트에서 API 요청으로 사용 가능한 기능을 제공하는 백엔드 프로젝트를 만들기로 하였습니다.
저는 저희 팀에서 Nest, Typescript 를 사용하고, Firebase 를 통해 배포하기로 결정했습니다.
Firebase 선정 이유 : Google 의 여러 기능들을 사용중인데, Google 의 firebase 를 사용하면 연결이 훨씬 수월하고, $300 의 크레딧을 3달동안 지원하기 때문에 메리트가 확실하다고 판단했습니다.
 

 Firebas functions 로 백엔드 프로젝트를 API 사용하기 위해 배포하면 유료 요금제인 Blaze 플랜을 사용해야만 합니다! 

물론! Firebase functions 배포도 3개월 무료 사용을 할 수는 있지만, 전체적으로 무료로 하려고 하시는 분들은 Firebase 가 아닌 vercel, Heroku, Render 등 여러 가지 서비스가 있습니다. 물론 다들 사용량이 증가한다면 유료 플랜으로 전환이 필요하고, 전환하기 않으면 본인의 프로젝트 서비스가 갑자기 끊길 수 있으니 조심하세요!

 
 

기술 선정 이유

1. TypeScript의 장점

  • 정적 타입 검사: TypeScript는 정적 타입 시스템을 제공해 코드 작성 시 오류를 미리 발견할 수 있어 더 안전하고 안정적인 코드를 작성할 수 있습니다. 이는 큰 규모의 프로젝트나 여러 개발자가 협업하는 환경에서 특히 유리합니다.
  • 개발 생산성 향상: 정적 타입 덕분에 IDE에서 코드 자동 완성, 리팩토링, 오류 탐지가 더 수월해져 개발 속도가 빨라지고 유지보수도 쉬워집니다.
  • JavaScript와의 호환성: TypeScript는 JavaScript의 상위 호환이기 때문에 기존 JavaScript 라이브러리나 코드를 손쉽게 통합할 수 있습니다. 유연하게 확장 가능한 개발을 지원합니다.

2. NestJS의 장점

  • 모듈화와 아키텍처: NestJS는 구조적으로 모듈화되어 있어 대규모 애플리케이션을 관리하기 쉽습니다. 각 기능을 모듈로 분리하여 개발하면 코드의 재사용성, 유지보수성, 테스트 용이성이 향상됩니다.
  • 의존성 주입 (Dependency Injection): NestJS는 의존성 주입 패턴을 지원하여 객체 간의 관계를 관리하고 코드의 결합도를 낮춥니다. 이를 통해 테스트 가능성이 높아지고 유지보수가 쉬워집니다.
  • TypeScript 기반: NestJS는 TypeScript를 기본으로 사용하므로, TypeScript의 장점을 모두 활용할 수 있습니다. 특히 정적 타입 시스템을 통해 복잡한 로직도 안전하게 관리할 수 있습니다.
  • 유연성: NestJS는 다양한 라이브러리와 프레임워크와 쉽게 통합됩니다. 예를 들어, Express나 Fastify를 기반으로 사용할 수 있고, GraphQL, WebSocket, Microservices 등의 다양한 기술 스택을 지원합니다.
  • 테스트 친화적: NestJS는 테스트 프레임워크와 자연스럽게 통합됩니다. Unit 테스트와 E2E 테스트를 쉽게 설정하고 관리할 수 있어, 안정적이고 견고한 코드를 유지하는 데 유리합니다.
  • 인증 및 권한 관리: NestJS는 인증과 권한 관리 기능을 쉽게 통합할 수 있는 패키지와 구조를 제공합니다. 특히 JWT 인증이나 OAuth 같은 표준 인증 방식을 쉽게 구현할 수 있습니다.

3. TypeORM을 선택했는가?

  • TypeScript와의 강력한 통합: TypeORM은 TypeScript를 기본적으로 지원하여, 타입 안전성과 함께 ORM 기능을 사용할 수 있습니다. 이는 NestJS와 자연스럽게 통합되며, 타입 안전한 데이터베이스 작업이 가능합니다.
  • 대규모 프로젝트에서의 유지보수성: ORM을 사용하면 SQL 쿼리를 직접 작성할 필요 없이 객체를 통해 데이터를 조작할 수 있기 때문에, 코드의 가독성이 높아지고 유지보수가 쉬워집니다. 특히 여러 개발자와 함께하는 대규모 프로젝트에서는 코드를 일관성 있게 유지하는 데 큰 도움이 됩니다.
  • 유연한 쿼리 작성: 쿼리 빌더를 사용하여 복잡한 SQL 쿼리도 객체 지향적으로 작성할 수 있어, 데이터베이스와의 상호작용을 더 직관적으로 처리할 수 있습니다.
  • 생산성 향상: TypeORM을 사용하면 기본적인 CRUD 작업을 더 빠르게 처리할 수 있고, 반복적인 데이터베이스 작업에서 발생하는 오류를 줄일 수 있어 개발 생산성이 향상됩니다.

4. 생태계와 지원

  • 인기와 커뮤니티 지원: TypeScript와 NestJS는 현재 많은 개발자와 기업들이 채택하고 있는 인기 기술입니다. 관련 자료, 튜토리얼, 라이브러리가 풍부하여 프로젝트에서 발생할 수 있는 문제를 쉽게 해결할 수 있습니다.
  • 미래 지향적 기술 스택: TypeScript는 빠르게 성장하고 있으며, 많은 기업들이 이 기술을 채택하고 있습니다. NestJS 역시 안정적이고 확장 가능한 아키텍처를 제공해 앞으로도 계속해서 성장할 가능성이 큽니다.

5. 유연한 확장성

  • 클라우드와의 통합 용이: NestJS는 AWS Lambda나 Firebase Functions 같은 서버리스 환경에서도 원활하게 동작할 수 있으며, 마이크로서비스 아키텍처로도 확장 가능하여 다양한 요구 사항을 충족할 수 있습니다.
  • API 개발 최적화: NestJS는 REST API 및 GraphQL API 개발에 최적화된 프레임워크로, 빠르고 효율적으로 API 서버를 구축할 수 있습니다. 다양한 프로젝트에서 동일한 백엔드를 쉽게 공유할 수 있는 구조를 지원합니다.

TypeScript와 NestJS는 안정성과 생산성을 높이는 동시에 미래 확장성까지 고려한 기술 스택이라고 생각하여 선정하게 되었습니다.
 
 
 
 
 

Firebase Functions 로 백엔드 프로젝트 배포하기

 
NestJS와 Firebase 활용하기: 프로젝트 생성부터 CORS 설정까지

NestJS와 Firebase를 연계하여 앱을 구축하는 과정은 매우 흥미로운 작업입니다. 아래에서는 프로젝트 생성부터 Firebase Functions 추가 및 배포, Firebase Authentication 설정, CORS 활성화에 대한 단계별 요약을 제공합니다.

---


1. 프로젝트 생성


먼저, NestJS와 Firebase 도구를 설치합니다.

npm install -g firebase-tools @nestjs/cli


새로운 NestJS 프로젝트를 생성합니다.

nest new 만들프로젝트이름

 
 
 

2. Firebase Functions 추가

Firebase 콘솔 (https://console.firebase.google.com/))에서 프로젝트를 생성하고 요금제를 Blaze로 변경합니다.

- Firebase Functions를 초기화합니다.

firebase login
firebase init functions


- 여기서 "Use an existing project"를 선택하고 생성한 프로젝트를 선택합니다.

? What language would you like to use to write Cloud Functions? TypeScript
? Do you want to use ESLint to catch probable bugs and enforce style? No
+  Wrote functions/package.json
+  Wrote functions/tsconfig.json
+  Wrote functions/src/index.ts
+  Wrote functions/.gitignore
? Do you want to install dependencies with npm now? No

 
 

3. functions 폴더 내용 복사와, 빌드 루트 변경

가장  중요한 부분입니다!  집중. 이 부분만 제대로 하면 됩니다. 물론 각자의 프로젝트 설정에 따라 다르겠지만, 큰 틀은 이 글을 따라하시면 됩니다.
 
2의 과정을 거치면 functions 라는 폴더가 생성이 되었을 것입니다. functions 폴더 안에 package.json 파일이 있습니다.
여기서 우리는 기존 프로젝트의 package.json 파일에 functions/package.json 파일의 몇몇 내용들을 그대로 넣을 것입니다.
 

  1. 스크립트 복사:
    functions/package.json에서 build와 start를 제외한 모든 스크립트를 메인 package.json으로 복사합니다.
  2. 엔진 및 메인 속성 복사:
    functions/package.json의 engines와 main 속성을 메인 package.json으로 복사합니다.
  3. 의존성 복사:
    functions/package.json의 모든 의존성을 복사하되, TypeScript는 메인 package.json에 이미 있으므로 제외합니다.
  4. 함수 폴더 삭제:
    functions 폴더를 삭제합니다.
  5. 새 파일 생성:
    프로젝트의 루트에 index.ts라는 새 파일을 생성하고, 그 안에 아래의 내용을 추가합니다.
import { NestFactory } from '@nestjs/core';
import { ExpressAdapter } from '@nestjs/platform-express';
import * as express from 'express';
import * as functions from 'firebase-functions';
import { AppModule } from './src/app.module';
const expressServer = express();
const createFunction = async (expressInstance): Promise<void> => {
  const app = await NestFactory.create(
    AppModule,
    new ExpressAdapter(expressInstance),
  );
await app.init();
};
export const api = functions.https.onRequest(async (request, response) => {
  await createFunction(expressServer);
  expressServer(request, response);
});

그런 다음, package.json에서 main 속성을 lib/index.js 대신 dist/index.js를 가리키도록 변경합니다.

이렇게 하면 빌드된 파일의 경로가 올바르게 설정됩니다.

 

위의 단계를 잘 따라왔을 때의 package.json 의 형태의 예시입니다.

당연히! 본인의 프로젝트와 내용이 완전히 같을 수 없습니다! 애초에 같을 수가 없습니다ㅋㅋ 본인의 결과물과 비교해보세요. 없다고 해도 누락된게 아닐 수 있어요. + node 나 이런 버전같은 경우, 현재 글을 작성하는게 2024.10.18 에 작성한 내용입니다. 다를 수 있으니 너무 당황하지 말아주세요.

{
  "name": "junmannn-kyurasi",
  "version": "0.0.1",
  "description": "Backend project tutorial with nest js, typescript with firebase",
  "author": "",
  "private": true,
  "license": "UNLICENSED",
  "main": "dist/index.js",
  "scripts": {
    "build": "nest build",
    "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
    "start": "nest start",
    "start:dev": "nest start --watch",
    "start:debug": "nest start --debug --watch",
    "start:prod": "node dist/main",
    "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
    "test": "jest",
    "test:watch": "jest --watch",
    "test:cov": "jest --coverage",
    "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
    "test:e2e": "jest --config ./test/jest-e2e.json",
    "serve": "npm run build && firebase emulators:start --only functions",
    "shell": "npm run build && firebase functions:shell",
    "deploy": "firebase deploy --only functions",
    "logs": "firebase functions:log"
  },
  "dependencies": {
    "@langchain/core": "^0.2.23",
    "@langchain/openai": "^0.2.6",
    "@nestjs/axios": "^3.0.3",
    "@nestjs/common": "^10.0.0",
    "@nestjs/config": "^3.2.3",
    "@nestjs/core": "^10.0.0",
    "@nestjs/platform-express": "^10.0.0",
    "@nestjs/serve-static": "^4.0.2",
    "@nestjs/typeorm": "^10.0.2",
    "@types/diff": "^5.2.2",
    "@types/fast-levenshtein": "^0.0.4",
    "axios": "^1.7.5",
    "bcrypt": "^5.1.1",
    "cheerio": "^1.0.0",
    "csv-parser": "^3.0.0",
    "date-fns": "^3.6.0",
    "diff": "^6.0.0",
    "dotenv": "^16.4.5",
    "express": "^4.19.2",
    "fast-levenshtein": "^3.0.0",
    "firebase-admin": "^11.11.1",
    "firebase-functions": "^6.0.1",
    "fs": "^0.0.1-security",
    "puppeteer": "^23.3.0",
    "reflect-metadata": "^0.2.0",
    "rxjs": "^7.8.1",
    "typeorm": "^0.3.20"
  },
  "devDependencies": {
    "@nestjs/cli": "^10.0.0",
    "@nestjs/schematics": "^10.0.0",
    "@nestjs/testing": "^10.0.0",
    "@testing-library/jest-dom": "^6.4.8",
    "@types/express": "^4.17.17",
    "@types/jest": "^29.5.2",
    "@types/node": "^20.16.11",
    "@types/supertest": "^6.0.0",
    "@typescript-eslint/eslint-plugin": "^7.0.0",
    "@typescript-eslint/parser": "^7.0.0",
    "@yarnpkg/sdks": "^3.2.0",
    "eslint": "^8.42.0",
    "eslint-config-prettier": "^9.0.0",
    "eslint-plugin-prettier": "^5.0.0",
    "firebase-functions-test": "^3.1.0",
    "jest": "^29.5.0",
    "prettier": "^3.0.0",
    "source-map-support": "^0.5.21",
    "supertest": "^7.0.0",
    "ts-jest": "^29.1.0",
    "ts-loader": "^9.4.3",
    "ts-node": "^10.9.1",
    "tsconfig-paths": "^4.2.0",
    "typescript": "^5.1.3"
  },
  "engines": {
    "node": "18"
  },
  "jest": {
    "moduleFileExtensions": [
      "js",
      "json",
      "ts"
    ],
    "rootDir": "src",
    "testRegex": ".*\\.spec\\.ts$",
    "transform": {
      "^.+\\.(t|j)s$": "ts-jest"
    },
    "collectCoverageFrom": [
      "**/*.(t|j)s"
    ],
    "coverageDirectory": "../coverage",
    "testEnvironment": "node"
  },
  "packageManager": "yarn@4.4.0"
}

 
package.json 까지 완료가 되었다면
 
firebase.json 파일을 엽니다. 이제 firebase functions에 올라갈 때에, 기본적으로 firebase 는 index.ts 를 찾아갑니다. 그리고 이 파일은 아까 우리가 삭제했던 functions 폴더에 있죠. 하지만 우리는 그 폴더를 삭제하고, 백엔드 프로젝트를 사용하기 위해서 만든것이므로 functions 가 읽어들일 폴더 source 상대주소를 변경해줘야겠죠. firebase.json에서 "source": "." 로 변경합니다
 

{
  "functions": {
	... 다른 설정들. 아래 source를 무조건 변경해줘야 해요
    "predeploy": "npm --prefix \"$RESOURCE_DIR\" run build",
    "source": "."
  }
}

 
이제 npm run serve를 실행해봅니다.

npm run serve

 
만일 제대로 실행되었다면, http://localhost 로 api 가 실행되었다는 문구가 나오게 됩니다. 하지만 문제가 발생한다면 해당 에러 로그를 보고 천천히 해겷하고 다음 단계로 넘어가면 됩니다.
 

4. Firebase Functions에 배포

npm run deploy

 
- 성공적으로 배포되면 Function URL을 출력하며, 이 URL을 통해 앱이 제대로 작동하는지 확인할 수 있습니다.


5. CORS 활성화 혹은 백엔드 프로젝트 접근성 설정

자, 여기까지 잘 따라오셨고 배포까지 완료되었는데 사용이 안될 수 있어요. 물론 백엔드 프로젝트 개발 시에 개발 오류로 잘못된 것일 수도 있겠지만, npm run serve 로 테스트 해보았던 로컬에서 너무 잘 작동했면, 이 설정을 한번 해보시겠습니다

 

혹시나 아래와 같은 설정으로 인해 안될 수도 있으니 참고!

=> http.use_ssl = true  # HTTPS 사용
=> http.use_ssl = false  # local 에서 테스트 해볼 때에는 HTTPS 사용이 아님


 
1) google cloud console 구글링 하여 본인의 google cloud 주소로 이동 
2) 좌측 상단에서 리소스 선택 (본인이 만든 프로젝트 리스트가 뜰 것입니다). 여기서 본인 프로젝트 이름 클릭 => 좌측 상단이라고 했지만 언제 ui 가 바뀔지는 모르겠습니다

3) 본인 프로젝트의 cloud run 함수로 이동

4) 해당 페이지에서 뜨는 함수를 체크한 후 "권한"클릭

5) API 접근 권한 설정
 

  • 함수 목록에서 접근 권한을 공개하려는 함수를 선택합니다.
  • 화면 상단의 권한(IAM) 탭을 클릭합니다.
  • Add Principal 버튼을 클릭합니다.
  • New Members에 allUsers를 입력합니다.
  • Role 선택 메뉴에서 Cloud Functions Invoker를 선택합니다.
  • 저장을 눌러 설정을 완료합니다.

이러면 지금 올려둔 백엔드 프로젝트 API 는 모든 프로젝트에서 접근 가능하게 됩니다!
=> 하지만! 민감 정보를 다루거나 유출되면 안되는 것을 다루신다면, 당연히 모두 공개로 설정하시면 안되겠죠ㅎㅎ



이 단계를 통해 NestJS 애플리케이션과 Firebase를 성공적으로 통합할 수 있습니다!

 

반응형

'Back-End' 카테고리의 다른 글

mysql Workbench db export, import 오류 해결  (0) 2023.10.25
mysql id를 1로 변경하기  (0) 2023.01.24
[mysql-함수]날짜 관련 함수 모음  (0) 2023.01.15