๐Ÿ”ŽAWS๋กœ ๋ฐฐํฌํ•˜๊ธฐ

์„œ๊ฐ€ํฌยท2021๋…„ 12์›” 18์ผ
0

Node.js

๋ชฉ๋ก ๋ณด๊ธฐ
13/15
post-thumbnail

์„œ๋น„์Šค ์šด์˜์„ ์œ„ํ•œ ํŒจํ‚ค์ง€

1. ์‹ค ์„œ๋น„์Šค ๋ฐฐํฌ ์ค€๋น„ํ•˜๊ธฐ

์„œ๋น„์Šค ๊ฐœ๋ฐœ ์‹œ์—๋Š” localhost๋กœ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ”๋กœ ๋ณผ ์ˆ˜ ์žˆ์—ˆ์Œ

  • ํ˜ผ์ž๋งŒ ๋ณผ ์ˆ˜ ์žˆ๊ธฐ์— ๋‹ค๋ฅธ ์‚ฌ๋žŒ์—๊ฒŒ ๊ณต๊ฐœํ•˜๋Š” ๊ณผ์ •์ด ํ•„์š”
  • 9์žฅ NodeBird ์•ฑ์„ ๋ฐฐํฌํ•ด๋ณผ ๊ฒƒ์ž„
    ๋ฐฐํฌ๋ฅผ ์œ„ํ•œ ์‚ฌ์ „ ์ž‘์—… ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์•Œ์•„๋ด„
  • ์„œ๋ฒ„ ์‹คํ–‰ ๊ด€๋ฆฌ, ์—๋Ÿฌ ๋‚ด์—ญ ๊ด€๋ฆฌ, ๋ณด์•ˆ ์œ„ํ˜‘ ๋Œ€์ฒ˜
  • AWS์™€ GCP์— ๋ฐฐํฌ

2. morgan

๊ฐœ๋ฐœ์šฉ์œผ๋กœ ์„ค์ •๋œ ์ต์Šคํ”„๋ ˆ์Šค ๋ฏธ๋“ค์›จ์–ด๋ฅผ ๋ฐฐํฌ์šฉ์œผ๋กœ ์ „ํ™˜

  • process.env.NODE_ENV๋Š” ๋ฐฐํฌ ํ™˜๊ฒฝ์ธ์ง€ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์ธ์ง€๋ฅผ ํŒ๋‹จํ•  ์ˆ˜ ์žˆ๋Š” ํ™˜๊ฒฝ ๋ณ€์ˆ˜
  • ๋ฐฐํฌ ํ™˜๊ฒฝ์ผ ๋•Œ๋Š” combined ์‚ฌ์šฉ(๋” ๋งŽ์€ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๋กœ๊ทธ๋กœ ๋‚จ๊น€)
  • NODE_ENV๋Š” ๋’ค์— ๋‚˜์˜ค๋Š” cross-env์—์„œ ์„ค์ •ํ•ด์คŒ

3. express-session

์„ค์ •๋“ค์„ ๋ฐฐํฌ์šฉ๊ณผ ๊ฐœ๋ฐœ์šฉ์œผ๋กœ ๋ถ„๊ธฐ ์ฒ˜๋ฆฌ

  • production์ผ ๋•Œ๋Š” proxy๋ฅผ true, secure๋ฅผ true๋กœ
  • ๋‹จ, https๋ฅผ ์ ์šฉํ•  ๊ฒฝ์šฐ์—๋งŒ secure๋ฅผ true๋กœ ํ•˜๊ณ , ๋…ธ๋“œ ์•ž์— ๋‹ค๋ฅธ ์„œ๋ฒ„๋ฅผ ๋‘์—ˆ์„ ๋•Œ proxy๋ฅผ true๋กœ ํ•จ

4. sequelize

์‹œํ€„๋ผ์ด์ฆˆ ์„ค์ •๋„ ํ•˜๋“œ์ฝ”๋”ฉ ๋Œ€์‹  process.env๋กœ ๋ณ€๊ฒฝ

  • JSON ํŒŒ์ผ์€ ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์œผ๋ฏ€๋กœ JS ํŒŒ์ผ์„ ์„ค์ • ํŒŒ์ผ๋กœ ์จ์•ผ ํ•จ
  • config.json์„ ์ง€์šฐ๊ณ  config.js ์‚ฌ์šฉ

๐Ÿ”ปconfig/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
KAKAO_ID=5d4daf57becfd72fd9c919882552c4a6
SEQUELIZE_PASSWORD=nodejsbook
REDIS_HOST=redis-18954.c92.us-east-1-3.ec2.cloud.redislabs.com
REDIS_PORT=18954
REDIS_PASSWORD=JwTwGgKM4P0OFGStgQDgy2AcXvZjX4dc

5. cross-env

๋™์ ์œผ๋กœ process.env ๋ณ€๊ฒฝ ๊ฐ€๋Šฅ

  • ์šด์˜์ฒด์ œ ์ƒ๊ด€ ์—†์ด ์ผ๊ด„ ์ ์šฉ ๊ฐ€๋Šฅ(๋งฅ, ์œˆ๋„, ๋ฆฌ๋ˆ…์Šค)
  • package.json์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜์ •(๋ฐฐํฌ์šฉ๊ณผ ๊ฐœ๋ฐœ์šฉ ์Šคํฌ๋ฆฝํŠธ ๊ตฌ๋ถ„)
  • ๋ฌธ์ œ์ : ์œˆ๋„์—์„œ๋Š” NODE_ENV๋ฅผ ์œ„์™€ ๊ฐ™์ด ์„ค์ •ํ•  ์ˆ˜ ์—†์Œ
  • ์ด ๋•Œ cross-env๊ฐ€ ํ•„์š”

