개발/Node

# [node] NodeJS 환경변수 .env 다루기 1탄

ForrestPark 2025. 1. 22. 09:57

9.1 환경 변수 소개

  • 다양한 환경의 외부 애플리케이션과 연동 할수 있음.
  • 최소한의 경우 데이터베이스 설정 필수
  • 배포를 어떤 환경에 하느냐에 따라 테스트용, 프로덕션용을 나누어야함.
  • QA 환경도 필요함.
  • 이외에 소스 코드에 들어가면 안된느 민감한 값 등 최소한 환경변수로 설정하거나 vault 같은 소스코드 이외의 외부 저장소에 두어야함. 예를들어 몽고디비 패스워드같은것.
  • 코드로 제어하지 말고 별도의 파일로 두어서 외부 서버에 설정해서 읽어올수있도록 해야함.
  • 그렇지 않으면 설정이 복잡해질수록 환경 변수를 제어하는 코드가 복잡해짐
  • NestJS 환경변수 설정은 ConfigModule 에서 할수있고 설정된 환경변수를 다른 모듈에서 가져다쓰려면 ConfigService 를 주입 받아 사용해야함.

9.2 프로젝트 생성 및 설정

(1) 환경설정 파일 테스트용 사용할 config-test 이름으로 NestJs 프로젝트 생성후 의존성 패키지 설치

nest new config-test
cd config-test
npm i @nestjs/config ## 의존성 패키지 설치 
  • nestjs/config 는 내부적으로 dotenv 를 사용함.
  • .dotenv 는 .env 라는 이름의 파일에 환경변수를 설정하고 불러올수있게 하는ㄴ 자바스크립트 라이브러리

9.3 NestJS 설정 및 테스트

순서 : app.module.ts - ConfigModule 설정 > .env 파일 생성 > app.controller.ts 에 테스트 라우팅 함수 추가

9.3.1 app.module.ts 에 ConfigModule 설정

(1) ConfigModuel

  • 설정에 특화 된 기능 모듈
    import { Module } from '@nestjs/common';
    import { ConfigModule } from '@nestjs/config'; // config importiing
    import { AppController } from './app.controller';
    import { AppService } from './app.service';
    

