
Nordbird 앱의 익스프레스 미들웨어 중 일부, express-session을 개발용에서 배포용으로 설정
lecture-api/app.js
...
const helmet = require('helmet');
const hpp = require('hpp');
const redis = require('redis');
const RedisStore = require('connect-redis')(session);
dotenv.config();
...
app.enable('trust proxy'); // proxy 서버 사용 시 있으면 좋음
nunjucks.configure('views', {
express: app,
watch: true,
});
sequelize.sync({ force: false })
.then(() => {
console.log('데이터베이스 연결 성공');
})
.catch((err) => {
console.error(err);
});
// 추가
if (process.env.NODE_ENV === 'production') {
app.enable('trust proxy');
app.use(morgan('combined'));
app.use(helmet( contentSecurityPolicy: false ));
app.use(hpp());
} else {
app.use(morgan('dev'));
}
app.use(express.static(path.join(__dirname, 'public')));
...
app.use(cookieParser(process.env.COOKIE_SECRET));
const sessionOption = {
resave: false,
saveUninitialized: false,
secret: process.env.COOKIE_SECRET,
cookie: {
httpOnly: true,
secure: false,
},
};
if (process.env.NODE_ENV === 'production') { // 배포 모드인 경우
sessionOption.proxy = true;
// sessionOption.cookie.secure = true; // http 적용 시엔 주석 해제
}
app.use(session(sessionOption));
app.use(passport.initialize());
...
json은 변수 사용 불가(process.env 사용 불가) -> config.json을 config.js로 변경한다.
config.js
require('dotenv').config();
module.exports = {
development: {
username: 'root',
password: process.env.SEQUELIZE_PASSWORD,
database: 'nodebird',
host: '127.0.0.1',
dialect: 'mysql',
},
test: {
username: "root",
password: process.env.SEQUELIZE_PASSWORD,
database: 'nodebird_test',
host: '127.0.0.1',
dialect: 'mysql',
},
production: {
username: "root",
password: process.env.SEQUELIZE_PASSWORD,
database: 'nodebird',
host: '127.0.0.1',
dialect: 'mysql',
logging: false,
},
};
.env
COOKIE_SECRET=nodebirdsecret
JWT_SECRET=nodebirdjwt
SEQUELIZE_PASSWORD=dbpassword
package.json 수정
{
...
"scripts": {
"start": "NODE_ENV=production PORT=80 node server",
"dev": "nodemon server",
"test": "jest"
},
...
}
cross-env
: 동적으로 process.env(환경 변수)를 변경
( + 모든 운영체제에서 동일한 바식으로 환경 변수 변경)
위의 package.json에서 process.env를 설정할 수 없기 때문에 cross-env를 설치해서 사용한다.npm i cross-envpackage.json은 다음과 같이 수정한다.
"start": "cross-env NODE_ENV=production PORT=80 node server",
sanitize-html, csurf 패키지는 각각 XSS, CSRF공격을 막기 위한 페이지이다.
XSS(Cross Site Scripting)
: 악의적인 사용자가 사이트에 스크립트를 삽입하는 공격
악성 사용자가 게시글/댓글 등을 업로드할 때 악성 스크립트를 삽입해 놓은 후, 다른 사용자가 해당 게시글/댓글을 클릭했을 때 악성 스크립트가 실행되도록 유도
해결책 : 서버에서 게시글/댓글 등이 업로드되는 경우 스크립트가 포함되어 있는지 검사(sanitize-html 라이브러리 사용)
CSRF(Cross Site Request Forgery)
: 사용자가 의도치 않게 공격자가 의도한 행동을 하게 만드는 공격
저절로 로그아웃/게시글 작성, 혹은 다른 사람에게 송금하는 공격적 행동까지 유도 가능
해결책 : 내가 한 행동이 내가 한 것이 맞다는 사실 인증 필요
-> CSRF 토큰 사용(csurf 패키지)
pm2 : 에러로 서버가 꺼진 경우 다시 켜주는 역할
npm i pm2
pm2 -> 콘솔에 입력하는 명령어
package.json 수정
(nodemon 대신 pm2 사용)

