์๋น์ค ๊ฐ๋ฐ ์์๋ localhost๋ก ๊ฒฐ๊ณผ๋ฅผ ๋ฐ๋ก ๋ณผ ์ ์์์
๊ฐ๋ฐ์ฉ์ผ๋ก ์ค์ ๋ ์ต์คํ๋ ์ค ๋ฏธ๋ค์จ์ด๋ฅผ ๋ฐฐํฌ์ฉ์ผ๋ก ์ ํ
์ค์ ๋ค์ ๋ฐฐํฌ์ฉ๊ณผ ๊ฐ๋ฐ์ฉ์ผ๋ก ๋ถ๊ธฐ ์ฒ๋ฆฌ
์ํ๋ผ์ด์ฆ ์ค์ ๋ ํ๋์ฝ๋ฉ ๋์ process.env๋ก ๋ณ๊ฒฝ
๐ป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
๋์ ์ผ๋ก process.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"
}
}
cross-env ์ค์น ํ ์ ์ฉ
npm i cross-env
XSS(Cross Site Scripting) ๊ณต๊ฒฉ ๋ฐฉ์ด
CSRF(Cross Site Request Forgery) ๊ณต๊ฒฉ ๋ฐฉ์ด
npm i csurf
์ํํ ์๋ฒ ์ด์์ ์ํ ํจํค์ง
pm2 ์ ์ญ ์ค์น ํ, ๋ช ๋ น์ด ์ฌ์ฉ
npm i pm2
pm2 list๋ก ํ๋ก์ธ์ค ๋ชฉ๋ก ํ์ธ ๊ฐ๋ฅ
pm2 start [ํ์ผ๋ช ] โi [ํ๋ก์ธ์ค ์] ๋ช ๋ น์ด๋ก ๋ฉํฐ ํ๋ก์ธ์ฑ ๊ฐ๋ฅ
๐ป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"
}
}
pm2 kill๋ก ํ๋ก์ธ์ค ์ ์ฒด ์ข ๋ฃ ๊ฐ๋ฅ
npx pm2 kill && npm start
pm2 monit์ผ๋ก ํ๋ก์ธ์ค ๋ชจ๋ํฐ๋ง
npx pm2 monit
console.log์ console.error๋ฅผ ๋์ฒดํ๊ธฐ ์ํ ๋ชจ๋
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;
createLogger๋ก ๋ก๊ฑฐ ์ธ์คํด์ค๋ฅผ ์์ฑ
๐ป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;
npm run dev๋ก ๊ฐ๋ฐ์ฉ ์๋ฒ ์คํ
๋ชจ๋ ์ทจ์ฝ์ ์ ๋ฐฉ์ดํด์ฃผ์ง ์์ง๋ง ์ค๋ฌด์์ ํ์์ธ ํจํค์ง
npm i helmet hpp
๋ฉํฐ ํ๋ก์ธ์ค๊ฐ ๋ฉ๋ชจ๋ฆฌ ๊ณต์ ๋ฅผ ์ํด redis ์ฌ์ฉ
npm i redis connect-redis
Get Started Free ํด๋ฆญํด์ ํ์๊ฐ์
์ธ์ฆ ์ด๋ฉ์ผ์์ Activate Now ํด๋ฆญ
Cloud ๋ฒํผ ์ ํ, ๊ฐ์ธ ์ ๋ณด ์
๋ ฅ ํ Continue
Create your subscription ๋ฒํผ ํด๋ฆญ
Free ์๊ธ์ ์ ํ ํ Subscription Name์ node-deploy๋ก ์ค์ .
๋ฐ์ดํฐ๋ฒ ์ด์ค ์ด๋ฆ์ node-deploy๋ก ์์ฑํ๊ณ , Activate ๋ฒํผ์ ๋๋ฆ
Endpoint์ Redis password๋ฅผ ๋ณต์ฌํด .env์ ๋ถ์ฌ ๋ฃ๊ธฐ
๐ป.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
app.js express-session ๋ฏธ๋ค์จ์ด ๋ถ๋ถ์ 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;
๋
ธ๋ ๋ฒ์ ์ ์
๋ฐ์ดํธํ๊ธฐ ์ํ ํจํค์ง
์๋์์๋ nvm-installer, ๋ฆฌ๋
์ค๋ ๋งฅ์์๋ n ํจํค์ง
๋งํฌ์ฐธ๊ณ
https://github.com/coreybutler/nvm-windows/releases
nvm use [๋ฒ์ ๋ช
]์ผ๋ก ์ฌ์ฉํ๊ณ ํ ๋ฒ์ ์
๋ ฅ
n์ ์ ์ญ ์ค์น
sudo npm i โg n
์์ค ์ฝ๋๊ฐ ์์ ๋ ๋๋ง๋ค ์ง์ ์ ๋ก๋ํ๋ ๊ฒ ์ฑ๊ฐ์ฌ
์ฝ์์์ git --version ์
๋ ฅ, ์ ๋๋ก ๋์ค๋ฉด ์ค์น ์ฑ๊ณต
.gitignore์ git์ผ๋ก ์์ค ๊ด๋ฆฌํ์ง ์์ ํ์ผ๊ณผ ๋๋ ํฐ๋ฆฌ ๋ฑ๋ก
nodebird ํ๋ก์ ํธ๋ก ์ด๋ ํ git init ๋ช
๋ น์ด ์
๋ ฅ
.gitignore์ ์ ํ ๊ฒ์ ์ ์ธํ ๋ชจ๋ ํ์ผ์ด git ๊ด๋ฆฌ ๋์์ด ๋จ
์ฌ์ฉ์ ์ ๋ณด๋ฅผ ์ค์ ํ ํ ์์ค ์ฝ๋ ์ปค๋ฐ(์ํ ์ ์ฅ)
์์ด๋์ ๋น๋ฐ๋ฒํธ ๋ถ๋ถ์ ์์ ์ ๊ฒ์ผ๋ก ๋์ฒด
git push origin master ์ ๋ ฅ
aws.amazon.com/ko์ ์ ์ํด์ ํ์๊ฐ์
๊ฐ๋จํ๊ฒ ๋
ธ๋ ์๋น์ค๋ฅผ ๋ฐฐํฌํ ์ ์๋ Lightsail ์ ํ
์ธ์คํด์ค ์์ฑ ๋ฒํผ ํด๋ฆญ
์์น๋ ๊ฐ๊น์ด ๊ณณ์ผ ์๋ก ์๋์ ์ด์ ์ด ์์, Linux์ Node.js ์ ํ
์ฒซ ๋ฌ์ ๋ฌด๋ฃ์ด๋ฏ๋ก $5 ์ธ์คํด์ค ์ ํ, ๋์ค์ ์ ๊ฑฐํด์ผ ๊ณผ๊ธ๋์ง ์์
์์ฑ๋ ์ธ์คํด์ค ํด๋ฆญ
SSH๋ฅผ ์ฌ์ฉํ์ฌ ์ฐ๊ฒฐ ๋ฒํผ ํด๋ฆญ
์ฝ์ ๋ช ๋ น์ด๋ก ๋ช ๋ น ์คํ
git clone ๋ช
๋ น์ด๋ก ์์ค ๋ด๋ ค๋ฐ๊ธฐ
๊ธฐ์กด์ ์ํ์น ์๋ฒ ์ข
๋ฃ
๋
ธ๋ ํ๋ก์ ํธ ์คํ
ํผ๋ธ๋ฆญ IP ํ์ธ ํ ๋ธ๋ผ์ฐ์ ์ ์
๋ ฅํด ์ ์(์์ ์์๋ 13.209.84.110)
https://console.cloud.google.com์ ์ ์(๊ตฌ๊ธ ๋ก๊ทธ์ธ ํ์)
์๋น์ค ์ฝ๊ด ๋์
ํ๋ก์ ํธ ์ด๋ฆ์ node-deploy๋ก ์ด๋ฆ ์ค์ , ๋ง๋ค๊ธฐ ๋ฒํผ ํด๋ฆญ
์ ๋ณด๋ฅผ ์ ๋ ฅํ๊ณ ๊ณ์ ๋ฒํผ์ผ๋ก ์งํ
300$ ์ด์น์ ๋ฌด๋ฃ ํฌ๋ ๋ง ์ ๊ณต
VM ์ธ์คํด์ค ํ๋ฉด์์ ํด๋ฆญ
VM ์ธ์คํด์ค ํ๋ฉด์์ ๊ธฐ๋ค๋ฆฌ๋ค ๋ณด๋ฉด VM ์ธ์คํด์ค๊ฐ ํ์ฑํ ๋จ
๋ง๋ค๊ธฐ ๋ฒํผ ํด๋ฆญ
๋จธ์ ์ ํ์ ์ด์ํ์ผ๋ก ๋ณ๊ฒฝ
์ธ์คํด์ค ์์ฑ ํ ์กฐ๊ธ ๊ธฐ๋ค๋ฆฌ๋ฉด ์ธ์คํด์ค๊ฐ ์ค๋น๋จ
SSH ๋ฒํผ์ ๋๋ฌ SSH ์คํ
SSH์ ๋ช ๋ น์ด ์ ๋ ฅ
git clone ๋ช
๋ น์ด๋ก ์์ค ๋ด๋ ค๋ฐ๊ธฐ
ges/skh9797/post/b2cb070c-6b37-460b-8748-137340a780a1/image.png)
๋ ธ๋ ํ๋ก์ ํธ ์คํ
์ธ๋ถ IP ํ์ธ ํ ๋ธ๋ผ์ฐ์ ์ ์
๋ ฅํด ์ ์(์์ ์์๋ 34.64.239.223)
https๋ก ์ ์ํ๊ณ ์ถ๋ค๋ฉด ์ธ์ฆ์๋ฅผ ๋ฐ๊ธ๋ฐ์์ผ ํจ
๐์ถ์ฒ๐
Node.js ๊ต๊ณผ์ - ๊ธฐ๋ณธ๋ถํฐ ํ๋ก์ ํธ ์ค์ต๊น์ง
https://www.inflearn.com/course/%EB%85%B8%EB%93%9C-%EA%B5%90%EA%B3%BC%EC%84%9C/dashboard