@Module({
imports: [ConfigModule.forRoot()], // config moudle 설정
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

- ConfigModule.forRoot() 옵션 

|옵션|설명|
|--|--|
|cache|메모리 환경변수 캐시 여부 |
|isGlobal|다른 모듈에서 임포트 안해도 사용가능|
|ignoreEnvFile|.env 파일 무시|
|ignoreEnvVars|환경 변수 무효|
|envFilePath|환경변수 파일 경로|
|encoding|환경변수 파일 이코딩|
|validate|환경 변수 유효성 검증 함수|
|load|커스텀 환경 설정 파일을 로딩시에 사용,ts,yaml 등 |

## 9.3.2  .env 파일 생성 

### (1) .env 작성
- .env: @nestjs/config 내부에서 사용하는 dotenv 라이브러리에서 환경 변수 파일 읽어올때 사용하는 파일 
- dotenv : 기본적으로 .env 확장자 파일 읽어옴. 
- 프로젝트 루트 디렉터리 바로 아래 .env 파일 생성후 작성 

```Bash
vi /test-config/.env
####################
MESSAGE=hello nestjs
####################
  • MESSAGE 가 key 이고 hello 가 value
  • = 사이 공백은 무시, 키는 보통 대문자로 함.
  • 맥에서는 env 명령실행시 시스템 환경 변수 확인 가능

9.3.3 app.controller.ts 코드 추가

(1) getHello() 함수를 수정해 .env 환경변수를 사용.

import { Controller, Get } from '@nestjs/common';
// Configure 
import { ConfigService } from '@nestjs/config';
import { AppService } from './app.service';

@Controller()
export class AppController {
  // ConfigService 주입 
  constructor(private configService: ConfigService){}
  @Get()
  getHello(): string {
    // configService.get() 호출 
    const message = this.configService.get("MESSAGE")
    return message
  }
}

9.3.4 테스트

9.4 ConfigModule 을 전역 모듈로 설정하기

(1) ConfigService 사용할 수있어야함.

isGlobal 옵션을 사용하도록 app.module.ts 의 ConfigModule 의 설정을 수정

@Module({
  imports: [ConfigModule.forRoot({ isGlobal: true})],

9.4.1 .env 환경변수 설정

(1) .env 에 변수 추가

vi .env
###################################
    WEATHER_API_URL = http://api.openweathermap.org/data/2.5/forecast?id=524901&appid=
    WEATHER_API_KEY = my_open_api_key
###################################

nest g module weather ##
nest g controller weather --no-spec

9.4.3 날씨 api 테스트용 핸들러 함수로 테스트

weather.controller.ts 작성

// weather/weather.controller.ts
import { Controller, Get } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';

@Controller('weather')
export class WeatherController {
    constructor(private configService: ConfigService){}

    @Get() 
    public getWeather(): string {
        // 환경 변수 값 가져 오기 
        const apiUrl = this.configService.get('WEATHER_API_URL')
        const apiKey = this.configService.get('WEATHER_API_KEY')

        // 내부함수인 callWeatherAPI() 호출
        return this.callWeatherApi(apiUrl, apiKey)
    }
    private callWeatherApi(apiUrl: string, apiKey: string): string {
        console.log('날씨 정보 가져오는중 ...')
        console.log(apiUrl)
        console.log(apiKey)
        return '내일은 맑음 '
    }
}

실제로 api 서비스를 해보기

npm install axios ## api 요청을 위한 라이브러리 

weather.controller.ts 작성

import { Controller, Get, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
// api 요청 라이브러리 
import axios from 'axios';

@Controller('weather')
export class WeatherController {
    // logger 선언 
    private readonly logger = new Logger(WeatherController.name)
    constructor(private configService: ConfigService){}

    @Get() 
    public async getWeather(): Promise<string> {
        // 환경 변수 값 가져 오기 
        const apiUrl = this.configService.get<string>('WEATHER_API_URL')
        const apiKey = this.configService.get<string>('WEATHER_API_KEY')
        console.log(apiUrl)
        console.log(`apiUrl type : ${typeof apiUrl}`)
        // 내부함수인 callWeatherAPI() 호출
        try {
            const weatherData = await this.callWeatherApi(apiUrl, apiKey)
            return weatherData
        } catch (error) {
            this.logger.error('Error fetching weather data', error.stack)
            return '날씨 정보 가져오는것 실패'
        }

    }
    private async callWeatherApi(apiUrl: string, apiKey: string): Promise<string> {
        console.log('함수 진입')
        this.logger.log('날씨 정보 가져오는중 ...')
        console.log(apiUrl)
        // this.logger.log(`API URL : ${apiUrl}${apiKey}`)
        console.log("asfdaf")
        try {
            const response = await  axios.get(apiUrl, {
                params: {
                    appid: apiKey,
                    q: 'Seoul' // 서울 날씨 
                }
            })

            // this.logger.debug(`API response: ${JSON.stringify(response.data)}`)
            // 응답 데이터에서 필요한 정보 추출
            const forecast = response.data.list[0];
            const description = forecast.weather[0].description;
            const temp = forecast.main.temp;
            const humidity = forecast.main.humidity;

            // 추출한 정보로 문자열 생성
            return `내일의 날씨는: ${description}, 온도: ${temp}K, 습도: ${humidity}%`

        } catch (error) {
            this.logger.error('Error occurred api call',error.stack)
            throw new Error('api call fail')
        }
    }
}

9.5 여러 환경변수 파일 사용

  • 현업에서는 dev, qa, beta, prod 용 환경변수를 만듬.
  • NODE_ENV 에 값을 할당해 환경별로 서버 설정을 다르게 적용 할수 있음

9.5.1 환경별로 서버가 기동되도록 스크립트 수정

(1) local,dev,prod 환경에서 서버기동 package.json 의 script 항목에 스크립트 추가해야함.

start, start:dev, start:prod 를 수정

    "start": "NODE_ENV=local&& nest start",
    "start:dev": "NODE_ENV=dev && nest start --watch",
    "start:debug": "nest start --debug --watch",
    "start:prod": "NODE_ENV=prod&& node dist/main",

9.5.2 local, dev, prod 환경 변수 생성

(1) 프로젝트 root에 envs 라는 디렉토리 생성

(2) envs/prod.env > SERVICE_URL = http://localhost:3000

등으로 각각 dev.env, local.env , prod.env 를 생성하여 service url 을
분류하여 나눈다.

9.5.3 환경변수에 다라 다른 환경 변수 파일을 사용하도록 설정 수정

(1) app.module.ts 를 수정

@Module({
  imports: [ConfigModule.forRoot({ 
    isGlobal: true,
    envFilePath : `../envs/${process.env.NODE_ENV}.env`,
    ignoreEnvFile : false
  }), WeatherModule],
  • {process.cwd()} 이게 안먹는것같아서 .. 으로 대체하니 해결됨

9.5.4 테스트용 핸들러 함수로 테스트

(1) http://localhost:3000/service-url 에 접속해 환경별로 추가한 service_url 환경변수가 화면에 표시되도록 테스트

9.6 커스텀 환경설정 파일 사용하기

9.6.1 환경 변수 파일 생성

(1) 실행 시 logLevel, apiVersion, MESSAGE 등 공통으로 사용할 환경 변수 정의하는 파일을 정의 configs/config.ts, configs/dev.ts,configs/local.ts, configs/prod.ts

// configs/config.ts
import common from "./common";
import local from "./local";
import dev from "./dev";
import prod from "./prod";
const phase = process.env.NODE_ENV
let conf = {}
if (phase === 'local') {
    conf = local
} else if (phase ==='dev') {
    conf = dev
} else if (phase ==='prod') {
    conf = prod
}
export default () => ({
    ...common,
    ...conf
})
// configs/common.ts
export default {
    logLevel: 'info',
    apiVersion: '1.0.0',
    MESSAGE : 'hello'
}
// configs/dev.ts
export default {
    logLevel: 'debug',
    dbInfo: 'http://dev-mysql:3306'
}

// configs/local.ts
export default {
    dbInfo: 'http://localhost:3306'
}
// configs/prod.ts
export default {
    logLevel: 'error',
    dbInfo: 'http://prod-mysql:3306'
}

9.6.2 ConfigModule 에 load 옵션 추가

(1) 커스텀 파일 설정을위해 load 옵션 추가

@Module({
  imports: [ConfigModule.forRoot({ 
    isGlobal: true,
    envFilePath : `../envs/${process.env.NODE_ENV}.env`,
    // ignoreEnvFile : false,
    // load 옵션을 사용해 커스텀 설정 파일 추가 
    load: [config]

9.7