๐Ÿ”ปpackage.json

{
  "name": "nodebird",
  "version": "0.0.1",
  "description": "์ต์Šคํ”„๋ ˆ์Šค๋กœ ๋งŒ๋“œ๋Š” SNS ์„œ๋น„์Šค",
  "main": "server.js",
  "scripts": {
    "start": "cross-env NODE_ENV=production PORT=80 pm2 start server.js -i 0",
    "dev": "nodemon server",
    "test": "jest"
  },
  "author": "ZeroCho",
  "license": "MIT",
  "dependencies": {
    "bcrypt": "^3.0.7",
    "connect-redis": "^4.0.4",
    "cookie-parser": "^1.4.3",
    "cross-env": "^7.0.0",
    "dotenv": "^8.2.0",
    "express": "^4.16.3",
    "express-session": "^1.15.6",
    "helmet": "^3.21.3",
    "hpp": "^0.2.3",
    "morgan": "^1.9.1",
    "multer": "^1.4.2",
    "mysql2": "^2.0.2",
    "nunjucks": "^3.2.0",
    "passport": "^0.4.0",
    "passport-kakao": "1.0.0",
    "passport-local": "^1.0.0",
    "pm2": "^4.2.3",
    "redis": "^3.0.2",
    "sequelize": "^5.21.3",
    "sequelize-cli": "^5.5.1",
    "winston": "^3.2.1"
  },
  "devDependencies": {
    "jest": "^24.9.0",
    "nodemon": "^2.0.2",
    "supertest": "^4.0.2"
  }
}

6. cross-env

cross-env ์„ค์น˜ ํ›„ ์ ์šฉ

npm i cross-env

  • package.json๋„ ์ˆ˜์ •

7. sanitize-html

XSS(Cross Site Scripting) ๊ณต๊ฒฉ ๋ฐฉ์–ด

  • npm i sanitize-html
  • ํ—ˆ์šฉํ•˜์ง€ ์•Š์€ html ์ž…๋ ฅ์„ ๋ง‰์Œ
  • ์•„๋ž˜์ฒ˜๋Ÿผ ๋นˆ ๋ฌธ์ž์—ด๋กœ ์น˜ํ™˜๋จ

8. csrf

CSRF(Cross Site Request Forgery) ๊ณต๊ฒฉ ๋ฐฉ์–ด

npm i csurf

  • csrfToken์„ ์ƒ์„ฑํ•ด์„œ ํ”„๋ŸฐํŠธ๋กœ ๋ณด๋‚ด์ฃผ๊ณ (์ฟ ํ‚ค๋กœ)
  • Form ๋“ฑ๋ก ์‹œ csrfToken์„ ๊ฐ™์ด ๋ฐ›์•„ ์ผ์น˜ํ•˜๋Š”์ง€ ๋น„๊ต

9. pm2 ์†Œ๊ฐœ

์›ํ™œํ•œ ์„œ๋ฒ„ ์šด์˜์„ ์œ„ํ•œ ํŒจํ‚ค์ง€

  • ์„œ๋ฒ„๊ฐ€ ์—๋Ÿฌ๋กœ ์ธํ•ด ๊บผ์กŒ์„ ๋•Œ ์„œ๋ฒ„๋ฅผ ๋‹ค์‹œ ์ผœ ์คŒ
  • ๋ฉ€ํ‹ฐ ํ”„๋กœ์„ธ์‹ฑ ์ง€์›(๋…ธ๋“œ ํ”„๋กœ์„ธ์Šค ์ˆ˜๋ฅผ 1๊ฐœ ์ด์ƒ์œผ๋กœ ๋Š˜๋ฆด ์ˆ˜ ์žˆ์Œ)
  • ์š”์ฒญ์„ ํ”„๋กœ์„ธ์Šค๋“ค์— ๊ณ ๋ฅด๊ฒŒ ๋ถ„๋ฐฐ
  • ๋‹จ์ : ํ”„๋กœ์„ธ์Šค๊ฐ„ ์„œ๋ฒ„์˜ ๋ฉ”๋ชจ๋ฆฌ ๊ฐ™์€ ์ž์› ๊ณต์œ  ๋ถˆ๊ฐ€
  • ๊ทน๋ณต: memcached๋‚˜ redis๊ฐ™์€ ๋ฉ”๋ชจ๋ฆฌ DB ์‚ฌ์šฉ(๊ณต์œ  ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ๋ณ„๋„ DB์— ์ €์žฅ)

10. pm2 ์‚ฌ์šฉํ•˜๊ธฐ

pm2 ์ „์—ญ ์„ค์น˜ ํ›„, ๋ช…๋ น์–ด ์‚ฌ์šฉ

npm i pm2

  • package.json ์Šคํฌ๋ฆฝํŠธ ์ˆ˜์ •
  • pm2 start ํŒŒ์ผ๋ช…์œผ๋กœ ์‹คํ–‰

