๐Ÿ”Ž์›น API ์„œ๋ฒ„ ๋งŒ๋“ค๊ธฐ

์„œ๊ฐ€ํฌยท2021๋…„ 11์›” 20์ผ
1

Node.js

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

API ์„œ๋ฒ„ ์ดํ•ดํ•˜๊ธฐ

1. NodeBird SNS ์„œ๋น„์Šค

API: Application Programming Interface

  • ๋‹ค๋ฅธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ํ˜„์žฌ ํ”„๋กœ๊ทธ๋žจ์˜ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•จ
  • ์›น API: ๋‹ค๋ฅธ ์›น ์„œ๋น„์Šค์˜ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ์ž์›์„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๊ฒŒ ํ•จ
  • ๋‹ค๋ฅธ ์‚ฌ๋žŒ์—๊ฒŒ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•˜๊ณ  ์‹ถ์€ ๋ถ€๋ถ„๋งŒ API๋ฅผ ์—ด๊ณ , ์ œ๊ณตํ•˜๊ณ  ์‹ถ์ง€ ์•Š์€ ๋ถ€๋ถ„์€ API๋ฅผ ๋งŒ๋“ค์ง€ ์•Š์œผ๋ฉด ๋จ
  • API์— ์ œํ•œ์„ ๊ฑธ์–ด ์ผ์ • ํšŸ์ˆ˜ ๋‚ด์—์„œ๋งŒ ๊ฐ€์ ธ๊ฐ€๊ฒŒ ํ•  ์ˆ˜๋„ ์žˆ์Œ
  • NodeBird์—์„œ๋Š” ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž์—๊ฒŒ๋งŒ ์ •๋ณด ์ œ๊ณต

ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ ๊ฐ–์ถ”๊ธฐ

1. NodeBird API ํด๋” ์„ธํŒ…

nodebird-api ํด๋”๋ฅผ ๋งŒ๋“ค๊ณ  package.json ์ƒ์„ฑ

  • ์ƒ์„ฑ ํ›„ npm i๋กœ ํŒจํ‚ค์ง€ ์„ค์น˜
  • NodeBird์—์„œ config, models, passport ๋ชจ๋‘ ๋ณต์‚ฌํ•ด์„œ nodebird-api์— ๋ถ™์—ฌ๋„ฃ๊ธฐ
  • routes ํด๋”์—์„œ๋Š” auth.js์™€ middlewares ์žฌ์‚ฌ์šฉ
  • .env ํŒŒ์ผ ๋ณต์‚ฌ
  • views ํด๋”๋ฅผ ๋งŒ๋“ค๊ณ  error.html ํŒŒ์ผ ์ƒ์„ฑ

๐Ÿ”ปnodebird-api/view/error.html


<h1>{{message}}</h1>
<h2>{{error.status}}</h2>
<pre>{{error.stack}}</pre>

๐Ÿ”ปnodebird-api/package.json

