저희 회사에서 사이트를 하나 더 만들 것을 고려하면서, 여러 프론트엔드 프로젝트에서 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 파일의 몇몇 내용들을 그대로 넣을 것입니다.
- 스크립트 복사:
functions/package.json에서 build와 start를 제외한 모든 스크립트를 메인 package.json으로 복사합니다. - 엔진 및 메인 속성 복사:
functions/package.json의 engines와 main 속성을 메인 package.json으로 복사합니다. - 의존성 복사:
functions/package.json의 모든 의존성을 복사하되, TypeScript는 메인 package.json에 이미 있으므로 제외합니다. - 함수 폴더 삭제:
functions 폴더를 삭제합니다. - 새 파일 생성:
프로젝트의 루트에 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 |