11. ํ”„๋กœ์„ธ์Šค ๋ชฉ๋ก ํ™•์ธํ•˜๊ธฐ

pm2 list๋กœ ํ”„๋กœ์„ธ์Šค ๋ชฉ๋ก ํ™•์ธ ๊ฐ€๋Šฅ

  • ํ”„๋กœ์„ธ์Šค๊ฐ€ ๋ฐฑ๊ทธ๋ผ์šด๋“œ๋กœ ๋Œ์•„๊ฐ€๊ธฐ ๋•Œ๋ฌธ์— ์ฝ˜์†”์— ๋‹ค๋ฅธ ๋ช…๋ น์–ด ์ž…๋ ฅ ๊ฐ€๋Šฅ


12. pm2๋กœ ๋ฉ€ํ‹ฐ ํ”„๋กœ์„ธ์‹ฑํ•˜๊ธฐ

pm2 start [ํŒŒ์ผ๋ช…] โ€“i [ํ”„๋กœ์„ธ์Šค ์ˆ˜] ๋ช…๋ น์–ด๋กœ ๋ฉ€ํ‹ฐ ํ”„๋กœ์„ธ์‹ฑ ๊ฐ€๋Šฅ

  • ํ”„๋กœ์„ธ์Šค ์ˆ˜์— ์›ํ•˜๋Š” ํ”„๋กœ์„ธ์Šค์˜ ์ˆ˜ ์ž…๋ ฅ
  • 0์ด๋ฉด CPU ์ฝ”์–ด ๊ฐœ์ˆ˜๋งŒํผ ์ƒ์„ฑ, -1์ด๋ฉด CPU ์ฝ”์–ด ๊ฐœ์ˆ˜๋ณด๋‹ค 1๊ฐœ ์ ๊ฒŒ ์ƒ์„ฑ
  • -1์€ ํ•˜๋‚˜์˜ ํ”„๋กœ์„ธ์Šค๋ฅผ ๋…ธ๋“œ ์™ธ์˜ ์ž‘์—… ์ˆ˜ํ–‰์„ ์œ„ํ•ด ํ’€์–ด์ฃผ๋Š” ๊ฒƒ

๐Ÿ”ปpackage.json

{
  "name": "nodebird",
  "version": "0.0.1",
  "description": "์ต์Šคํ”„๋ ˆ์Šค๋กœ ๋งŒ๋“œ๋Š” SNS ์„œ๋น„์Šค",
  "main": "server.js",
  "scripts": {
    "start": "cross-env NODE_ENV=production PORT=80 pm2 start server.js -i 0",
    "dev": "nodemon server",
    "test": "jest"
  },
  "author": "ZeroCho",
  "license": "MIT",
  "dependencies": {
    "bcrypt": "^3.0.7",
    "connect-redis": "^4.0.4",
    "cookie-parser": "^1.4.3",
    "cross-env": "^7.0.0",
    "dotenv": "^8.2.0",
    "express": "^4.16.3",
    "express-session": "^1.15.6",
    "helmet": "^3.21.3",
    "hpp": "^0.2.3",
    "morgan": "^1.9.1",
    "multer": "^1.4.2",
    "mysql2": "^2.0.2",
    "nunjucks": "^3.2.0",
    "passport": "^0.4.0",
    "passport-kakao": "1.0.0",
    "passport-local": "^1.0.0",
    "pm2": "^4.2.3",
    "redis": "^3.0.2",
    "sequelize": "^5.21.3",
    "sequelize-cli": "^5.5.1",
    "winston": "^3.2.1"
  },
  "devDependencies": {
    "jest": "^24.9.0",
    "nodemon": "^2.0.2",
    "supertest": "^4.0.2"
  }
}

13. ์„œ๋ฒ„ ์ข…๋ฃŒ ํ›„ ๋ฉ€ํ‹ฐ ํ”„๋กœ์„ธ์‹ฑ ํ•˜๊ธฐ

pm2 kill๋กœ ํ”„๋กœ์„ธ์Šค ์ „์ฒด ์ข…๋ฃŒ ๊ฐ€๋Šฅ

npx pm2 kill && npm start

  • ์žฌ์‹œ์ž‘ํ•˜๋ฉด ํ”„๋กœ์„ธ์Šค๊ฐ€ CPU ์ฝ”์–ด ๊ฐœ์ˆ˜๋งŒํผ ์‹คํ–‰๋จ

14. ํ”„๋กœ์„ธ์Šค ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๊ธฐ

pm2 monit์œผ๋กœ ํ”„๋กœ์„ธ์Šค ๋ชจ๋‹ˆํ„ฐ๋ง

npx pm2 monit

  • ํ”„๋กœ์„ธ์Šค๋ณ„๋กœ ๋กœ๊ทธ๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ณผ ์ˆ˜ ์žˆ์Œ

15. winston

console.log์™€ console.error๋ฅผ ๋Œ€์ฒดํ•˜๊ธฐ ์œ„ํ•œ ๋ชจ๋“ˆ

  • ์œ„ ๋‘ ๋ฉ”์„œ๋“œ๋Š” ํœ˜๋ฐœ์„ฑ
  • ๋กœ๊ทธ๋ฅผ ํŒŒ์ผ์— ๊ธฐ๋กํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Œ
  • ์œˆ์Šคํ„ด ์„ค์น˜ ํ›„ logger.js ์ž‘์„ฑ![]

    npm i winston