{
  "name": "nodebird-api",
  "version": "0.0.1",
  "description": "NodeBird API ์„œ๋ฒ„",
  "main": "app.js",
  "scripts": {
    "start": "nodemon app",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "seokahi",
  "license": "ISC",
  "dependencies": {
    "bcrypt": "^5.0.0",
    "cookie-parser": "^1.4.5",
    "dotenv": "^8.2.0",
    "express": "^4.17.1",
    "express-session": "^1.17.1",
    "morgan": "^1.10.0",
    "mysql2": "^2.1.0",
    "nunjucks": "^3.2.1",
    "passport": "^0.4.1",
    "passport-kakao": "1.0.0",
    "passport-local": "^1.0.0",
    "sequelize": "^5.21.7",
    "uuid": "^7.0.3"
  },
  "devDependencies": {
    "nodemon": "^2.0.3"
  }
}

npm i bcrypt cookie-parser dotenv express express-session mysql2 nunjucks passport passport-local
npm i sequelize uuid
npm i -D nodemon

2. app.js ์ƒ์„ฑํ•˜๊ธฐ

์†Œ์Šค์ฐธ๊ณ 
๐Ÿ‘ https://github.com/zerocho/nodejs-book
8002๋ฒˆ ํฌํŠธ ์‚ฌ์šฉ

  • 8001๋ฒˆ์„ ์‚ฌ์šฉํ•˜๋Š” NodeBird ์„œ๋น„์Šค์™€ 8003์„ ์‚ฌ์šฉํ•  nodecat ์„œ๋น„์Šค์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Œ
  • ์ฝ˜์†”์„ ์—ฌ๋Ÿฌ ๊ฐœ ์‹คํ–‰ํ•ด ๊ฐ๊ฐ์˜ ์„œ๋น„์Šค๋ฅผ ๋Œ๋ฆฌ๋ฉด ๋จ
    nodebird-api/views/login.html ํ™”๋ฉด ์ƒ์„ฑ
  • NodeBird ์„œ๋น„์Šค์˜ ๊ณ„์ •์œผ๋กœ ๋กœ๊ทธ์ธํ•˜๋ฉด ๋จ(์นด์นด์˜คํ†ก ๋กœ๊ทธ์ธ์€ ์•ˆ ๋จ)![]

3. ๋„๋ฉ”์ธ ๋ชจ๋ธ ์ƒ์„ฑํ•˜๊ธฐ

models/domain.js ์ž‘์„ฑ

  • API๋ฅผ ์‚ฌ์šฉํ•  ๋„๋ฉ”์ธ(๋˜๋Š” ํ˜ธ์ŠคํŠธ)์„ ์ €์žฅํ•˜๋Š” ๋ชจ๋ธ
  • ENUM type์œผ๋กœ free๋‚˜ premium๋งŒ ์“ธ ์ˆ˜ ์žˆ๊ฒŒ ์ œํ•œ
  • clientSecret์€ uuid ํƒ€์ž…์œผ๋กœ

๐Ÿ”ปnodebird-api/models/domain.js

const Sequelize = require('sequelize');

module.exports = class Domain extends Sequelize.Model {
  static init(sequelize) {
    return super.init({
      host: {
        type: Sequelize.STRING(80),
        allowNull: false,
      },
      type: {
        type: Sequelize.ENUM('free', 'premium'),
        //ENUM์€ ๋ฌธ์ž์—ด์ด์ง€๋งŒ free,premium ๋‘˜ ์ค‘ ํ•˜๋‚˜ ์‚ฌ์šฉ
        allowNull: false,
      },
      clientSecret: {
        type: Sequelize.STRING(36),
        allowNull: false,
        //ํ‚ค ๋ฐœ๊ธ‰
      },
    }, {
      sequelize,
      timestamps: true,
      paranoid: true,
      modelName: 'Domain',
      tableName: 'domains',
    });
  }

  static associate(db) {
    db.Domain.belongsTo(db.User);
  }
};

4. ๋„๋ฉ”์ธ ๋“ฑ๋ก ๋ผ์šฐํ„ฐ

routes/index์—์„œ ๋„๋ฉ”์ธ ๋“ฑ๋ก ๋ผ์šฐํ„ฐ ์ƒ์„ฑ

  • uuid ํŒจํ‚ค์ง€๋กœ ์‚ฌ์šฉ์ž๊ฐ€ ๋“ฑ๋กํ•œ ๋„๋ฉ”์ธ์— ๊ณ ์œ ํ•œ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ถ€์—ฌ
  • uuid๋Š” ์ถฉ๋Œ(๊ณ ์œ ํ•˜์ง€ ์•Š์€ ์ƒํ™ฉ) ์œ„ํ—˜์ด ์žˆ์ง€๋งŒ ๋งค์šฐ ํฌ๋ฐ•
  • ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜๋Š” ์š”์ฒญ๋งŒ API ์‘๋‹ต

๐Ÿ”ปnodebird-api/routes/index.js

const express = require('express');
const { v4: uuidv4 } = require('uuid');
const { User, Domain } = require('../models');
const { isLoggedIn } = require('./middlewares');

const router = express.Router();

router.get('/', async (req, res, next) => {
  try {
    const user = await User.findOne({
      where: { id: req.user && req.user.id || null },
      include: { model: Domain },
    });
    res.render('login', {
      user,
      domains: user && user.Domains,
    });
  } catch (err) {
    console.error(err);
    next(err);
  }
});

router.post('/domain', isLoggedIn, async (req, res, next) => {
  try {
    await Domain.create({
      UserId: req.user.id,
      host: req.body.host,
      type: req.body.type,
      clientSecret: uuidv4(),
    });
    res.redirect('/');
  } catch (err) {
    console.error(err);
    next(err);
  }
});

module.exports = router;

5. ๋„๋ฉ”์ธ ๋“ฑ๋กํ•˜๊ณ  ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ฐœ๊ธ‰๋ฐ›๊ธฐ

๋ผ์šฐํ„ฐ ์ž‘์„ฑ ํ›„ localhost:8002 ์ ‘์†

  • ๋„๋ฉ”์ธ์ด ๋‹ค๋ฅธ ํ”„๋ŸฐํŠธ์—”๋“œ์—์„œ ์š”์ฒญ์„ ๋ณด๋‚ด๋ฉด CORS ์—๋Ÿฌ(10.7) ๋ฐœ์ƒ
  • ๋กœ๊ทธ์ธ ํ›„ localhost:8003(nodecat ์„œ๋ฒ„) ๋“ฑ๋ก

JWT ํ† ํฐ์œผ๋กœ ์ธ์ฆํ•˜๊ธฐ

1. ์ธ์ฆ์„ ์œ„ํ•œ JWT

NodeBird๊ฐ€ ์•„๋‹Œ ๋‹ค๋ฅธ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ๊ฐ€๊ฒŒ ํ•˜๋ ค๋ฉด ์ธ์ฆ ๊ณผ์ •์ด ํ•„์š”ํ•จ
JWT(JSON Web Token)์„ ์‚ฌ์šฉํ•จ

  • ํ—ค๋”.ํŽ˜์ด๋กœ๋“œ.์‹œ๊ทธ๋‹ˆ์ฒ˜๋กœ ๊ตฌ์„ฑ๋จ
  • ํ—ค๋”: ํ† ํฐ ์ข…๋ฅ˜์™€ ํ•ด์‹œ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์ •๋ณด๊ฐ€ ๋“ค์–ด์žˆ์Œ
  • ํŽ˜์ด๋กœ๋“œ: ํ† ํฐ์˜ ๋‚ด์šฉ๋ฌผ์ด ์ธ์ฝ”๋”ฉ๋œ ๋ถ€๋ถ„
  • ์‹œ๊ทธ๋‹ˆ์ฒ˜: ์ผ๋ จ์˜ ๋ฌธ์ž์—ด๋กœ, ์‹œ๊ทธ๋‹ˆ์ฒ˜๋ฅผ ํ†ตํ•ด ํ† ํฐ์ด ๋ณ€์กฐ๋˜์—ˆ๋Š”์ง€ ์—ฌ๋ถ€ ํ™•์ธ
  • ์‹œ๊ทธ๋‹ˆ์ฒ˜๋Š” JWT ๋น„๋ฐ€ํ‚ค๋กœ ๋งŒ๋“ค์–ด์ง€๊ณ , ๋น„๋ฐ€ํ‚ค๊ฐ€ ๋…ธ์ถœ๋˜๋ฉด ํ† ํฐ ์œ„์กฐ ๊ฐ€๋Šฅ

2. JWT ์‚ฌ์šฉ ์‹œ ์ฃผ์˜์ 

JWT์— ๋ฏผ๊ฐํ•œ ๋‚ด์šฉ์„ ๋„ฃ์œผ๋ฉด ์•ˆ ๋จ

  • ํŽ˜์ด๋กœ๋“œ ๋‚ด์šฉ ๋ณผ ์ˆ˜ ์žˆ์Œ
  • ๊ทธ๋Ÿผ์—๋„ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ ๋Š” ํ† ํฐ ๋ณ€์กฐ๊ฐ€ ๋ถˆ๊ฐ€๋Šฅํ•˜๊ณ , ๋‚ด์šฉ๋ฌผ์ด ๋“ค์–ด์žˆ๊ธฐ ๋•Œ๋ฌธ
  • ๋‚ด์šฉ๋ฌผ์ด ๋“ค์–ด์žˆ์œผ๋ฏ€๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์กฐํšŒ๋ฅผ ํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Œ(๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์กฐํšŒ๋Š” ๋น„์šฉ์ด ํฐ ์ž‘์—…)
  • ๋…ธ์ถœ๋˜์–ด๋„ ์ข‹์€ ์ •๋ณด๋งŒ ๋„ฃ์–ด์•ผ ํ•จ
  • ์šฉ๋Ÿ‰์ด ์ปค์„œ ์š”์ฒญ ์‹œ ๋ฐ์ดํ„ฐ ์–‘์ด ์ฆ๊ฐ€ํ•œ๋‹ค๋Š” ๋‹จ์ ์ด ์žˆ์Œ

3. ๋…ธ๋“œ์—์„œ JWT ์‚ฌ์šฉํ•˜๊ธฐ

JWT ๋ชจ๋“ˆ ์„ค์น˜

  • npm i jsonwebtoken
  • JWT ๋น„๋ฐ€ํ‚ค .env์— ์ €์žฅ
  • JWT ํ† ํฐ์„ ๊ฒ€์‚ฌํ•˜๋Š” verifyToken ๋ฏธ๋“ค์›จ์–ด ์ž‘์„ฑ
  • jwt.verify ๋ฉ”์„œ๋“œ๋กœ ๊ฒ€์‚ฌ ๊ฐ€๋Šฅ(๋‘ ๋ฒˆ์งธ ์ธ์ˆ˜๊ฐ€ JWT ๋น„๋ฐ€ํ‚ค)
  • JWT ํ† ํฐ์€ req.headers.authorization์— ๋“ค์–ด ์žˆ์Œ
  • ๋งŒ๋ฃŒ๋œ JWT ํ† ํฐ์ธ ๊ฒฝ์šฐ 419 ์—๋Ÿฌ ๋ฐœ์ƒ
  • ์œ ํšจํ•˜์ง€ ์•Š์€ ํ† ํฐ์ธ ๊ฒฝ์šฐ 401์—๋Ÿฌ ๋ฐœ์ƒ
  • req.decoded์— ํŽ˜์ด๋กœ๋“œ๋ฅผ ๋„ฃ์–ด ๋‹ค์Œ ๋ฏธ๋“ค์›จ์–ด์—์„œ ์“ธ ์ˆ˜ ์žˆ๊ฒŒ ํ•จ

๐Ÿ”ปnodebird-api/.env

COOKIE_SECRET=nodebirdsecret
KAKAO_ID=5d4daf57becfd72fd9c919882552c4a6
JWT_SECRET=jwtSecret

nodebird-api/routes/middlewares.js

const jwt = require('jsonwebtoken');

exports.isLoggedIn = (req, res, next) => {
  if (req.isAuthenticated()) {
    next();
  } else {
    res.status(403).send('๋กœ๊ทธ์ธ ํ•„์š”');
  }
};

exports.isNotLoggedIn = (req, res, next) => {
  if (!req.isAuthenticated()) {
    next();
  } else {
    res.redirect('/');
  }
};

exports.verifyToken = (req, res, next) => {
  try {
    req.decoded = jwt.verify(req.headers.authorization, process.env.JWT_SECRET);
    return next();
  } catch (error) {
    if (error.name === 'TokenExpiredError') { // ์œ ํšจ๊ธฐ๊ฐ„ ์ดˆ๊ณผ
      return res.status(419).json({
        code: 419,
        message: 'ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค',
      });
    }
    return res.status(401).json({
      code: 401,
      message: '์œ ํšจํ•˜์ง€ ์•Š์€ ํ† ํฐ์ž…๋‹ˆ๋‹ค',
    });
  }
};

4. JWT ํ† ํฐ ๋ฐœ๊ธ‰ ๋ผ์šฐํ„ฐ ๋งŒ๋“ค๊ธฐ

routes/v1.js ์ž‘์„ฑ

  • ๋ฒ„์ „ 1์ด๋ผ๋Š” ๋œป์˜ v1.js
  • ํ•œ ๋ฒˆ ๋ฒ„์ „์ด ์ •ํ•ด์ง„ ํ›„์—๋Š” ๋ผ์šฐํ„ฐ๋ฅผ ํ•จ๋ถ€๋กœ ์ˆ˜์ •ํ•˜๋ฉด ์•ˆ ๋จ
  • ๋‹ค๋ฅธ ์‚ฌ๋žŒ์ด ๊ธฐ์กด API๋ฅผ ์“ฐ๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ(๊ทธ ์‚ฌ๋žŒ์—๊ฒŒ ์˜ํ–ฅ์ด ๊ฐ)
  • ์ˆ˜์ • ์‚ฌํ•ญ์ด ์ƒ๊ธฐ๋ฉด ๋ฒ„์ „์„ ์˜ฌ๋ ค์•ผ ํ•จ
  • POST /token์—์„œ JWT ํ† ํฐ ๋ฐœ๊ธ‰
  • ๋จผ์ € ๋„๋ฉ”์ธ ๊ฒ€์‚ฌ ํ›„ ๋“ฑ๋ก๋œ ๋„๋ฉ”์ธ์ด๋ฉด jwt.sign ๋ฉ”์„œ๋“œ๋กœ JWT ํ† ํฐ ๋ฐœ๊ธ‰
  • ์ฒซ ๋ฒˆ์งธ ์ธ์ˆ˜๋กœ ํŽ˜์ด๋กœ๋“œ๋ฅผ ๋„ฃ๊ณ , ๋‘ ๋ฒˆ์งธ ์ธ์ˆ˜๋Š” JWT ๋น„๋ฐ€ํ‚ค, ์„ธ ๋ฒˆ์งธ ์ธ์ˆ˜๋กœ ํ† * * ํฐ ์˜ต์…˜(expiresIn์€ ๋งŒ๋ฃŒ ์‹œ๊ฐ„, issuer์€ ๋ฐœ๊ธ‰์ž)
  • expiresIn์€ 1m(1๋ถ„), 60 * 1000๊ฐ™์€ ๋ฐ€๋ฆฌ์ดˆ ๋‹จ์œ„๋„ ๊ฐ€๋Šฅ
  • GET /test ๋ผ์šฐํ„ฐ์—์„œ ํ† ํฐ ์ธ์ฆ ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ
const token=jwt.sign({
id: domain.user.id,
nick: domain.user.nick,
}, process.env.JMT_SECRET, {
	expiresIn: '1m',
    issuer: 'nodebird'.
});

5. app.js์— ๋ผ์šฐํ„ฐ ์—ฐ๊ฒฐ

app.js์— v1 ๋ผ์šฐํ„ฐ ์—ฐ๊ฒฐ
v1.js > ๋„๋ฉ”์ธ ๊ฒ€์‚ฌ -> ํ† ํฐ ๋ฐœ๊ธ‰ -> ๋ฐœ๊ธ‰ ๋ฐ›์€ ํ† ํฐ์œผ๋กœ API ๋ฐ์ดํ„ฐ ์š”์ฒญ
๐Ÿ”ปnode-bird-api/app.js

...
const dotenv = require('dotenv');

dotenv.config();
const v1 = require('./routes/v1');
const authRouter = require('./routes/auth');
...
app.use('/v1', v1);
app.use('/auth', authRouter);
app.use('/', indexRouter);

6. JWT ํ† ํฐ์œผ๋กœ ๋กœ๊ทธ์ธํ•˜๊ธฐ

์„ธ์…˜ ์ฟ ํ‚ค ๋ฐœ๊ธ‰ ๋Œ€์‹  JWT ํ† ํฐ์„ ์ฟ ํ‚ค๋กœ ๋ฐœ๊ธ‰ํ•˜๋ฉด ๋จ

  • Authenticate ๋ฉ”์„œ๋“œ์˜ ๋‘ ๋ฒˆ์งธ ์ธ์ˆ˜๋กœ ์˜ต์…˜์„ ์ฃผ๋ฉด ์„ธ์…˜ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ
...
router.post('/login',isNotLoggedIn, (reqq,res,next)=> {
	passport.authenticate('local',{session:false}, (authError, user,info)=> {
    if (authError) {
...

ํด๋ผ์ด์–ธํŠธ์—์„œ JWT๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด

  • process.env.JWT_SECRET์€ ํด๋ผ์ด์–ธํŠธ์—์„œ ๋…ธ์ถœ๋˜๋ฉด ์•ˆ ๋จ
  • RSA๊ฐ™์€ ์–‘๋ฐฉํ–ฅ ๋น„๋Œ€์นญ ์•”ํ˜ธํ™” ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์‚ฌ์šฉํ•ด์•ผ ํ•จ
  • JWT๋Š” PEM ํ‚ค๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์–‘๋ฐฉํ–ฅ ์•”ํ˜ธํ™”๋ฅผ ํ•˜๋Š” ๊ฒƒ์„ ์ง€์›ํ•จ

ํ˜ธ์ถœ ์„œ๋ฒ„ ๋งŒ๋“ค๊ธฐ

1. API ํ˜ธ์ถœ์šฉ ์„œ๋ฒ„ ๋งŒ๋“ค๊ธฐ

nodecat ํด๋” ๋งŒ๋“ค๊ณ  package.json ํŒŒ์ผ์„ ๋งŒ๋“ฆ

๐Ÿ”ปnodecat.package.json

{
  "name": "nodecat",
  "version": "0.0.1",
  "description": "๋…ธ๋“œ๋ฒ„๋“œ 2์ฐจ ์„œ๋น„์Šค",
  "main": "app.js",
  "scripts": {
    "start": "nodemon app"
  },
  "author": "seokahi",
  "license": "ISC",
  "dependencies": {
    "axios": "^0.21.1",
    "cookie-parser": "^1.4.5",
    "dotenv": "^8.2.0",
    "express": "^4.17.1",
    "express-session": "^1.17.1",
    "morgan": "^1.10.0",
    "nunjucks": "^3.2.1"
  },
  "devDependencies": {
    "nodemon": "^2.0.3"
  }
}

npm i axios cookie-parser dotenv express express-session morgan nunjucks
npm i -D nodemon

2. ๊ฐ„๋‹จํ•œ ํด๋” ๊ตฌ์กฐ ๊ฐ–์ถ”๊ธฐ

app.js ํŒŒ์ผ ์ƒ์„ฑ

  • ์†Œ์Šค ์ฝ”๋“œ๋Š” https:.//github.com/zerocho/nodejs-book/ch10/10.4/nodecat
  • views/error.html๋Š” nodebird-api๋กœ๋ถ€ํ„ฐ ๋ณต์‚ฌ
  • ์•„๊นŒ ๋ฐœ๊ธ‰๋ฐ›์€ ๋น„๋ฐ€ํ‚ค๋ฅผ .env์— ์ž…๋ ฅ

๐Ÿ”ปnodecat/.env


COOKIE_SECRET=nodecat
CLIENT_SECRET=7d67444e-fd01-4f9b-8680-f72464d02a57
//API ์„œ๋ฒ„์—์„œ ๋ฐœ๊ธ‰ ๋ฐ›์€ ํ‚ค

3. ํ† ํฐ ํ…Œ์ŠคํŠธ์šฉ ๋ผ์šฐํ„ฐ ๋งŒ๋“ค๊ธฐ

routes/index.js ์ƒ์„ฑ

  • GET /test์— ์ ‘๊ทผ ์‹œ ์„ธ์…˜ ๊ฒ€์‚ฌ
  • ์„ธ์…˜์— ํ† ํฐ์ด ์ €์žฅ๋˜์–ด ์žˆ์ง€ ์•Š์œผ๋ฉด POST http://localhost:8002/v1/token ๋ผ์šฐํ„ฐ๋กœ๋ถ€ํ„ฐ ํ† ํฐ ๋ฐœ๊ธ‰
  • ์ด ๋•Œ HTTP ์š”์ฒญ ๋ณธ๋ฌธ์— ํด๋ผ์ด์–ธํŠธ ๋น„๋ฐ€ํ‚ค ๋™๋ด‰
    ๋ฐœ๊ธ‰์— ์„ฑ๊ณตํ–ˆ๋‹ค๋ฉด ๋ฐœ๊ธ‰๋ฐ›์€ ํ† ํฐ์œผ๋กœ ๋‹ค์‹œ GET https://localhost:8002/v1/test ๋ผ์šฐํ„ฐ ์ ‘๊ทผํ•ด์„œ ํ† ํฐ ํ…Œ์ŠคํŠธ
    nodecat: node-bird api๋กœ ๋ถ€ํ„ฐ ๊ฐ€์ ธ์˜ค๋Š” ์™ธ๋ถ€ ์„œ๋น„์Šค
    ๐Ÿ”ปnodecat/routes/index.js
const express = require('express');
const axios = require('axios');

const router = express.Router();

router.get('/test', async (req, res, next) => { // ํ† ํฐ ํ…Œ์ŠคํŠธ ๋ผ์šฐํ„ฐ
  try {
    if (!req.session.jwt) { // ์„ธ์…˜์— ํ† ํฐ์ด ์—†์œผ๋ฉด ํ† ํฐ ๋ฐœ๊ธ‰ ์‹œ๋„
      const tokenResult = await axios.post('http://localhost:8002/v1/token', {
        clientSecret: process.env.CLIENT_SECRET,
      });
      if (tokenResult.data && tokenResult.data.code === 200) { // ํ† ํฐ ๋ฐœ๊ธ‰ ์„ฑ๊ณต
        req.session.jwt = tokenResult.data.token; // ์„ธ์…˜์— ํ† ํฐ ์ €์žฅ
      } else { // ํ† ํฐ ๋ฐœ๊ธ‰ ์‹คํŒจ
        return res.json(tokenResult.data); // ๋ฐœ๊ธ‰ ์‹คํŒจ ์‚ฌ์œ  ์‘๋‹ต
      }
    }
    // ๋ฐœ๊ธ‰๋ฐ›์€ ํ† ํฐ ํ…Œ์ŠคํŠธ
    const result = await axios.get('http://localhost:8002/v1/test', {
      headers: { authorization: req.session.jwt },
    });
    return res.json(result.data);
  } catch (error) {
    console.error(error);
    if (error.response.status === 419) { // ํ† ํฐ ๋งŒ๋ฃŒ ์‹œ
      return res.json(error.response.data);
    }
    return next(error);
  }
});

module.exports = router;

4. ์‹ค์ œ ์š”์ฒญ ๋ณด๋‚ด๊ธฐ

npm start๋กœ ์„œ๋ฒ„ ์‹œ์ž‘
http://localhost:4000/test๋กœ ์ ‘์†

  • 1๋ถ„์„ ๊ธฐ๋‹ค๋ฆฐ ํ›„ ๋‹ค์‹œ ์ ‘์†ํ•˜๋ฉด ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜์—ˆ๋‹ค๋Š” ๋ฉ”์‹œ์ง€ ๋œธ

SNS API ์„œ๋ฒ„ ๋งŒ๋“ค๊ธฐ

1. NodeBird ๋ฐ์ดํ„ฐ ์ œ๊ณตํ•˜๊ธฐ

nodebird-api์˜ ๋ผ์šฐํ„ฐ ์ž‘์„ฑ
GET /posts/my, GET /posts/hashtag/:title

๐Ÿ”ปnodebird-api/routes/v1.js

const express = require('express');
const jwt = require('jsonwebtoken');

const { verifyToken } = require('./middlewares');
const { Domain, User, Post, Hashtag } = require('../models');

const router = express.Router();

router.post('/token', async (req, res) => {
  const { clientSecret } = req.body;
  try {
    const domain = await Domain.findOne({
      where: { clientSecret },
      include: {
        model: User,
        attribute: ['nick', 'id'],
      },
    });
    if (!domain) {
      return res.status(401).json({
        code: 401,
        message: '๋“ฑ๋ก๋˜์ง€ ์•Š์€ ๋„๋ฉ”์ธ์ž…๋‹ˆ๋‹ค. ๋จผ์ € ๋„๋ฉ”์ธ์„ ๋“ฑ๋กํ•˜์„ธ์š”',
      });
    }
    const token = jwt.sign({
      id: domain.User.id,
      nick: domain.User.nick,
    }, process.env.JWT_SECRET, {
      expiresIn: '1m', // 1๋ถ„
      issuer: 'nodebird',
    });
    return res.json({
      code: 200,
      message: 'ํ† ํฐ์ด ๋ฐœ๊ธ‰๋˜์—ˆ์Šต๋‹ˆ๋‹ค',
      token,
    });
  } catch (error) {
    console.error(error);
    return res.status(500).json({
      code: 500,
      message: '์„œ๋ฒ„ ์—๋Ÿฌ',
    });
  }
});

router.get('/test', verifyToken, (req, res) => {
  res.json(req.decoded);
});

router.get('/posts/my', verifyToken, (req, res) => {
  Post.findAll({ where: { userId: req.decoded.id } })
    .then((posts) => {
      console.log(posts);
      res.json({
        code: 200,
        payload: posts,
      });
    })
    .catch((error) => {
      console.error(error);
      return res.status(500).json({
        code: 500,
        message: '์„œ๋ฒ„ ์—๋Ÿฌ',
      });
    });
});

router.get('/posts/hashtag/:title', verifyToken, async (req, res) => {
  try {
    const hashtag = await Hashtag.findOne({ where: { title: req.params.title } });
    if (!hashtag) {
      return res.status(404).json({
        code: 404,
        message: '๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค',
      });
    }
    const posts = await hashtag.getPosts();
    return res.json({
      code: 200,
      payload: posts,
    });
  } catch (error) {
    console.error(error);
    return res.status(500).json({
      code: 500,
      message: '์„œ๋ฒ„ ์—๋Ÿฌ',
    });
  }
});

module.exports = router;

2. NodeBird ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ

nodecat์˜ ๋ผ์šฐํ„ฐ ์ž‘์„ฑ

  • ํ† ํฐ์„ ๋ฐœ๊ธ‰๋ฐ›๊ณ  ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ๋ถ€๋ถ„์„ request ํ•จ์ˆ˜๋กœ ๋งŒ๋“ค์–ด ๋‘ 
  • ์š”์ฒญ์€ axios๋กœ ๋ณด๋‚ด๊ณ  ์„ธ์…˜ ํ† ํฐ ๊ฒ€์‚ฌ, ์žฌ๋ฐœ๊ธ‰๊นŒ์ง€ ๊ฐ™์ด ์ˆ˜ํ–‰

๐Ÿ”ปnodecat/routes/index.js

const express = require('express');
const axios = require('axios');

const router = express.Router();
const URL = 'http://localhost:8002/v1';

axios.defaults.headers.origin = 'http://localhost:4000'; // origin ํ—ค๋” ์ถ”๊ฐ€
const request = async (req, api) => {
  try {
    if (!req.session.jwt) { // ์„ธ์…˜์— ํ† ํฐ์ด ์—†์œผ๋ฉด
      const tokenResult = await axios.post(`${URL}/token`, {
        clientSecret: process.env.CLIENT_SECRET,
      });
      req.session.jwt = tokenResult.data.token; // ์„ธ์…˜์— ํ† ํฐ ์ €์žฅ
    }
    return await axios.get(`${URL}${api}`, {
      headers: { authorization: req.session.jwt },
    }); // API ์š”์ฒญ
  } catch (error) {
    if (error.response.status === 419) { // ํ† ํฐ ๋งŒ๋ฃŒ์‹œ ํ† ํฐ ์žฌ๋ฐœ๊ธ‰ ๋ฐ›๊ธฐ
      delete req.session.jwt;
      return request(req, api);
    } // 419 ์™ธ์˜ ๋‹ค๋ฅธ ์—๋Ÿฌ๋ฉด
    return error.response;
  }
};

router.get('/mypost', async (req, res, next) => {
  try {
    const result = await request(req, '/posts/my');
    res.json(result.data);
  } catch (error) {
    console.error(error);
    next(error);
  }
});

router.get('/search/:hashtag', async (req, res, next) => {
  try {
    const result = await request(
      req, `/posts/hashtag/${encodeURIComponent(req.params.hashtag)}`,
    );
    res.json(result.data);
  } catch (error) {
    if (error.code) {
      console.error(error);
      next(error);
    }
  }
});

module.exports = router;

3. ์‹ค์ œ ์š”์ฒญ ๋ณด๋‚ด๊ธฐ

localhost:4000/mypost์— ์ ‘์†ํ•˜๋ฉด ๊ฒŒ์‹œ๊ธ€ ๋ฐ›์•„์˜ด(NodeBird ์„œ๋น„์Šค์— ๊ฒŒ์‹œ๊ธ€์ด ์žˆ์–ด์•ผ ํ•จ)

localhost:4000/search/๋…ธ๋“œ ๋ผ์šฐํ„ฐ์— ์ ‘์†ํ•˜๋ฉด ๋…ธ๋“œ ํ•ด์‹œํƒœ๊ทธ ๊ฒ€์ƒ‰

์‚ฌ์šฉ๋Ÿ‰ ์ œํ•œ ๊ตฌํ˜„ํ•˜๊ธฐ

1. ์‚ฌ์šฉ๋Ÿ‰ ์ œํ•œ ๊ตฌํ˜„ํ•˜๊ธฐ

DOS ๊ณต๊ฒฉ ๋“ฑ์„ ๋Œ€๋น„ํ•ด์•ผ ํ•จ

  • ์ผ์ • ์‹œ๊ฐ„๋™์•ˆ ํšŸ์ˆ˜ ์ œํ•œ์„ ๋‘์–ด ๋ฌด์ฐจ๋ณ„์ ์ธ ์š”์ฒญ์„ ๋ง‰์„ ํ•„์š”๊ฐ€ ์žˆ์Œ
  • npm i express-rate-limit
  • apiLimiter ๋ฏธ๋“ค์›จ์–ด ์ถ”๊ฐ€
    • windowMS(๊ธฐ์ค€ ์‹œ๊ฐ„), max(ํ—ˆ์šฉ ํšŸ์ˆ˜), delayMS(ํ˜ธ์ถœ ๊ฐ„๊ฒฉ), handler(์ œํ•œ ์ดˆ๊ณผ ์‹œ ์ฝœ๋ฐฑ ํ•จ์ˆ˜)
  • deprecated ๋ฏธ๋“ค์›จ์–ด๋Š” ์‚ฌ์šฉํ•˜๋ฉด ์•ˆ ๋˜๋Š” ๋ผ์šฐํ„ฐ์— ๋ถ™์—ฌ์„œ ์‚ฌ์šฉ ์‹œ ๊ฒฝ๊ณ 

๐Ÿ”ปnodebird-api/routes.middlewares.js

const jwt = require('jsonwebtoken');
const RateLimit = require('express-rate-limit');

exports.isLoggedIn = (req, res, next) => {
  if (req.isAuthenticated()) {
    next();
  } else {
    res.status(403).send('๋กœ๊ทธ์ธ ํ•„์š”');
  }
};

exports.isNotLoggedIn = (req, res, next) => {
  if (!req.isAuthenticated()) {
    next();
  } else {
    res.redirect('/');
  }
};

exports.verifyToken = (req, res, next) => {
  try {
    req.decoded = jwt.verify(req.headers.authorization, process.env.JWT_SECRET);
    return next();
  } catch (error) {
    if (error.name === 'TokenExpiredError') { // ์œ ํšจ๊ธฐ๊ฐ„ ์ดˆ๊ณผ
      return res.status(419).json({
        code: 419,
        message: 'ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค',
      });
    }
    return res.status(401).json({
      code: 401,
      message: '์œ ํšจํ•˜์ง€ ์•Š์€ ํ† ํฐ์ž…๋‹ˆ๋‹ค',
    });
  }
};

exports.apiLimiter = new RateLimit({
  windowMs: 60 * 1000, // 1๋ถ„
  max: 10,
  delayMs: 0,
  handler(req, res) {
    res.status(this.statusCode).json({
      code: this.statusCode, // ๊ธฐ๋ณธ๊ฐ’ 429
      message: '1๋ถ„์— ํ•œ ๋ฒˆ๋งŒ ์š”์ฒญํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.',
    });
  },
});

exports.deprecated = (req, res) => {
  res.status(410).json({
    code: 410,
    message: '์ƒˆ๋กœ์šด ๋ฒ„์ „์ด ๋‚˜์™”์Šต๋‹ˆ๋‹ค. ์ƒˆ๋กœ์šด ๋ฒ„์ „์„ ์‚ฌ์šฉํ•˜์„ธ์š”.',
  });
};

2. ์‘๋‹ต ์ฝ”๋“œ ์ •๋ฆฌ

์‘๋‹ต ์ฝ”๋“œ๋ฅผ ์ •๋ฆฌํ•ด์„œ ์–ด๋–ค ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋Š”์ง€ ์•Œ๋ ค์ฃผ๊ธฐ

  • ์ผ๊ด€์„ฑ์ด ์žˆ์œผ๋ฉด ๋จ

3. ์ƒˆ ๋ผ์šฐํ„ฐ ๋ฒ„์ „ ๋‚ด๋†“๊ธฐ

์‚ฌ์šฉ๋Ÿ‰ ์ œํ•œ ๊ธฐ๋Šฅ์ด ์ถ”๊ฐ€๋˜์–ด ๊ธฐ์กด API์™€ ํ˜ธํ™˜๋˜์ง€ ์•Š์Œ

  • ์ด๋Ÿฐ ๊ฒฝ์šฐ ์ƒˆ๋กœ์šด ๋ฒ„์ „์˜ ๋ผ์šฐํ„ฐ๋ฅผ ๋‚ด๋†“์œผ๋ฉด ๋จ
  • v2 ๋ผ์šฐํ„ฐ ์ž‘์„ฑ(apiLimiter ์ถ”๊ฐ€๋จ)
  • v1 ๋ผ์šฐํ„ฐ๋Š” deprecated ์ฒ˜๋ฆฌ(router.use๋กœ ํ•œ ๋ฒˆ์— ๋ชจ๋“  ๋ผ์šฐํ„ฐ์— ์ ์šฉ)

๐Ÿ”ปnodebird-api/routes.v1.js

const express = require('express');
const jwt = require('jsonwebtoken');

const { verifyToken, deprecated } = require('./middlewares');
const { Domain, User, Post, Hashtag } = require('../models');

const router = express.Router();

router.use(deprecated);

router.post('/token', async (req, res) => {
...

๐Ÿ”ปnodebird-api/app.js

const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const passport = require('passport');
const morgan = require('morgan');
const session = require('express-session');
const nunjucks = require('nunjucks');
const dotenv = require('dotenv');

dotenv.config();
const v1 = require('./routes/v1');
const v2 = require('./routes/v2');
const authRouter = require('./routes/auth');
const indexRouter = require('./routes');
const { sequelize } = require('./models');
const passportConfig = require('./passport');

const app = express();
passportConfig();
app.set('port', process.env.PORT || 8002);
app.set('view engine', 'html');
nunjucks.configure('views', {
  express: app,
  watch: true,
});
sequelize.sync({ force: false })
  .then(() => {
    console.log('๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์„ฑ๊ณต');
  })
  .catch((err) => {
    console.error(err);
  });

app.use(morgan('dev'));
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser(process.env.COOKIE_SECRET));
app.use(session({
  resave: false,
  saveUninitialized: false,
  secret: process.env.COOKIE_SECRET,
  cookie: {
    httpOnly: true,
    secure: false,
  },
}));
app.use(passport.initialize());
app.use(passport.session());

app.use('/v1', v1);
app.use('/v2', v2);
app.use('/auth', authRouter);
app.use('/', indexRouter);

...

4. ์ƒˆ ๋ผ์šฐํ„ฐ ์‹คํ–‰ํ•ด๋ณด๊ธฐ

nodecat์˜ ๋ฒ„์ „ v2๋กœ ๋ฐ”๊พธ๊ธฐ
๐Ÿ”ปnodecat/routes/index.js

const express = require('express');
const axios = require('axios');

const router = express.Router();
const URL = 'http://localhost:8002/v2';

...

v1 API๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ์‚ฌ์šฉ๋Ÿ‰์„ ์ดˆ๊ณผํ•˜๋ฉด ์—๋Ÿฌ ๋ฐœ์ƒ

CORS ์ดํ•ดํ•˜๊ธฐ

1. nodecat ํ”„๋ŸฐํŠธ ์ž‘์„ฑํ•˜๊ธฐ

ํ”„๋ŸฐํŠธ์—์„œ ์„œ๋ฒ„์˜ API๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ?
routes/index.js์™€ views/main.html ์ž‘์„ฑ

๐Ÿ”ปnodecat/routes/index.js

...
router.get('/', (req, res) => {
  res.render('main', { key: process.env.CLIENT_SECRET });
});

module.exports = router;

2. ํ”„๋ŸฐํŠธ์—์„œ ์š”์ฒญ ๋ณด๋‚ด๊ธฐ

localhost:4000์— ์ ‘์†ํ•˜๋ฉด ์—๋Ÿฌ ๋ฐœ์ƒ

์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ํ”„๋ŸฐํŠธ(localhost:4000), ์š”์ฒญ์„ ๋ฐ›๋Š” ์„œ๋ฒ„(localhost:8002)๊ฐ€ ๋‹ค๋ฅด๋ฉด ์—๋Ÿฌ ๋ฐœ์ƒ(์„œ๋ฒ„์—์„œ ์„œ๋ฒ„๋กœ ์š”์ฒญ์„ ๋ณด๋‚ผ๋•Œ๋Š” ๋ฐœ์ƒํ•˜์ง€ ์•Š์Œ)

  • CORS: Cross-Origin Resource Sharing ๋ฌธ์ œ
  • POST ๋Œ€์‹  OPTIONS ์š”์ฒญ์„ ๋จผ์ € ๋ณด๋‚ด ์„œ๋ฒ„๊ฐ€ ๋„๋ฉ”์ธ์„ ํ—ˆ์šฉํ•˜๋Š”์ง€ ๋ฏธ๋ฆฌ ์ฒดํฌ
    CORS๋Š” ๋ธŒ๋ผ์šฐ์ €์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์—๋Ÿฌ but ํ•ด๊ฒฐ์€ ์„œ๋ฒ„์—์„œ

3. CORS ๋ฌธ์ œ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

Access-Control-Allow-Origin ์‘๋‹ต ํ—ค๋”๋ฅผ ๋„ฃ์–ด์ฃผ์–ด์•ผ CORS ๋ฌธ์ œ ํ•ด๊ฒฐ ๊ฐ€๋Šฅ

  • res.set ๋ฉ”์„œ๋“œ๋กœ ์ง์ ‘ ๋„ฃ์–ด์ฃผ์–ด๋„ ๋˜์ง€๋งŒ ํŒจํ‚ค์ง€๋ฅผ ์‚ฌ์šฉํ•˜๋Š”๊ฒŒ ํŽธ๋ฆฌ
  • npm i cors
  • v2 ๋ผ์šฐํ„ฐ์— ์ ์šฉ
  • credentials: true๋ฅผ ํ•ด์•ผ ํ”„๋ŸฐํŠธ์™€ ๋ฐฑ์—”๋“œ ๊ฐ„์— ์ฟ ํ‚ค๊ฐ€ ๊ณต์œ ๋จ

๐Ÿ”ปnodebird-api/routes/v2.js

const express = require('express');
const jwt = require('jsonwebtoken');
const cors = require('cors');
const url = require('url');

const { verifyToken, apiLimiter } = require('./middlewares');
const { Domain, User, Post, Hashtag } = require('../models');

const router = express.Router();

router.use(async (req, res, next) => {
  const domain = await Domain.findOne({
    where: { host: url.parse(req.get('origin')).host },
  });
  if (domain) {
    cors({
      origin: req.get('origin'),
      credentials: true,
    })(req, res, next);
  } else {
    next();
  }
});

router.post('/token', apiLimiter, async (req, res) => {
  const { clientSecret } = req.body;
  try {
    const domain = await Domain.findOne({
      where: { clientSecret },
      include: {
        model: User,
        attribute: ['nick', 'id'],
      },
    });
    if (!domain) {
      return res.status(401).json({
        code: 401,
        message: '๋“ฑ๋ก๋˜์ง€ ์•Š์€ ๋„๋ฉ”์ธ์ž…๋‹ˆ๋‹ค. ๋จผ์ € ๋„๋ฉ”์ธ์„ ๋“ฑ๋กํ•˜์„ธ์š”',
      });
    }
    const token = jwt.sign({
      id: domain.User.id,
      nick: domain.User.nick,
    }, process.env.JWT_SECRET, {
      expiresIn: '30m', // 30๋ถ„
      issuer: 'nodebird',
    });
    return res.json({
      code: 200,
      message: 'ํ† ํฐ์ด ๋ฐœ๊ธ‰๋˜์—ˆ์Šต๋‹ˆ๋‹ค',
      token,
    });
  } catch (error) {
    console.error(error);
    return res.status(500).json({
      code: 500,
      message: '์„œ๋ฒ„ ์—๋Ÿฌ',
    });
  }
});

router.get('/test', verifyToken, apiLimiter, (req, res) => {
  res.json(req.decoded);
});

router.get('/posts/my', apiLimiter, verifyToken, (req, res) => {
  Post.findAll({ where: { userId: req.decoded.id } })
    .then((posts) => {
      console.log(posts);
      res.json({
        code: 200,
        payload: posts,
      });
    })
    .catch((error) => {
      console.error(error);
      return res.status(500).json({
        code: 500,
        message: '์„œ๋ฒ„ ์—๋Ÿฌ',
      });
    });
});

router.get('/posts/hashtag/:title', verifyToken, apiLimiter, async (req, res) => {
  try {
    const hashtag = await Hashtag.findOne({ where: { title: req.params.title } });
    if (!hashtag) {
      return res.status(404).json({
        code: 404,
        message: '๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค',
      });
    }
    const posts = await hashtag.getPosts();
    return res.json({
      code: 200,
      payload: posts,
    });
  } catch (error) {
    console.error(error);
    return res.status(500).json({
      code: 500,
      message: '์„œ๋ฒ„ ์—๋Ÿฌ',
    });
  }
});

module.exports = router;

4. CORS ์ ์šฉ ํ™•์ธํ•˜๊ธฐ

http://localhost:4000์— ์ ‘์†ํ•˜๋ฉด ์ •์ƒ์ ์œผ๋กœ ํ† ํฐ์ด ๋ฐœ๊ธ‰๋จ

์‘๋‹ต ํ—ค๋”๋ฅผ ๋ณด๋ฉด Access-Control-Allow-Origin ํ—ค๋”๊ฐ€ ๋“ค์–ด ์žˆ์Œ

  • *์€ ๋ชจ๋“  ๋„๋ฉ”์ธ์„ ํ—ˆ์šฉํ•จ์„ ์˜๋ฏธ

5. ํด๋ผ์ด์–ธํŠธ ๋„๋ฉ”์ธ ๊ฒ€์‚ฌํ•˜๊ธฐ

ํด๋ผ์ด์–ธํŠธ ํ™˜๊ฒฝ์—์„œ๋Š” ๋น„๋ฐ€ํ‚ค๊ฐ€ ๋…ธ์ถœ๋จ

  • ๋„๋ฉ”์ธ๊นŒ์ง€ ๊ฐ™์ด ๊ฒ€์‚ฌํ•ด์•ผ ์š”์ฒญ ์ธ์ฆ ๊ฐ€๋Šฅ
  • ํ˜ธ์ŠคํŠธ์™€ ๋น„๋ฐ€ํ‚ค๊ฐ€ ๋ชจ๋‘ ์ผ์น˜ํ•  ๋•Œ๋งŒ CORS๋ฅผ ํ—ˆ์šฉ
  • ํด๋ผ์ด์–ธํŠธ์˜ ๋„๋ฉ”์ธ(req.get(โ€˜originโ€™))๊ณผ ๋“ฑ๋ก๋œ ํ˜ธ์ŠคํŠธ๊ฐ€ ์ผ์น˜ํ•˜๋Š” ์ง€ ์ฐพ์Œ
  • url.parse().host๋Š” http๊ฐ™์€ ํ”„๋กœํ† ์ฝœ์„ ๋–ผ์–ด๋‚ด๊ธฐ ์œ„ํ•จ
  • cors์˜ ์ธ์ž๋กœ origin์„ ์ฃผ๋ฉด * ๋Œ€์‹  ์ฃผ์–ด์ง„ ๋„๋ฉ”์ธ๋งŒ ํ—ˆ์šฉํ•  ์ˆ˜ ์žˆ์Œ

๐Ÿ”ปnodebird-api/routes/v2.js

const express = require('express');
const jwt = require('jsonwebtoken');
const cors = require('cors');
const url = require('url');

const { verifyToken, apiLimiter } = require('./middlewares');
const { Domain, User, Post, Hashtag } = require('../models');

const router = express.Router();

router.use(async (req, res, next) => {
  const domain = await Domain.findOne({
    where: { host: url.parse(req.get('origin')).host },
  });
  if (domain) {
    cors({
      origin: req.get('origin'),
      credentials: true,
    })(req, res, next);
  } else {
    next();
  }
});

7. ์œ ์šฉํ•œ ๋ฏธ๋“ค์›จ์–ด ํŒจํ„ด ์•Œ์•„๋ณด๊ธฐ

์œ„์˜ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์•„๋ž˜์ฒ˜๋Ÿผ ์ˆ˜์ • ๊ฐ€๋Šฅ

  • ์•„๋ž˜์ฒ˜๋Ÿผ ์“ฐ๋ฉด ๋ฏธ๋“ค์›จ์–ด ์œ„ ์•„๋ž˜๋กœ ์ž„์˜์˜ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Œ
  • ํ™œ์šฉ ๊ฐ€๋Šฅ

8. CORS ์š”์ฒญ ๋ณด๋‚ด๊ธฐ

localhost:4000์— ์ ‘์†

  • ์‘๋‹ต ํ—ค๋”์˜ ๋„๋ฉ”์ธ ํ™•์ธ

9. ํ”„๋ก์‹œ ์„œ๋ฒ„

CORS ๋ฌธ์ œ์— ๋Œ€ํ•œ ๋˜๋‹ค๋ฅธ ํ•ด๊ฒฐ์ฑ…

  • ์„œ๋ฒ„-์„œ๋ฒ„ ๊ฐ„์˜ ์š”์ฒญ/์‘๋‹ต์—๋Š” CORS ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์„ ํ™œ์šฉ
  • ์ง์ ‘ ๊ตฌํ˜„ํ•ด๋„ ๋˜์ง€๋งŒ http-proxy-middleware๊ฐ™์€ ํŒจํ‚ค์ง€๋กœ ์†์‰ฝ๊ฒŒ ์—ฐ๋™ ๊ฐ€๋Šฅ

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

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