문제 상황
Node.js(+express)에 AWS S3를 연결하면서 .env 파일에 다음과 같이 환경변수를 추가하였다.
# AWS S3
AWS_ACCESS_KEY=비밀
AWS_ACCESS_SECRET_KEY=비밀
AWS_REGION=ap-northeast-2
AWS_S3_BUCKET_NAME=비밀
그리고 이 환경변수를 s3 configuration 파일에서 읽어와야 하는데, 제대로 읽어오지 못 해서 계속해서 undefined로 값이 들어가는 상황이 발생하였다.
문제의 파일 's3Client.js'
import { S3Client } from '@aws-sdk/client-s3';
const s3 = () =>
new S3Client({
region: process.env.AWS_REGION,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY,
secretAccessKey: process.env.AWS_ACCESS_SECRET_KEY,
},
});
export default s3;
콘솔을 파일에 찍어서 확인해 본 결과,
index.js 의 `dotenv.config();` 구문이 실행되기 전에 위의 s3Client.js 파일이 먼저 읽히고 있었다.
이를 해결하기 위해 dotenv.config(); 구문을 가장 위로 올려보기도 하였으나 먹히지 않았다 (ㅎ;;)
index.js
import dotenv from 'dotenv';
dotenv.config();
import express from 'express';
import pool from './config/psql.js';
import cookieParser from 'cookie-parser';
import authRouter from './routes/authRouter.js';
import jwtMiddleware from './lib/jwtMiddleware.js';
import oauthRouter from './routes/oauthRouter.js';
import articleRouter from './routes/articleRouter.js';
import commentRouter from './routes/commentRouter.js';
import memberRouter from './routes/memberRouter.js';
import imageRouter from './routes/imageRouter.js';
const app = new express();
app.set('port', process.env.PORT || 4000);
app.use(express.json());
app.use(cookieParser());
// 라우터 등록
app.use(jwtMiddleware);
app.use('/api/auth', authRouter);
app.use('/api/oauth', oauthRouter);
app.use('/api/article', articleRouter);
app.use('/api/comment', commentRouter);
app.use('/api/member', memberRouter);
app.use('/api/image', imageRouter);
app.listen(app.get('port'), () => {
console.log(`Listening to ${app.get('port')} port ...`);
});
// 서버 종료 시 Pool 종료
process.on('SIGINT', async () => {
await pool.end();
console.log('\nPool had ended.');
process.exit();
});
이렇게 .. 해보았음 그러나 실패
해결 방법
열심히 구글링을 하다 들리게 된 한 블로그 (https://www.daleseo.com/js-dotenv/ ) 덕분에 이 문제를 해결할 수 있었다.
현재 ES 모듈을 사용하고 있어 생긴 문제였으며,
문제 해결의 키는 dotenv.config(); 를 실행하는 파일을 따로 두어서 이 파일을 index.js 에서 불러오는 것이다.
env.js
import dotenv from 'dotenv';
dotenv.config();
위 파일을 index.js 에서 가장 먼저 불러오도록 수정하니 바로 문제가 해결되었다.
변경된 index.js 부분
import './env.js';
알아보기
그렇다면 ES 모듈과 CommonJS가 파일을 읽어오는 방식에 어떤 차이가 있을까?
CommonJS는 동기적으로 모듈을 로드한다.
즉, require()을 통해 어떤 모듈이 전부 읽혀지기 전에는 코드의 실행이 멈춘다.
이와 반대로, ES 모듈은 비동기적으로 모듈을 로드한다.
ES 모듈은 1. import 구문들을 먼저 최상단으로 호이스팅 한 후에 2. 해당 구문들을 비동기적으로 로드한다.
이제 이것을 알고나서, 나의 예전 index.js 코드를 살펴보자
import dotenv from 'dotenv';
dotenv.config();
import express from 'express';
import pool from './config/psql.js';
import cookieParser from 'cookie-parser';
import authRouter from './routes/authRouter.js';
import jwtMiddleware from './lib/jwtMiddleware.js';
import oauthRouter from './routes/oauthRouter.js';
import articleRouter from './routes/articleRouter.js';
import commentRouter from './routes/commentRouter.js';
import memberRouter from './routes/memberRouter.js';
import imageRouter from './routes/imageRouter.js';
const app = new express();
app.set('port', process.env.PORT || 4000);
app.use(express.json());
app.use(cookieParser());
// 라우터 등록
app.use(jwtMiddleware);
app.use('/api/auth', authRouter);
app.use('/api/oauth', oauthRouter);
app.use('/api/article', articleRouter);
app.use('/api/comment', commentRouter);
app.use('/api/member', memberRouter);
app.use('/api/image', imageRouter);
app.listen(app.get('port'), () => {
console.log(`Listening to ${app.get('port')} port ...`);
});
// 서버 종료 시 Pool 종료
process.on('SIGINT', async () => {
await pool.end();
console.log('\nPool had ended.');
process.exit();
});
dotenv.config(); 를 맨 위로 올려놓아도 먼저 실행되지 않는 이유를 이제 알게 된다.
ES 모듈은 import 구문들을 먼저 최상단으로 호이스팅 하기 때문에,
어느 위치에 있든 해당 구문은 import로 모듈들이 로드되고 난 후 실행되게 된다.
그리고 문제의 s3Client.js 파일은 index.js 파일에서 로드되는 imageRouter.js 내에 또 import가 되어있다.
따라서 dotenv.config(); 구문 이전에 읽혀지게 되며 이로 인해 .env 내의 파일들이 환경변수로 설정이 되기 전에 해당 변수에 접근하게 되어 undefined로 읽힌다.
⭐️ 따라서 index 파일에서 import 호이스팅이 실행될 때 dotenv.config(); 이 함께, 그리고 먼저 실행되도록 파일로 따로 빼어서 가장 먼저 import 처리해주어야 하는 것이다.
여기서 잠깐 ...
이렇게 따로 빼준 env.js 파일을 import 문 가장 뒷쪽에 배치한다면 어떻게 될까?
가장 먼저 import문으로 선언해주었던 import './env.js'; 를 가장 뒷쪽으로 빼보았다.
그랬더니
ㅇㅇ 에러가 난다