๐Ÿ”ปlogger.js

const { createLogger, format, transports } = require('winston');

const logger = createLogger({
  level: 'info',
  format: 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;

16. winston ๋ฉ”์„œ๋“œ

createLogger๋กœ ๋กœ๊ฑฐ ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑ

  • level์€ ๋กœ๊ทธ์˜ ์‹ฌ๊ฐ๋„(error, warn, info, verbose, debug, silly ์ˆœ, ์ค‘์š”๋„ ์ˆœ)
  • info๋ฅผ ๊ณ ๋ฅธ ๊ฒฝ์šฐ info๋ณด๋‹ค ์‹ฌ๊ฐํ•œ ๋‹จ๊ณ„ ๋กœ๊ทธ๋„ ๊ฐ™์ด ๊ธฐ๋ก๋จ
  • format์€ ๋กœ๊ทธ์˜ ํ˜•์‹(json, label, timestamp, printf, combine, simple ๋“ฑ ์ง€์›)
  • ๊ธฐ๋ณธ์ ์œผ๋กœ๋Š” JSON์œผ๋กœ ๊ธฐ๋กํ•˜์ง€๋งŒ ๋กœ๊ทธ ์‹œ๊ฐ„์„ ํ‘œ์‹œํ•˜๋ ค๋ฉด timestamp๋ฅผ ์“ฐ๋Š” ๊ฒŒ ์ข‹์Œ
  • transports๋Š” ๋กœ๊ทธ ์ €์žฅ ๋ฐฉ์‹
  • new transports.File์€ ํŒŒ์ผ๋กœ ์ €์žฅํ•œ๋‹ค๋Š” ๋œป, new transports.Console์€ ์ฝ˜์†”์— ์ถœ๋ ฅํ•œ๋‹ค๋Š” ๋œป
  • ์ธ์ž๋กœ filename(ํŒŒ์ผ๋ช…), level(์‹ฌ๊ฐ๋„) ์ œ๊ณต

17. winston ์ ์šฉํ•˜๊ธฐ

๐Ÿ”ปapp.js

const express = require('express');
const cookieParser = require('cookie-parser');
const morgan = require('morgan');
const path = require('path');
const session = require('express-session');
const nunjucks = require('nunjucks');
const dotenv = require('dotenv');
const passport = require('passport');
const helmet = require('helmet');
const hpp = require('hpp');
const redis = require('redis');
const RedisStore = require('connect-redis')(session);

dotenv.config();
const redisClient = redis.createClient({
  url: `redis://${process.env.REDIS_HOST}:${process.env.REDIS_PORT}`,
  password: process.env.REDIS_PASSWORD,
});
const pageRouter = require('./routes/page');
const authRouter = require('./routes/auth');
const postRouter = require('./routes/post');
const userRouter = require('./routes/user');
const { sequelize } = require('./models');
const passportConfig = require('./passport');
const logger = require('./logger');

const app = express();
passportConfig(); // ํŒจ์ŠคํฌํŠธ ์„ค์ •
app.set('port', process.env.PORT || 8001);
app.set('view engine', 'html');
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('/img', express.static(path.join(__dirname, 'uploads')));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
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 }),
};
if (process.env.NODE_ENV === 'production') {
  sessionOption.proxy = true;
  // sessionOption.cookie.secure = true;
}
app.use(session(sessionOption));
app.use(passport.initialize());
app.use(passport.session());

app.use('/', pageRouter);
app.use('/auth', authRouter);
app.use('/post', postRouter);
app.use('/user', userRouter);

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);
});