-i 0을 삭제-i 뒤에는 생성되길 원하는 프로세스 개수 기입(0은 현재 코어 개수만큼, -1은 프로세스를 CPU 코어 개수보다 한 개 덜 생성)pm2는 노드 프로세스를 백그라운드로 실행 -> 콘솔에 다른 명령어 사용 가능
백그라운드의 노드 프로세스 확인 방법npx pm2 listnpx pm2 list 실행한 콘솔
노드 프로세스 종료
npx pm2 kill서버 재시작
npx pm2 reload all켜진 프로세스 모니터링
npx pm2 monit
: console.log, console.error 는 메모리 기반 -> 서버 끄면 휘발
대체 모듈 : winston
=> 로그를 파일이나 다른 데이터베이스에 저장하는데 사용
npm i winston
const { createLogger, format, transports } = require('winston')
const logger = createLogger ({
level: 'info',
format.json(),
transports: [
new transports.File({ filename: 'combined_log'}),
new transports.File({ filename: 'error.log', level: 'error' }),
],
});
if (process.env.NODE_ENV !== 'production') {
logger.add(new transports.Console({ format: format.simple() }));
}
module.exports = logger;
app.js
...
const logger = require('./logger');
...
app.use((req, res, next) => {
const error = new Error(`${req.method} ${req.url} 라우터가 없습니다.`);
error.status = 404;
logger.info('hello');
logger.error(error.message);
next(error);
});
...
: 서버의 각종 취약점을 보완해주는 패키지, 익스프레스 미들웨어로서 사용 가능
패키지 설치
npm i helmet hpp
배포 환경에서나 적용
...
if (process.env.NODE_ENV === 'production') {
app.enable('trust proxy');
app.use(morgan('combined'));
app.use(helmet({
contentSecourityPolicy: false,
crossOriginEmbedderPolicy: false,
crossOriginResourcePolicy: false,
}));
app.use(hpp());
} else {
app.use(morgan('dev'));
}
...
기존에는 로그인할 때 사용한 express-session의 세션 id와 실제 사용자 정보가 모두 메모리에 저장되어 있었기 때문에 서버 종료되면 모두 날아감
-> Redis라는 별도 메모리 기반 데이터베이스에 정보를 저장
: 멀티 프로세션 간 세션 공유를 위해 Redis와 express를 연결해주는 패키지
redis라는 DB는 따로 설치가 필요
패키지 설치
이건 mysql같은 드라이브에 가까움
npm i redis connect-redis
진짜 Redis 설치
1. Redis 공식 홈페이지에 로그인
2. DB 정보를 입력한다.

3. Public endpoint와 Default user password를 각각 찾아 ENV의 REDIS_HOST, REDIS_PASSWORD에 입력한다.
![]() | ![]() |
|---|


app.js
...
const redis = require('redis');
const RedisStore = require('connect-redis')(session);
const logger = require('./logger');
dotenv.config();
const redisClient = redis.createClient({
url:`redis://${process.env.REDIS_HOST}:${process.env.REDIS_PORT}`,
password: process.env.REDIS_PASSWORD,
legacyMode: true,
})
...
app.use(cookieParser(process.env.COOKIE_SECRET));
// 수정
const sessionOption = {
resave: false,
saveUninitialized: false,
secret: process.env.COOKIE_SECRET,
cookie: {
httpOnly: true,
secure: false,
},
store: new RedisStore({ client: redisClient }),
};
...
express-rate-limit
express-rate-limit도 메모리에 사용량 기록
-> Redis에 기록하면 좋음
(rate-limit-redis + express-rate-limit 같이 사용하면 됨)
이전에 먼저 git과 github 설치 필요
github에 node-deploy 리포지토리 하나 만든다.

프로젝트를 Nodebird에 올린다.
AWS 웹 사이트에 접속해 회원 가입한다.
리전 설정(한국으로)

lightsail 검색

인스턴스 생성

Node.js 선택

서버 생성된 것을 확인

인스턴스로 들어간 후 'ssh를 사용하여 연결' 클릭

새로 뜬 SSH 창에 다음과 같은 명령어를 차례대로 입력

무엇을 설치할 지 물어보면 MySQL Server & Cluster(Currently selected: mysql-8.0)으로 설치한다.
다음과 같은 명령어를 차례로 입력한다.
sudo apt update
sudo apt-get install -y mysql-server
mysql-server 설치 중 비밀번호 설정 화면이 나타나면 NordBird 실습 시 사용한 MySQL 비밀번호를 입력하는 것이 좋음
Use Legacy Authentication Method 선택
MySQL에 접속해 정상 작동하는지 확인
sudo mysql -u root -p
git clone https://--/node-deploy
레포지토리가 public이냐 private이냐에 따라 clone 방식이 달라진다.
- public인 경우 : 비밀번호 필요X
- private인 경우 : username, password에 나오는데 password에는 깃허브 계정에서 발급한 토큰을 넣어야 한다.
(자세한 건 여기 참고)
node-deploy 폴더가 생성됨을 확인
npm ci로 node_modules를 설치한다.(npm i는 사용x)안에 .env 파일을 수동으로 작성해 준다.
서버 실행 전 Lightsail에선 기본적으로 비트나미 아파치 서버가 켜져 있음 -> 종료
cd /opt/bitnami
sudo ./ctlscript.sh stop apache
서버 실행 확인 ->
sudo pm2 list
이래도 서버가 실행되지 않았다면
sudo pm2 logs --err명령어 입력해서 어떤 에러 가 발생했는지 확인
-> 에러 해결 후sudo pm2 reload all로 재시작
추후 소스 코드 수정해서 업데이트한 내용으로 배포 -> git pull 사용
git pull sudo pm2 reload all
http://퍼블릭IP로 접속