app.use((err, req, res, next) => {
  console.error(err);
  res.locals.message = err.message;
  res.locals.error = process.env.NODE_ENV !== 'production' ? err : {};
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

18. winston ๋กœ๊ทธ ํ™•์ธํ•˜๊ธฐ

npm run dev๋กœ ๊ฐœ๋ฐœ์šฉ ์„œ๋ฒ„ ์‹คํ–‰

  • http://localhost:8001/abcd ์— ์ ‘์†
  • ๊ฐ๊ฐ์˜ ๋กœ๊ทธ๊ฐ€ ํŒŒ์ผ์— ๊ธฐ๋ก๋จ

  • ํŒŒ์ผ์— ๋กœ๊ทธ๊ฐ€ ์ €์žฅ๋˜์–ด ๊ด€๋ฆฌ ๊ฐ€๋Šฅ
  • winston-daily-rotate-file์ด๋ผ๋Š” ํŒจํ‚ค์ง€๋กœ ๋‚ ์งœ๋ณ„๋กœ ๊ด€๋ฆฌ ๊ฐ€๋Šฅ

19. helmet, hpp๋กœ ๋ณด์•ˆ ๊ด€๋ฆฌํ•˜๊ธฐ

๋ชจ๋“  ์ทจ์•ฝ์ ์„ ๋ฐฉ์–ดํ•ด์ฃผ์ง„ ์•Š์ง€๋งŒ ์‹ค๋ฌด์—์„œ ํ•„์ˆ˜์ธ ํŒจํ‚ค์ง€

npm i helmet hpp

  • ๋ฐฐํฌ ํ™˜๊ฒฝ์ผ ๋•Œ๋งŒ ์‚ฌ์šฉํ•˜๋ฉด ๋จ

20. connect-redis

๋ฉ€ํ‹ฐ ํ”„๋กœ์„ธ์Šค๊ฐ„ ๋ฉ”๋ชจ๋ฆฌ ๊ณต์œ ๋ฅผ ์œ„ํ•ด redis ์‚ฌ์šฉ

  • connect-redis๊ฐ€ ์ต์Šคํ”„๋ ˆ์Šค์™€ ๋ ˆ๋””์Šค๋ฅผ ์—ฐ๊ฒฐํ•ด์คŒ

npm i redis connect-redis

  • redislabs ์›น์‚ฌ์ดํŠธ ์ ‘์†, LOGIN ๋ฒ„ํŠผ ํด๋ฆญ

21. redislabs ํšŒ์›๊ฐ€์ž…ํ•˜๊ธฐ

Get Started Free ํด๋ฆญํ•ด์„œ ํšŒ์›๊ฐ€์ž…

22. redislabs ์ธ์ฆํ•˜๊ธฐ

์ธ์ฆ ์ด๋ฉ”์ผ์—์„œ Activate Now ํด๋ฆญ

23. ๋ ˆ๋””์Šค ํด๋ผ์šฐ๋“œ ํ˜ธ์ŠคํŒ… ์‚ฌ์šฉํ•˜๊ธฐ

Cloud ๋ฒ„ํŠผ ์„ ํƒ, ๊ฐœ์ธ ์ •๋ณด ์ž…๋ ฅ ํ›„ Continue

24. ๋ ˆ๋””์Šค subscription ์ƒ์„ฑ

Create your subscription ๋ฒ„ํŠผ ํด๋ฆญ

25. Subscription ์„ค์ •ํ•˜๊ธฐ

Free ์š”๊ธˆ์ œ ์„ ํƒ ํ›„ Subscription Name์„ node-deploy๋กœ ์„ค์ •.

26. ๋ ˆ๋””์Šค ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ค์ •

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ด๋ฆ„์„ node-deploy๋กœ ์ƒ์„ฑํ•˜๊ณ , Activate ๋ฒ„ํŠผ์„ ๋ˆ„๋ฆ„

27. ๋ ˆ๋””์Šค ํ˜ธ์ŠคํŒ… ์ƒ์„ฑ ์™„๋ฃŒ

Endpoint์™€ Redis password๋ฅผ ๋ณต์‚ฌํ•ด .env์— ๋ถ™์—ฌ ๋„ฃ๊ธฐ

  • endpoint์—์„œ host์™€ port ๋ถ„๋ฆฌ

๐Ÿ”ป.env

COOKIE_SECRET=nodebirdsecret
KAKAO_ID=5d4daf57becfd72fd9c919882552c4a6
SEQUELIZE_PASSWORD=nodejsbook
REDIS_HOST=redis-18954.c92.us-east-1-3.ec2.cloud.redislabs.com
REDIS_PORT=18954
REDIS_PASSWORD=JwTwGgKM4P0OFGStgQDgy2AcXvZjX4dc

28. connect-redis ์—ฐ๊ฒฐํ•˜๊ธฐ

app.js express-session ๋ฏธ๋“ค์›จ์–ด ๋ถ€๋ถ„์— store ์†์„ฑ ์ถ”๊ฐ€

  • RedisStore ์ƒ์„ฑ์ž์˜ ์ธ์Šคํ„ด์Šค๋ฅผ store ์†์„ฑ์— ๋“ฑ๋ก
  • ์ด์ œ ์„œ๋ฒ„๋ฅผ ๊ป๋‹ค ์ผœ๋„ ๋กœ๊ทธ์ธ์ด ์œ ์ง€๋จ

๐Ÿ”ปapp.js

const express = require('express');
const cookieParser = require('cookie-parser');
const morgan = require('morgan');
const path = require('path');
const session = require('express-session');
const nunjucks = require('nunjucks');
const dotenv = require('dotenv');
const passport = require('passport');
const helmet = require('helmet');
const hpp = require('hpp');
const redis = require('redis');
const RedisStore = require('connect-redis')(session);

dotenv.config();
const redisClient = redis.createClient({
  url: `redis://${process.env.REDIS_HOST}:${process.env.REDIS_PORT}`,
  password: process.env.REDIS_PASSWORD,
});
const pageRouter = require('./routes/page');
const authRouter = require('./routes/auth');
const postRouter = require('./routes/post');
const userRouter = require('./routes/user');
const { sequelize } = require('./models');
const passportConfig = require('./passport');
const logger = require('./logger');

const app = express();
passportConfig(); // ํŒจ์ŠคํฌํŠธ ์„ค์ •
app.set('port', process.env.PORT || 8001);
app.set('view engine', 'html');
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('/img', express.static(path.join(__dirname, 'uploads')));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
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 }),
};
if (process.env.NODE_ENV === 'production') {
  sessionOption.proxy = true;
  // sessionOption.cookie.secure = true;
}
app.use(session(sessionOption));
app.use(passport.initialize());
app.use(passport.session());

app.use('/', pageRouter);
app.use('/auth', authRouter);
app.use('/post', postRouter);
app.use('/user', userRouter);

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);
});

app.use((err, req, res, next) => {
  console.error(err);
  res.locals.message = err.message;
  res.locals.error = process.env.NODE_ENV !== 'production' ? err : {};
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

29. nvm, n

๋…ธ๋“œ ๋ฒ„์ „์„ ์—…๋ฐ์ดํŠธํ•˜๊ธฐ ์œ„ํ•œ ํŒจํ‚ค์ง€
์œˆ๋„์—์„œ๋Š” nvm-installer, ๋ฆฌ๋ˆ…์Šค๋‚˜ ๋งฅ์—์„œ๋Š” n ํŒจํ‚ค์ง€

30. nvm-installer

๋งํฌ์ฐธ๊ณ 
https://github.com/coreybutler/nvm-windows/releases

  • nvm-setup.zip ๋‚ด๋ ค๋ฐ›๊ณ  ์••์ถ• ํ•ด์ œ ํ›„ ์‹คํ–‰
  • nvm list๋กœ ๋…ธ๋“œ ๋ฒ„์ „ ํ™•์ธ, nvm instal๋กœ ๋ฒ„์ „ ์„ค์น˜(nvm install latest๋กœ ์ตœ์‹  ๋ฒ„์ „ ์„ค์น˜)

31. nvm-installer๋กœ ๋…ธ๋“œ ๋ฒ„์ „ ๋ฐ”๊พธ๊ธฐ

nvm use [๋ฒ„์ „๋ช…]์œผ๋กœ ์‚ฌ์šฉํ•˜๊ณ ํ”ˆ ๋ฒ„์ „ ์ž…๋ ฅ

32. ๋งฅ, ๋ฆฌ๋ˆ…์Šค์—์„œ n์œผ๋กœ ๋…ธ๋“œ ์—…๋ฐ์ดํŠธ

n์„ ์ „์—ญ ์„ค์น˜

sudo npm i โ€“g n

  • n ๋ช…๋ น์–ด๋กœ ํ˜„์žฌ ๋…ธ๋“œ ๋ฒ„์ „ ํ™•์ธ
  • n ๋ฒ„์ „์œผ๋กœ ์ƒˆ ๋ฒ„์ „ ์„ค์น˜

Git๊ณผ GitHub ์‚ฌ์šฉํ•˜๊ธฐ

1. Git์œผ๋กœ ์†Œ์Šค์ฝ”๋“œ ๊ด€๋ฆฌํ•˜๊ธฐ

์†Œ์Šค ์ฝ”๋“œ๊ฐ€ ์ˆ˜์ •๋  ๋•Œ๋งˆ๋‹ค ์ง์ ‘ ์—…๋กœ๋“œํ•˜๋Š” ๊ฒŒ ์„ฑ๊ฐ€์‹ฌ

  • ํ˜‘์—…ํ•  ๋•Œ๋„ ์„œ๋กœ ์ฝ”๋“œ๊ฐ€ ๋‹ฌ๋ผ์„œ ์ถฉ๋Œ์ด ๋ฐœ์ƒํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋ฐœ์ƒํ•จ
  • Git์ด๋ผ๋Š” ๋ถ„์‚ฐํ˜• ๋ฒ„์ „ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ์„ ๋งŽ์ด ์‚ฌ์šฉ
  • GitHub๋Š” Git์œผ๋กœ๋ถ€ํ„ฐ ์—…๋กœ๋“œํ•œ ์†Œ์Šค ์ฝ”๋“œ๋ฅผ ์„œ๋ฒ„์— ์ €์žฅํ•  ์ˆ˜ ์žˆ๋Š” ์›๊ฒฉ ์ €์žฅ์†Œ
  • ์—ฌ๋Ÿฌ ์‚ฌ๋žŒ์ด ์ฝ”๋“œ๋ฅผ ๊ณต๋™ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Œ

2~3. Git ์„ค์น˜ํ•˜๊ธฐ ์ƒ๋žต

4. git ๋ช…๋ น์–ด ์‚ฌ์šฉํ•˜๊ธฐ

์ฝ˜์†”์—์„œ git --version ์ž…๋ ฅ, ์ œ๋Œ€๋กœ ๋‚˜์˜ค๋ฉด ์„ค์น˜ ์„ฑ๊ณต

.gitignore์— git์œผ๋กœ ์†Œ์Šค ๊ด€๋ฆฌํ•˜์ง€ ์•Š์„ ํŒŒ์ผ๊ณผ ๋””๋ ‰ํ„ฐ๋ฆฌ ๋“ฑ๋ก

5~10.github ์‚ฌ์šฉ๋ฐฉ๋ฒ• ์ƒ๋žต

11. ํ”„๋กœ์ ํŠธ์— git ์„ค์ •ํ•˜๊ธฐ

nodebird ํ”„๋กœ์ ํŠธ๋กœ ์ด๋™ ํ›„ git init ๋ช…๋ น์–ด ์ž…๋ ฅ

12. ๋ชจ๋“  ํŒŒ์ผ git์— ์ถ”๊ฐ€ํ•˜๊ธฐ

.gitignore์— ์ ํžŒ ๊ฒƒ์„ ์ œ์™ธํ•œ ๋ชจ๋“  ํŒŒ์ผ์ด git ๊ด€๋ฆฌ ๋Œ€์ƒ์ด ๋จ

  • ์ (.)์€ ๋ชจ๋“  ํŒŒ์ผ์„ ์˜๋ฏธํ•จ

13. ์†Œ์Šค ์ฝ”๋“œ ์ปค๋ฐ‹ํ•˜๊ธฐ

์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์„ค์ •ํ•œ ํ›„ ์†Œ์Šค ์ฝ”๋“œ ์ปค๋ฐ‹(์ƒํƒœ ์ €์žฅ)

14. GitHub ์ฃผ์†Œ ๋“ฑ๋กํ•˜๊ธฐ

์•„์ด๋””์™€ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ถ€๋ถ„์„ ์ž์‹ ์˜ ๊ฒƒ์œผ๋กœ ๋Œ€์ฒด

15. GitHub์— ์ฝ”๋“œ ์˜ฌ๋ฆฌ๊ธฐ

git push origin master ์ž…๋ ฅ

  • ์†Œ์Šค ์ฝ”๋“œ๊ฐ€ ๊นƒํ—ˆ๋ธŒ์— ์˜ฌ๋ผ๊ฐ„ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Œ

AWS ์‹œ์ž‘ํ•˜๊ธฐ

1. AWS ์ด์šฉํ•˜๊ธฐ

aws.amazon.com/ko์— ์ ‘์†ํ•ด์„œ ํšŒ์›๊ฐ€์ž…

2~6-. AWS ์„ค์น˜๊ณผ์ • ์ƒ๋žต

7. Lightsail ์„ ํƒ

๊ฐ„๋‹จํ•˜๊ฒŒ ๋…ธ๋“œ ์„œ๋น„์Šค๋ฅผ ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ๋Š” Lightsail ์„ ํƒ

8. Lightsail ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ

์ธ์Šคํ„ด์Šค ์ƒ์„ฑ ๋ฒ„ํŠผ ํด๋ฆญ

9. Lightsail ์„ค์ •ํ•˜๊ธฐ

์œ„์น˜๋Š” ๊ฐ€๊นŒ์šด ๊ณณ์ผ ์ˆ˜๋ก ์†๋„์— ์ด์ ์ด ์žˆ์Œ, Linux์™€ Node.js ์„ ํƒ

10. ์ธ์Šคํ„ด์Šค ๊ณ„ํš ์„ ํƒ

์ฒซ ๋‹ฌ์€ ๋ฌด๋ฃŒ์ด๋ฏ€๋กœ $5 ์ธ์Šคํ„ด์Šค ์„ ํƒ, ๋‚˜์ค‘์— ์ œ๊ฑฐํ•ด์•ผ ๊ณผ๊ธˆ๋˜์ง€ ์•Š์Œ

11. ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ ํ™•์ธํ•˜๊ธฐ

์ƒ์„ฑ๋œ ์ธ์Šคํ„ด์Šค ํด๋ฆญ

12. ์ธ์Šคํ„ด์Šค ์‚ญ์ œ ๋ฐฉ๋ฒ•

AWS์— ๋ฐฐํฌํ•˜๊ธฐ

1. SSH ์—ฐ๊ฒฐํ•˜๊ธฐ

SSH๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์—ฐ๊ฒฐ ๋ฒ„ํŠผ ํด๋ฆญ

2. LightSail ์ฝ˜์†” ์—ด๊ธฐ

์ฝ˜์†” ๋ช…๋ น์–ด๋กœ ๋ช…๋ น ์‹คํ–‰

  • 7์žฅ์˜ ๋ฐฉ์‹๋Œ€๋กœ MySQL ์„ค์น˜

3. GitHub์—์„œ ์†Œ์Šค ๋‚ด๋ ค๋ฐ›๊ธฐ

git clone ๋ช…๋ น์–ด๋กœ ์†Œ์Šค ๋‚ด๋ ค๋ฐ›๊ธฐ

4. LightSail ์„œ๋ฒ„ ์‹คํ–‰ํ•˜๊ธฐ

๊ธฐ์กด์˜ ์•„ํŒŒ์น˜ ์„œ๋ฒ„ ์ข…๋ฃŒ

๋…ธ๋“œ ํ”„๋กœ์ ํŠธ ์‹คํ–‰

5. LightSail ์„œ๋ฒ„ ์ ‘์†ํ•˜๊ธฐ

ํผ๋ธ”๋ฆญ IP ํ™•์ธ ํ›„ ๋ธŒ๋ผ์šฐ์ €์— ์ž…๋ ฅํ•ด ์ ‘์†(์˜ˆ์ œ์—์„œ๋Š” 13.209.84.110)

GCP ์‹œ์ž‘ํ•˜๊ธฐ

1. GCP ์ฝ˜์†” ์ ‘์†ํ•˜๊ธฐ

https://console.cloud.google.com์— ์ ‘์†(๊ตฌ๊ธ€ ๋กœ๊ทธ์ธ ํ•„์š”)
์„œ๋น„์Šค ์•ฝ๊ด€ ๋™์˜

2. ์ƒˆ ํ”„๋กœ์ ํŠธ ์ƒ์„ฑํ•˜๊ธฐ

ํ”„๋กœ์ ํŠธ ์ด๋ฆ„์„ node-deploy๋กœ ์ด๋ฆ„ ์„ค์ •, ๋งŒ๋“ค๊ธฐ ๋ฒ„ํŠผ ํด๋ฆญ

3. GCP ๊ณ„์ • ๋“ฑ๋กํ•˜๊ธฐ

์ •๋ณด๋ฅผ ์ž…๋ ฅํ•˜๊ณ  ๊ณ„์† ๋ฒ„ํŠผ์œผ๋กœ ์ง„ํ–‰

4. ๋ฌด๋ฃŒ ํ‰๊ฐ€ํŒ ์‹œ์ž‘ํ•˜๊ธฐ

300$ ์–ด์น˜์˜ ๋ฌด๋ฃŒ ํฌ๋ ˆ๋”ง ์ œ๊ณต

5. ๊ฒฐ์ œ ์‚ฌ์šฉ ์„ค์ •ํ•˜๊ธฐ

VM ์ธ์Šคํ„ด์Šค ํ™”๋ฉด์—์„œ ํด๋ฆญ

6. VM ์ธ์Šคํ„ด์Šค ๋งŒ๋“ค๊ธฐ

VM ์ธ์Šคํ„ด์Šค ํ™”๋ฉด์—์„œ ๊ธฐ๋‹ค๋ฆฌ๋‹ค ๋ณด๋ฉด VM ์ธ์Šคํ„ด์Šค๊ฐ€ ํ™œ์„ฑํ™” ๋จ
๋งŒ๋“ค๊ธฐ ๋ฒ„ํŠผ ํด๋ฆญ

7. VM ์ธ์Šคํ„ด์Šค ๋งŒ๋“ค๊ธฐ

๋จธ์‹  ์œ ํ˜•์„ ์ดˆ์†Œํ˜•์œผ๋กœ ๋ณ€๊ฒฝ

  • ์˜์—ญ์€ us-east1, us-central-1, us-west1๋งŒ ๋ฌด๋ฃŒ
  • ์šด์˜์ฒด์ œ๋Š” Ubuntu 18.04LTS, e2-micro
  • ๋ฐฉํ™”๋ฒฝ์€ http์™€ https ํŠธ๋ž˜ํ”ฝ ๋‘˜ ๋‹ค ํ—ˆ์šฉ
  • ๋งŒ๋“ค๊ธฐ ๋ฒ„ํŠผ ํด๋ฆญ

8. VM ์ธ์Šคํ„ด์Šค ํ™•์ธํ•˜๊ธฐ

์ธ์Šคํ„ด์Šค ์ƒ์„ฑ ํ›„ ์กฐ๊ธˆ ๊ธฐ๋‹ค๋ฆฌ๋ฉด ์ธ์Šคํ„ด์Šค๊ฐ€ ์ค€๋น„๋จ

GCP์— ๋ฐฐํฌํ•˜๊ธฐ

1. SSH ์‹คํ–‰ํ•˜๊ธฐ

SSH ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ SSH ์‹คํ–‰

2. ๋ฐฐํฌ๋ฅผ ์œ„ํ•œ ํ”„๋กœ๊ทธ๋žจ ์„ค์น˜ํ•˜๊ธฐ

SSH์— ๋ช…๋ น์–ด ์ž…๋ ฅ

  • sudo su ์ž…๋ ฅ ํ›„ 1์žฅ๊ณผ 7์žฅ๋Œ€๋กœ ๋…ธ๋“œ์™€ MySQL ์„ค์น˜

3. GitHub์—์„œ ์†Œ์Šค ๋‚ด๋ ค๋ฐ›๊ธฐ

git clone ๋ช…๋ น์–ด๋กœ ์†Œ์Šค ๋‚ด๋ ค๋ฐ›๊ธฐ
ges/skh9797/post/b2cb070c-6b37-460b-8748-137340a780a1/image.png)

4. ์„œ๋ฒ„ ์‹คํ–‰ํ•˜๊ธฐ

๋…ธ๋“œ ํ”„๋กœ์ ํŠธ ์‹คํ–‰


5. ์™ธ๋ถ€ IP๋กœ ์ ‘์†ํ•˜๊ธฐ

์™ธ๋ถ€ IP ํ™•์ธ ํ›„ ๋ธŒ๋ผ์šฐ์ €์— ์ž…๋ ฅํ•ด ์ ‘์†(์˜ˆ์ œ์—์„œ๋Š” 34.64.239.223)

6. ์ถ”๊ฐ€๋กœ ์•Œ์•„๋‘˜ ์ 

https๋กœ ์ ‘์†ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ์ธ์ฆ์„œ๋ฅผ ๋ฐœ๊ธ‰๋ฐ›์•„์•ผ ํ•จ

๐Ÿ˜ƒ์ถœ์ฒ˜๐Ÿ˜ƒ
Node.js ๊ต๊ณผ์„œ - ๊ธฐ๋ณธ๋ถ€ํ„ฐ ํ”„๋กœ์ ํŠธ ์‹ค์Šต๊นŒ์ง€
https://www.inflearn.com/course/%EB%85%B8%EB%93%9C-%EA%B5%90%EA%B3%BC%EC%84%9C/dashboard

0๊ฐœ์˜ ๋Œ“๊ธ€