์ƒ์„ธ ์ปจํ…์ธ 

๋ณธ๋ฌธ ์ œ๋ชฉ

[๋…ธ๋“œ 1ํŒ€] 9์žฅ. ์ต์Šคํ”„๋ ˆ์Šค๋กœ SNS ์„œ๋น„์Šค ๋งŒ๋“ค๊ธฐ

24-25/Node.js 1

by gooroominuna 2025. 1. 3. 10:00

๋ณธ๋ฌธ

728x90

 

๐Ÿ“Œ ์ด๋ฒˆ ์žฅ์—์„œ๋Š” ๋กœ๊ทธ์ธ, ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ, ๊ฒŒ์‹œ๊ธ€ ์ž‘์„ฑ, ํ•ด์‹œํƒœ๊ทธ ๊ฒ€์ƒ‰, ํŒ”๋กœ์ž‰ ๋“ฑ์˜ ๊ธฐ๋Šฅ์ด ์žˆ๋Š” SNS ์„œ๋น„์Šค๋ฅผ ๋งŒ๋“ ๋‹ค.

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

๋‹ค์Œ๊ณผ ๊ฐ™์ด ์•ฑ๋ช…์ธ 'nodebird'๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ํด๋”๋ฅผ ๋งŒ๋“ค๊ณ  package.json์„ ์ƒ์„ฑํ•œ๋‹ค.
์‹œํ€„๋ผ์ด์ฆˆ๋„ ์„ค์น˜ํ•œ๋‹ค.

$ npm init
$ npm i sequelize mysql2 sequelize-cli
$ npx sequelize init

 
๋‹ค์Œ๊ณผ ๊ฐ™์ด ํด๋”์˜ ๊ตฌ์กฐ๋ฅผ ๊ฐ–์ถ˜๋‹ค.

nodebird
> config
> migrations
> models
> node_modules
> passport
> public
> routes
> seeders
> views
.env
app.js
{ } package-lock.json
{ } package.json

 
๊ทธ๋‹ค์Œ, ํ•„์š”ํ•œ npm ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•œ ํ›„

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

 
๋‹ค์Œ๊ณผ ๊ฐ™์ด 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');

dotenv.config();
const pageRouter = require('./routes/page');

const app = express();
app.set('port', process.env.PORT || 8001); // ์•ฑ์„ 8001๋ฒˆ ํฌํŠธ์— ์—ฐ๊ฒฐ
app.set('view engine', 'html');
nunjucks.configure('views', {
  express: app,
  watch: true,
});

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('/', pageRouter);

app.use((req, res, next) => {
  const error =  new Error(`${req.method} ${req.url} ๋ผ์šฐํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.`);
  error.status = 404; // ๋ผ์šฐํ„ฐ๊ฐ€ ์—†์„ ์‹œ 404 ์—๋Ÿฌ ๋ฐ˜ํ™˜
  next(error);
});

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

app.listen(app.get('port'), () => {
  console.log(app.get('port'), '๋ฒˆ ํฌํŠธ์—์„œ ๋Œ€๊ธฐ์ค‘');
});

 
์•„๋ž˜๋Š” ๊ธฐ๋ณธ์ ์ธ ๋ผ์šฐํ„ฐ์™€ ํ…œํ”Œ๋ฆฟ ์—”์ง„์ด๋‹ค.

const express = require('express');
const { renderProfile, renderJoin, renderMain } = require('../controllers/page');

const router = express.Router();

router.use((req, res, next) => {
  res.locals.user = null;
  res.locals.followerCount = 0;
  res.locals.followingCount = 0;
  res.locals.followingIdList = [];
  next();
});

router.get('/profile', renderProfile);

router.get('/join', renderJoin);

router.get('/', renderMain);

module.exports = router;

 
์œ„ ์ฝ”๋“œ๋ฅผ ์‚ดํŽด๋ณด๋ฉด, router.use๋กœ ๋ผ์šฐํ„ฐ์šฉ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ๋งŒ๋“ค์–ด ํ…œํ”Œ๋ฆฟ ์—”์ง„์—์„œ ์‚ฌ์šฉํ•  user, followingCount, followerCount, followingIdList ๋ณ€์ˆ˜๋ฅผ res.locals๋กœ ์„ค์ •ํ•œ๋‹ค. ์—ฌ๊ธฐ์„œ ์ด์ „๊ณผ๋Š” ๋‹ค๋ฅด๊ฒŒ ๋ผ์šฐํ„ฐ์˜ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ๋‹ค๋ฅธ ๊ณณ์—์„œ ๋ถˆ๋Ÿฌ์˜ค๊ณ  ์žˆ๋‹ค๋Š” ์ ์„ ์ฃผ๋ชฉํ•ด์•ผ ํ•œ๋‹ค.

์ปจํŠธ๋กค๋Ÿฌ

: ์•„๋ž˜ renderProfile, renderJoin, renderMain๊ณผ ๊ฐ™์ด ๋ผ์šฐํ„ฐ ๋งˆ์ง€๋ง‰์— ์œ„์น˜ํ•ด ํด๋ผ์ด์–ธํŠธ์— ์‘๋‹ต์„ ๋ณด๋‚ด๋Š” ๋ฏธ๋“ค์›จ์–ด๋ฅผ ๋งํ•œ๋‹ค.
 

exports.renderProfile = (req, res) => {
  res.render('profile', { title: '๋‚ด ์ •๋ณด - NodeBird' });
};

exports.renderJoin = (req, res) => {
  res.render('join', { title: 'ํšŒ์›๊ฐ€์ž… - NodeBird' });
};

exports.renderMain = (req, res, next) => {
  const twits = [];
  res.render('main', {
    title: 'NodeBird',
    twits,
  });
};

 
์ปจํŠธ๋กค๋Ÿฌ๋Š” ์‹ค๋ฌด์—์„œ ์ฝ”๋“œ๋ฅผ ํŽธ๋ฆฌํ•˜๊ฒŒ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•œ๋‹ค๊ณ  ๋ณด๋ฉด ๋œ๋‹ค.
 
๊ทธ ์™ธ์— ํ•„์š”ํ•œ html, css ์ฝ”๋“œ๋Š” https://github.com/ZeroCho/nodejs-book/tree/master ์—์„œ ์ฐธ์กฐํ•˜๊ธฐ ๋ฐ”๋ž€๋‹ค.
 

9.2. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ธํŒ…ํ•˜๊ธฐ

๐Ÿ“Œ ์‚ฌ์šฉ์ž ํ…Œ์ด๋ธ”, ๊ฒŒ์‹œ๊ธ€ ํ…Œ์ด๋ธ”, ํ•ด์‹œํƒœ๊ทธ ํ…Œ์ด๋ธ”

models
> user.js
> post.js
> hashtag.js

์‚ฌ์šฉ์ž ํ…Œ์ด๋ธ”(models/user.js)

์‚ฌ์šฉ์ž ์ •๋ณด(์ด๋ฉ”์ผ, ๋‹‰๋„ค์ž„, ๋น„๋ฐ€๋ฒˆํ˜ธ)๋ฅผ ์ €์žฅํ•˜๋Š” ํ…Œ์ด๋ธ”์ด๋‹ค. ์ด๋Š” ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ์— ์‚ฌ์šฉ๋œ๋‹ค.

const Sequelize = require('sequelize');

class User extends Sequelize.Model {
  static initiate(sequelize) {
    User.init({
      email: {
        type: Sequelize.STRING(40),
        allowNull: true,
        unique: true,
      },
      nick: {
        type: Sequelize.STRING(15),
        allowNull: false,
      },
      password: {
        type: Sequelize.STRING(100),
        allowNull: true,
      },
      provider: { // SNS ๋กœ๊ทธ์ธ์„ ํ–ˆ์„ ๊ฒฝ์šฐ ์ €์žฅ
        type: Sequelize.ENUM('local', 'kakao'),
        allowNull: false,
        defaultValue: 'local',
      },
      snsId: { // SNS ๋กœ๊ทธ์ธ์„ ํ–ˆ์„ ๊ฒฝ์šฐ ์ €์žฅ
        type: Sequelize.STRING(30),
        allowNull: true,
      },
    }, {
      sequelize, // ํ…Œ์ด๋ธ” ์˜ต์…˜
      timestamps: true,
      underscored: false,
      modelName: 'User',
      tableName: 'users',
      paranoid: true,
      charset: 'utf8',
      collate: 'utf8_general_ci',
    });
  }
  
  static associate(db) {}
};

module.exports = User;

๊ฒŒ์‹œ๊ธ€ ํ…Œ์ด๋ธ” (models/post.js)

๊ฒŒ์‹œ๊ธ€ ๋‚ด์šฉ๊ณผ ์ด๋ฏธ์ง€ ๊ฒฝ๋กœ๋ฅผ ์ €์žฅํ•œ๋‹ค.

const Sequelize = require('sequelize');

class Post extends Sequelize.Model {
  static initiate(sequelize) {
    Post.init({
      content: {
        type: Sequelize.STRING(140),
        allowNull: false,
      },
      img: {
        type: Sequelize.STRING(200),
        allowNull: true,
      },
    }, {
      sequelize,
      timestamps: true,
      underscored: false,
      modelName: 'Post',
      tableName: 'posts',
      paranoid: false,
      charset: 'utf8mb4',
      collate: 'utf8mb4_general_ci',
    });
  }
  
  static associate(db) {
    db.Post.belongsTo(db.User);
    db.Post.belongsToMany(db.Hashtag, {through: 'PostHashtag'});
  }
}

module.exports = Post;

ํ•ด์‹œํƒœ๊ทธ ํ…Œ์ด๋ธ” (models/hashtag.js)

ํƒœ๊ทธ ์ด๋ฆ„์„ ์ €์žฅํ•œ๋‹ค.

const Sequelize = require('sequelize');

class Hashtag extends Sequelize.Model {
  static initiate(sequelize) {
    Hashtag.init({
      title: {
        type: Sequelize.STRING(15),
        allowNull: false,
        unique: true,
      },
    }, {
      sequelize,
      timestamps: true,
      underscored: false,
      modelName: 'Hashtag',
      tableName: 'hashtags',
      paranoid: false,
      charset: 'utf8mb4',
      collate: 'utf8mb4_general_ci',
    });
  }

  static associate(db) {
    db.Hashtag.belongsToMany(db.Post, { through: 'PostHashtag' });
  }
};

module.exports = Hashtag;

์‹œํ€„๋ผ์ด์ฆˆ (models/index.js)

์œ„์—์„œ ์ƒ์„ฑํ•œ ๋ชจ๋ธ์„ ์‹œํ€„๋ผ์ด์ฆˆ์— ๋“ฑ๋กํ•œ ํ›„, ๊ฐ๊ฐ์˜ ๋ชจ๋ธ์„ ์‹œํ€„๋ผ์ด์ฆˆ ๊ฐ์ฒด์— ์—ฐ๊ฒฐํ•œ๋‹ค.

const Sequelize = require('sequelize');
const fs = require('fs');
const path = require('path');
const env = process.env.NODE_ENV || 'development';
const config = require('../config/config')[env];

const db = {};
const sequelize = new Sequelize(
  config.database, config.username, config.password, config,
);

db.sequelize = sequelize;

const basename = path.basename(__filename);
fs
  .readdirSync(__dirname) // ํ˜„์žฌ ํด๋”์˜ ๋ชจ๋“  ํŒŒ์ผ์„ ์กฐํšŒ
  .filter(file => { // ์ˆจ๊น€ ํŒŒ์ผ, index.js, js ํ™•์žฅ์ž๊ฐ€ ์•„๋‹Œ ํŒŒ์ผ ํ•„ํ„ฐ๋ง
    return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
  })
  .forEach(file => { // ํ•ด๋‹น ํŒŒ์ผ์˜ ๋ชจ๋ธ ๋ถˆ๋Ÿฌ์™€์„œ init
    const model = require(path.join(__dirname, file));
    console.log(file, model.name);
    db[model.name] = model;
    model.initiate(sequelize);
  });

Object.keys(db).forEach(modelName => { // associate ํ˜ธ์ถœ
  if (db[modelName].associate) {
    db[modelName].associate(db);
  }
});

module.exports = db;

 
๊ฐ ๋ชจ๋ธ ๊ฐ„์˜ ๊ด€๊ณ„๋ฅผ models/user.js ์† associate ํ•จ์ˆ˜ ๋‚ด์— ์ •์˜ํ•œ๋‹ค.

  static associate(db) {
    db.User.hasMany(db.Post);
    db.User.belongsToMany(db.User, {
      foreignKey: 'followingId',
      as: 'Followers',
      through: 'Follow',
    });
    db.User.belongsToMany(db.User, {
      foreignKey: 'followerId',
      as: 'Followings',
      through: 'Follow',
    });
  }
};

 

N:M ๊ด€๊ณ„

์œ„ ๋ชจ๋ธ๋“ค์€ N:M ๊ด€๊ณ„๋ฅผ ๊ฐ€์ง„๋‹ค.

ํ•˜๋‚˜์˜ ์‚ฌ์šฉ์ž๊ฐ€ ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž์™€ ๊ด€๊ณ„๋ฅผ ๋งบ์„ ์ˆ˜ ์žˆ๋Š” ๊ด€๊ณ„๋ฅผ N:M ๊ด€๊ณ„๋ผ๊ณ  ํ•˜๋Š”๋ฐ, ํŒ”๋กœ์ž‰ ๊ธฐ๋Šฅ์ด ๋Œ€ํ‘œ์ ์ด๋‹ค.
 
๋™์ผํ•œ ํ…Œ์ด๋ธ”์„ ์ฐธ์กฐํ•˜๋Š” ๊ด€๊ณ„๋ฅผ ๊ตฌ๋ถ„ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” as ์˜ต์…˜์„ ์‚ฌ์šฉํ•œ๋‹ค.
 

  • followerId: ํŒ”๋กœ์šฐ๋ฅผ ํ•œ ์‚ฌ์šฉ์ž ID
  • followingId: ํŒ”๋กœ์šฐ๋ฅผ ๋ฐ›์€ ์‚ฌ์šฉ์ž ID
  • foreignKey๊ฐ€ followerId๋ผ๋Š” ๊ฒƒ์€ ํŒ”๋กœ์šฐ๋ฅผ ํ•œ ์‚ฌ์šฉ์ž๋ฅผ ๋‚˜ํƒ€๋‚ด๋ฏ€๋กœ, ์ด๋ฅผ ๊ตฌ๋ถ„ํ•˜๊ธฐ ์œ„ํ•ด as๋ฅผ 'Followings'๋กœ ์„ค์ •ํ•œ๋‹ค.
  • foreignKey๊ฐ€ followingId๋ผ๋Š” ๊ฒƒ์€ ํŒ”๋กœ์šฐ๋ฅผ ๋ฐ›์€ ์‚ฌ์šฉ์ž๋ฅผ ๋‚˜ํƒ€๋‚ด๋ฏ€๋กœ, ์ด๋ฅผ ๊ตฌ๋ถ„ํ•˜๊ธฐ ์œ„ํ•ด as๋ฅผ 'Followers'๋กœ ์„ค์ •ํ•œ๋‹ค.

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๋ชจ๋‘ ์„ค์ •ํ•œ ํ›„์—๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด app.js์— ๋ชจ๋ธ์„ ์„œ๋ฒ„์™€ ์—ฐ๊ฒฐํ•œ๋‹ค.

...
dotenv.config();
const pageRouter = require('./routes/page');
**const { sequelize } = require('./models');**

const app = express();
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);
  });**

app.use(morgan('dev'));
...

 

9.3. Passport ๋ชจ๋“ˆ๋กœ ๋กœ๊ทธ์ธ ๊ตฌํ˜„ํ•˜๊ธฐ

๐Ÿ“Œ ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ
 
๋ณต์žกํ•œ ์ž‘์—…์„ ์ง์ ‘ ๊ตฌํ˜„ํ•˜๊ธฐ๋ณด๋‹จ, ์ด๋ฏธ ๊ฒ€์ฆ๋œ ๋ชจ๋“ˆ์ธ Passport ๋ชจ๋“ˆ์„ ์ด์šฉํ•ด ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•œ๋‹ค.
 
๋จผ์ € Passport ๊ด€๋ จ ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•œ๋‹ค.

$ npm i passport passport-local passport-kakao bcrypt

 

Passport (passport/index.js)

const passport = require('passport');
const local = require('./localStrategy');
const kakao = require('./kakaoStrategy');
const User = require('../models/user');

module.exports = () => {
  passport.serializeUser((user, done) => {
    done(null, user.id);
  });

  passport.deserializeUser((id, done) => {
    User.findOne({ where: { id } })
      .then(user => done(null, user))
      .catch(err => done(err));
  });

  local();
  kakao();
};

 
 
์œ„ ์ฝ”๋“œ์—์„œ

  • serializeUser: ์‚ฌ์šฉ์ž ์ •๋ณด ๊ฐ์ฒด์—์„œ ์•„์ด๋””๋ฅผ ์ถ”๋ ค ์„ธ์…˜์— ์ €์žฅํ•œ๋‹ค. ๋กœ๊ทธ์ธ ์‹œ ์‹คํ–‰๋˜๋ฉฐ, req.session ๊ฐ์ฒด์— ์–ด๋–ค ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ• ์ง€ ์ •ํ•œ๋‹ค.
  • deserializeUser: ์„ธ์…˜์— ๋ถˆํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ด์•„๋‘์ง€ ์•Š๊ธฐ ์œ„ํ•ด, ์„ธ์…˜์— ์ €์žฅํ•œ ์•„์ด๋””๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ์ž ์ •๋ณด ๊ฐ์ฒด ๋ถˆ๋Ÿฌ์˜จ๋‹ค.

๋กœ๊ทธ์ธ ๊ณผ์ •์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

1. /auth/login ๋ผ์šฐํ„ฐ๋ฅผ ํ†ตํ•ด ๋กœ๊ทธ์ธ ์š”์ฒญ์ด ๋“ค์–ด์˜ด
2. ๋ผ์šฐํ„ฐ์—์„œ passport.authenticate ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ
3. ๋กœ๊ทธ์ธ ๋กœ์ง ์ˆ˜ํ–‰
4. ๋กœ๊ทธ์ธ ์„ฑ๊ณต ์‹œ ์‚ฌ์šฉ์ž ์ •๋ณด ๊ฐ์ฒด์™€ ํ•จ๊ป˜ req.login ํ˜ธ์ถœ
5. req.login ๋ฉ”์„œ๋“œ๊ฐ€ passport/serializeUser ํ˜ธ์ถœ
6. req.session์— ์‚ฌ์šฉ์ž ์•„์ด๋””๋งŒ ์ €์žฅํ•ด์„œ ์„ธ์…˜ ์ƒ์„ฑ
7. express-session์— ์„ค์ •ํ•œ ๋Œ€๋กœ ๋ธŒ๋ผ์šฐ์ €์— connect.sid ์„ธ์…˜ ์ฟ ํ‚ค ์ „์†ก
8. ๋กœ๊ทธ์ธ ์™„๋ฃŒ

 
๋กœ๊ทธ์ธ ์ดํ›„์˜ ๊ณผ์ •์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

1. ์š”์ฒญ์ด ๋“ค์–ด์˜ด
2. ๋ผ์šฐํ„ฐ์— ์š”์ฒญ์ด ๋„๋‹ฌํ•˜๊ธฐ ์ „์— passport.session ๋ฏธ๋“ค์›จ์–ด๊ฐ€ passport.deserializeUser ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ
3. connect.sid ์„ธ์…˜ ์ฟ ํ‚ค๋ฅผ ์ฝ๊ณ  ์„ธ์…˜ ๊ฐ์ฒด๋ฅผ ์ฐพ์•„์„œ req.session์œผ๋กœ ๋งŒ๋“ฆ
4. req.session์— ์ €์žฅ๋œ ์•„์ด๋””๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์‚ฌ์šฉ์ž ์กฐํšŒ
5. ์กฐํšŒ๋œ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ req.user์— ์ €์žฅ(deserializeUser)
6. ๋ผ์šฐํ„ฐ์—์„œ req.user ๊ฐ์ฒด ์‚ฌ์šฉ ๊ฐ€๋Šฅ

 

9.3.1. ๋กœ์ปฌ ๋กœ๊ทธ์ธ ๊ตฌํ˜„ํ•˜๊ธฐ

๐Ÿ“Œ ์ž์ฒด์ ์œผ๋กœ ํšŒ์› ๊ฐ€์ž… ํ›„ ๋กœ๊ทธ์ธํ•˜๋Š” ๊ฒƒ (์•„์ด๋””/๋น„๋ฐ€๋ฒˆํ˜ธ or ์ด๋ฉ”์ผ/๋น„๋ฐ€๋ฒˆํ˜ธ๋กœ ๋กœ๊ทธ์ธ)
 
passport-local ๋ชจ๋“ˆ์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌํ˜„ํ•œ๋‹ค.

ํšŒ์› ๊ฐ€์ž…, ๋กœ๊ทธ์ธ, ๋กœ๊ทธ์•„์›ƒ ๋ผ์šฐํ„ฐ ๋งŒ๋“ค๊ธฐ

  • ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž: ํšŒ์› ๊ฐ€์ž…๊ณผ ๋กœ๊ทธ์ธ ๋ผ์šฐํ„ฐ์— ์ ‘๊ทผํ•˜๋ฉด ์•ˆ ๋œ๋‹ค.
  • ๋กœ๊ทธ์ธํ•˜์ง€ ์•Š์€ ์‚ฌ์šฉ์ž: ๋กœ๊ทธ์•„์›ƒ ๋ผ์šฐํ„ฐ์— ์ ‘๊ทผํ•˜๋ฉด ์•ˆ ๋œ๋‹ค.

-> ์ ‘๊ทผ ๊ถŒํ•œ์„ ์ œํ•œํ•˜๋Š” ๋ฏธ๋“ค์›จ์–ด๊ฐ€ ํ•„์š” (middlewares/index.js)

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

exports.isNotLoggedIn = (req, res, next) => {
  if (!req.isAuthenticated()) { // ๋กœ๊ทธ์ธ o ์ƒํƒœ
    next();
  } else {
    const message = encodeURIComponent('๋กœ๊ทธ์ธํ•œ ์ƒํƒœ์ž…๋‹ˆ๋‹ค.');
    res.redirect(`/?error=${message}`);
  }
};

 
์ด ๋ฏธ๋“ค์›จ์–ด๊ฐ€ page ๋ผ์šฐํ„ฐ์— ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ ์šฉ๋œ๋‹ค. (routes/page.js)

const express = require('express');
const { isLoggedIn, isNotLoggedIn } = require('../middlewares');
const { renderProfile, renderJoin, renderMain } = require('../controllers/page');

const router = express.Router();

router.use((req, res, next) => {
  res.locals.user = req.user;
  res.locals.followerCount = 0;
  res.locals.followingCount = 0;
  res.locals.followingIdList = [];
  next();
});

router.get('/profile', isLoggedIn, renderProfile);

router.get('/join', isNotLoggedIn, renderJoin);

router.get('/', renderMain);

module.exports = router;

 
์œ„์ฒ˜๋Ÿผ ์ ‘๊ทผ ์„ค์ •์„ ์™„๋ฃŒํ•˜์˜€์œผ๋ฉด, ํšŒ์› ๊ฐ€์ž…, ๋กœ๊ทธ์ธ, ๋กœ๊ทธ์•„์›ƒ ๋ผ์šฐํ„ฐ๋ฅผ ์ž‘์„ฑํ•œ๋‹ค. (routes/auth.js)

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

const { isLoggedIn, isNotLoggedIn } = require('../middlewares');
const { join, login, logout } = require('../controllers/auth');

const router = express.Router();

// POST /auth/join
router.post('/join', isNotLoggedIn, join); 

// POST /auth/login
router.post('/login', isNotLoggedIn, login);

// GET /auth/logout
router.get('/logout', isLoggedIn, logout);

// GET /auth/kakao
router.get('/kakao', passport.authenticate('kakao'));

// GET /auth/kakao/callback
router.get('/kakao/callback', passport.authenticate('kakao', {
  failureRedirect: '/?error=์นด์นด์˜ค๋กœ๊ทธ์ธ ์‹คํŒจ',
}), (req, res) => {
  res.redirect('/'); // ์„ฑ๊ณต ์‹œ์—๋Š” /๋กœ ์ด๋™
});

module.exports = router;

 
ํšŒ์› ๊ฐ€์ž…, ๋กœ๊ทธ์ธ, ๋กœ๊ทธ์•„์›ƒ ์ปจํŠธ๋กค๋Ÿฌ๋„ ์ž‘์„ฑํ•œ๋‹ค. (controllers/auth.js)

const bcrypt = require('bcrypt');
const passport = require('passport');
const User = require('../models/user');

// ํšŒ์› ๊ฐ€์ž… ์ปจํŠธ๋กค๋Ÿฌ
exports.join = async (req, res, next) => {
  const { email, nick, password } = req.body;
  try {
    const exUser = await User.findOne({ where: { email } });
    if (exUser) {
      return res.redirect('/join?error=exist');
    }
    const hash = await bcrypt.hash(password, 12);
    await User.create({
      email,
      nick,
      password: hash,
    });
    return res.redirect('/');
  } catch (error) {
    console.error(error);
    return next(error);
  }
}

// ๋กœ๊ทธ์ธ ์ปจํŠธ๋กค๋Ÿฌ
exports.login = (req, res, next) => {
  passport.authenticate('local', (authError, user, info) => {
    if (authError) {
      console.error(authError);
      return next(authError);
    }
    if (!user) {
      return res.redirect(`/?error=${info.message}`);
    }
    return req.login(user, (loginError) => {
      if (loginError) {
        console.error(loginError);
        return next(loginError);
      }
      return res.redirect('/');
    });
  })(req, res, next); // ๋ฏธ๋“ค์›จ์–ด ๋‚ด์˜ ๋ฏธ๋“ค์›จ์–ด์—๋Š” (req, res, next)๋ฅผ ๋ถ™์ž…๋‹ˆ๋‹ค.
};

// ๋กœ๊ทธ์•„์›ƒ ์ปจํŠธ๋กค๋Ÿฌ
exports.logout = (req, res) => {
  req.logout(() => {
    res.redirect('/');
  });
};

 
 
๋งˆ์ง€๋ง‰์œผ๋กœ ๋กœ๊ทธ์ธ ์ „๋žต์„ ๊ตฌํ˜„ํ•˜๋ฉด ๋œ๋‹ค. (passport/localStrategy.js)

const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const bcrypt = require('bcrypt');

const User = require('../models/user');

module.exports = () => {
  passport.use(new LocalStrategy({
    usernameField: 'email',
    passwordField: 'password',
    passReqToCallback: false,
  }, async (email, password, done) => {
    try {
      const exUser = await User.findOne({ where: { email } });
      if (exUser) {
        const result = await bcrypt.compare(password, exUser.password);
        if (result) {
          done(null, exUser);
        } else {
          done(null, false, { message: '๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.' });
        }
      } else {
        done(null, false, { message: '๊ฐ€์ž…๋˜์ง€ ์•Š์€ ํšŒ์›์ž…๋‹ˆ๋‹ค.' });
      }
    } catch (error) {
      console.error(error);
      done(error);
    }
  }));
};

 
์ „๋žต์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋กœ์ง์„ ๊ฐ€์ง„๋‹ค.

 

9.3.2. ์นด์นด์˜ค ๋กœ๊ทธ์ธ ๊ตฌํ˜„ํ•˜๊ธฐ

๐Ÿ“Œ ๋กœ๊ทธ์ธ ์ธ์ฆ ๊ณผ์ •์„ ์นด์นด์˜ค์— ๋งก๊ธฐ๋Š” ๊ฒƒ
 
passport-kakao ๋ชจ๋“ˆ๋กœ๋ถ€ํ„ฐ Strategy ์ƒ์„ฑ์ž๋ฅผ ๋ถˆ๋Ÿฌ์™€ ์ „๋žต์„ ๊ตฌํ˜„ํ•œ๋‹ค.

์นด์นด์˜ค ๋กœ๊ทธ์ธ ์ „๋žต ์ž‘์„ฑํ•˜๊ธฐ (passport/kakaoStrategy.js)

const passport = require('passport');
const KakaoStrategy = require('passport-kakao').Strategy;

const User = require('../models/user');

// ์นด์นด์˜ค ๋กœ๊ทธ์ธ์— ๋Œ€ํ•œ ์„ค์ •
module.exports = () => {
  passport.use(new KakaoStrategy({
    clientID: process.env.KAKAO_ID,
    callbackURL: '/auth/kakao/callback',
  }, async (accessToken, refreshToken, profile, done) => { // ์นด์นด์˜ค ํšŒ์›๊ฐ€์ž… ์—ฌ๋ถ€ ์กฐํšŒ
    console.log('kakao profile', profile);
    try {
      const exUser = await User.findOne({
        where: { snsId: profile.id, provider: 'kakao' },
      });
      if (exUser) {
        done(null, exUser);
      } else { // ์นด์นด์˜ค ํšŒ์›์ด ์•„๋‹ˆ๋ผ๋ฉด ํšŒ์›๊ฐ€์ž… ์ง„ํ–‰
        const newUser = await User.create({
          email: profile._json?.kakao_account?.email,
          nick: profile.displayName,
          snsId: profile.id,
          provider: 'kakao',
        });
        done(null, newUser);
      }
    } catch (error) {
      console.error(error);
      done(error);
    }
  }));
};

์นด์นด์˜ค ๋กœ๊ทธ์ธ ๋ผ์šฐํ„ฐ ์ž‘์„ฑํ•˜๊ธฐ (routes/auth.js)

...
router.get('/logout', isLoggedIn, logout);

// GET /auth/kakao -> ์นด์นด์˜ค ๋กœ๊ทธ์ธ ์ „๋žต ์ˆ˜ํ–‰
router.get('/kakao', passport.authenticate('kakao'));

// GET /auth/kakao/callback -> ๋กœ๊ทธ์ธ ํ›„ ์„ฑ๊ณต ์—ฌ๋ถ€ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ›์Œ
router.get('/kakao/callback', passport.authenticate('kakao', {
  failureRedirect: '/?error=์นด์นด์˜ค๋กœ๊ทธ์ธ ์‹คํŒจ',
}), (req, res) => {
  res.redirect('/'); // ์„ฑ๊ณต ์‹œ์—๋Š” /๋กœ ์ด๋™
});

module.exports = router;

 
 
์œ„ ๋ผ์šฐํ„ฐ ์ž‘์„ฑ ํ›„ app.js์— ์—ฐ๊ฒฐํ•˜๋ฉด ๋œ๋‹ค.
๊ทธ ํ›„ https://developers.kakao.com/ ์ ‘์† ํ›„ clientID๋ฅผ ๋ฐœ๊ธ‰๋ฐ›๊ณ  ์นด์นด์˜ค ๊ฐœ๋ฐœ์ž ๊ณ„์ •๊ณผ ์นด์นด์˜ค ๋กœ๊ทธ์ธ์šฉ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋“ฑ๋กํ•˜์—ฌ ์ž‘๋™ํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•˜๋ฉด ๋œ๋‹ค.

9.4. multer ํŒจํ‚ค์ง€๋กœ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ๊ตฌํ˜„ํ•˜๊ธฐ

๐Ÿ“Œ multer ๋ชจ๋“ˆ์„ ์‚ฌ์šฉํ•ด ๋ฉ€ํ‹ฐ ํŒŒํŠธ ํ˜•์‹์˜ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ๊ธฐ๋Šฅ ๊ตฌํ˜„
 
๋จผ์ € ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•œ ํ›„

$ npm i multer

 
post ๋ผ์šฐํ„ฐ(routes/post.js)์™€

const express = require('express');
const multer = require('multer');
const path = require('path');
const fs = require('fs');

const { afterUploadImage, uploadPost } = require('../controllers/post');
const { isLoggedIn } = require('../middlewares');

const router = express.Router();

try {
  fs.readdirSync('uploads');
} catch (error) {
  console.error('uploads ํด๋”๊ฐ€ ์—†์–ด uploads ํด๋”๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.');
  fs.mkdirSync('uploads');
}

const upload = multer({
  storage: multer.diskStorage({
    destination(req, file, cb) {
      cb(null, 'uploads/');
    },
    filename(req, file, cb) {
      const ext = path.extname(file.originalname);
      cb(null, path.basename(file.originalname, ext) + Date.now() + ext);
    },
  }),
  limits: { fileSize: 5 * 1024 * 1024 },
});

// POST /post/img -> ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œ ๋ฐ›์€ ํ›„ ์ด๋ฏธ์ง€์˜ ์ €์žฅ ๊ฒฝ๋กœ๋ฅผ ํด๋ผ์ด์–ธํŠธ๋กœ ์‘๋‹ต
router.post('/img', isLoggedIn, upload.single('img'), afterUploadImage);

// POST /post -> ๊ฒŒ์‹œ๊ธ€ ์—…๋กœ๋“œ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ผ์šฐํ„ฐ
const upload2 = multer();
router.post('/', isLoggedIn, upload2.none(), uploadPost);

module.exports = router;

 
์ปจํŠธ๋กค๋Ÿฌ(controllers/post.js)๋ฅผ ์ž‘์„ฑํ•œ๋‹ค.

const { Post, Hashtag } = require('../models');

exports.afterUploadImage = (req, res) => {
  console.log(req.file);
  res.json({ url: `/img/${req.file.filename}` });
};

exports.uploadPost = async (req, res, next) => {
  try {
    const post = await Post.create({
      content: req.body.content,
      img: req.body.url,
      UserId: req.user.id,
    });
    const hashtags = req.body.content.match(/#[^\s#]*/g);
    if (hashtags) {
      const result = await Promise.all(
        hashtags.map(tag => {
          return Hashtag.findOrCreate({
            where: { title: tag.slice(1).toLowerCase() },
          })
        }),
      );
      await post.addHashtags(result.map(r => r[0]));
    }
    res.redirect('/');
  } catch (error) {
    console.error(error);
    next(error);
  }
};

 
์œ„์˜ ๊ณผ์ •์„ ํ†ตํ•ด ๊ฒŒ์‹œ๊ธ€ ์ž‘์„ฑ ๊ธฐ๋Šฅ์ด ์ถ”๊ฐ€๋˜์—ˆ์œผ๋ฏ€๋กœ ๋ฉ”์ธ ํŽ˜์ด์ง€ ๋กœ๋”ฉ ์‹œ ๋ฉ”์ธ ํŽ˜์ด์ง€์™€ ๊ฒŒ์‹œ๊ธ€์ด ํ•จ๊ป˜ ๋กœ๋”ฉ๋˜๋„๋ก controllers/page.js๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.

const { User, Post } = require('../models');

exports.renderProfile = (req, res) => {
  res.render('profile', { title: '๋‚ด ์ •๋ณด - NodeBird' });
};

exports.renderJoin = (req, res) => {
  res.render('join', { title: 'ํšŒ์›๊ฐ€์ž… - NodeBird' });
};

exports.renderMain = async (req, res, next) => {
  try {
    const posts = await Post.findAll({
      include: {
        model: User,
        attributes: ['id', 'nick'],
      },
      order: [['createdAt', 'DESC']],
    });
    res.render('main', {
      title: 'NodeBird',
      twits: posts,
    });
  } catch (err) {
    console.error(err);
    next(err);
  }
}

 

9.5. ํ”„๋กœ์ ํŠธ ๋งˆ๋ฌด๋ฆฌํ•˜๊ธฐ

๐Ÿ“Œ ํŒ”๋กœ์ž‰ ๊ธฐ๋Šฅ๊ณผ ํ•ด์‹œํƒœ๊ทธ ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ
 
๋Š˜ ๊ทธ๋žฌ๋“ฏ์ด ๋ผ์šฐํ„ฐ(routes/user.js)๋ฅผ ๋จผ์ € ๊ตฌํ˜„ํ•˜๊ณ 

const express = require('express');

const { isLoggedIn } = require('../middlewares');
const { follow } = require('../controllers/user');

const router = express.Router();

// POST /user/:id/follow
router.post('/:id/follow', isLoggedIn, follow);

module.exports = router;

 
์ปจํŠธ๋กค๋Ÿฌ(controllers/user.js)๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค.

const User = require('../models/user');

exports.follow = async (req, res, next) => {
  try {
    const user = await User.findOne({ where: { id: req.user.id } });
    if (user) { // req.user.id๊ฐ€ followerId, req.params.id๊ฐ€ followingId
      await user.addFollowing(parseInt(req.params.id, 10));
      res.send('success');
    } else {
      res.status(404).send('no user');
    }
  } catch (error) {
    console.error(error);
    next(error);
  }
};

 
ํŒ”๋กœ์ž‰ ๊ด€๊ณ„๊ฐ€ ์ƒ๊ฒผ์œผ๋ฏ€๋กœ req.user์—๋„ ํŒ”๋กœ์›Œ์™€ ํŒ”๋กœ์ž‰ ๋ชฉ๋ก์„ ์ €์žฅํ•œ๋‹ค. (passport/index.js)

...
passport.deserializeUser((id, done) => {
    console.log('deserialize');
    User.findOne({
      where: { id },
      include: [{
        model: User,
        attributes: ['id', 'nick'],
        as: 'Followers',
      }, {
        model: User,
        attributes: ['id', 'nick'],
        as: 'Followings',
      }],
    })
      .then(user => {
        console.log('user', user);
        done(null, user);
       })
      .catch(err => done(err));
  });
  ...

 
routes/page.js๋„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜์ •ํ•œ๋‹ค.

const express = require('express');
const { isLoggedIn, isNotLoggedIn } = require('../middlewares');
const {
  renderProfile, renderJoin, renderMain, renderHashtag,
} = require('../controllers/page');

const router = express.Router();

router.use((req, res, next) => {
  res.locals.user = req.user;
  res.locals.followerCount = req.user?.Followers?.length || 0;
  res.locals.followingCount = req.user?.Followings?.length || 0;
  res.locals.followingIdList = req.user?.Followings?.map(f => f.id) || [];
  next();
});

router.get('/profile', isLoggedIn, renderProfile);

router.get('/join', isNotLoggedIn, renderJoin);

router.get('/', renderMain);

router.get('/hashtag', renderHashtag);

module.exports = router;

 
๋งˆ์ง€๋ง‰์œผ๋กœ ์ปจํŠธ๋กค๋Ÿฌ(controllers/page.js)๋„ ์ž‘์„ฑํ•œ ํ›„

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

exports.renderProfile = (req, res) => {
  res.render('profile', { title: '๋‚ด ์ •๋ณด - NodeBird' });
};

exports.renderJoin = (req, res) => {
  res.render('join', { title: 'ํšŒ์›๊ฐ€์ž… - NodeBird' });
};

exports.renderMain = async (req, res, next) => {
  try {
    const posts = await Post.findAll({
      include: {
        model: User,
        attributes: ['id', 'nick'],
      },
      order: [['createdAt', 'DESC']],
    });
    res.render('main', {
      title: 'NodeBird',
      twits: posts,
    });
  } catch (err) {
    console.error(err);
    next(err);
  }
}

exports.renderHashtag = async (req, res, next) => {
  const query = req.query.hashtag;
  if (!query) {
    return res.redirect('/');
  }
  try {
    const hashtag = await Hashtag.findOne({ where: { title: query } });
    let posts = [];
    if (hashtag) {
      posts = await hashtag.getPosts({ include: [{ model: User }] });
    }

    return res.render('main', {
      title: `${query} | NodeBird`,
      twits: posts,
    });
  } catch (error) {
    console.error(error);
    return next(error);
  }
};

 
๋ผ์šฐํ„ฐ๋ฅผ app.js์— ์—ฐ๊ฒฐํ•˜๋ฉด ๋๋‚œ๋‹ค.

...
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');
...
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('/', pageRouter);
app.use('/auth', authRouter);
app.use('/post', postRouter);
app.use('/user', userRouter);
...

 


Quiz

1. (      ์ปจํŠธ๋กค๋Ÿฌ      )๋Š” ๋ผ์šฐํ„ฐ ๋งˆ์ง€๋ง‰์— ์œ„์น˜ํ•ด ํด๋ผ์ด์–ธํŠธ์— ์‘๋‹ต์„ ๋ณด๋‚ด๋Š” ๋ฏธ๋“ค์›จ์–ด์ด๋‹ค.
2. ํ•˜๋‚˜์˜ ์‚ฌ์šฉ์ž๊ฐ€ ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž์™€ ๊ด€๊ณ„๋ฅผ ๋งบ์„ ์ˆ˜ ์žˆ๋Š” ๊ด€๊ณ„๋ฅผ (      N:M ๊ด€๊ณ„      )๋ผ๊ณ  ๋ถ€๋ฅธ๋‹ค.
3. N:M ๊ด€๊ณ„์—์„œ ๋™์ผํ•œ ํ…Œ์ด๋ธ”์„ ์ฐธ์กฐํ•˜๋Š” ๊ด€๊ณ„๋ฅผ ๊ตฌ๋ถ„ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” (        as        ) ์˜ต์…˜์„ ์‚ฌ์šฉํ•œ๋‹ค.
4. ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•  ๋•Œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“ˆ์—๋Š” (       Passport    )๊ฐ€ ์žˆ๋‹ค.
5. ๋กœ์ปฌ ๋กœ๊ทธ์ธ์ด ์•„๋‹Œ ์นด์นด์˜ค ๋กœ๊ทธ์ธ์„ ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” (   passport-kakao   ) ๋ชจ๋“ˆ์„ ์‚ฌ์šฉํ•œ๋‹ค.
6. ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ๋ฅผ ์œ„ํ•ด์„œ๋Š” (      multer      ) ๋ชจ๋“ˆ์„ ์‚ฌ์šฉํ•œ๋‹ค.
7. ํŒ”๋กœ์ž‰ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•  ๋•Œ ํŒ”๋กœ์›Œ์™€ ํŒ”๋กœ์ž‰ ๋ชฉ๋ก์€ (      req.user      )์— ์ €์žฅํ•ด์•ผ ํ•œ๋‹ค.
8. ๋ผ์šฐํ„ฐ๋ฅผ ๊ตฌํ˜„ํ•œ ํ›„์—๋Š” ๋ฐ˜๋“œ์‹œ (     app.js       )์— ํ•ด๋‹น ๋ผ์šฐํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•œ๋‹ค.
 

Programming Quiz

1. ๋กœ๊ทธ์ธ ๋ผ์šฐํ„ฐ๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ ์ „, ์ ‘๊ทผ ๊ถŒํ•œ์„ ์ œ์–ดํ•˜๋Š” ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์ž‘์„ฑํ•˜๋ ค ํ•œ๋‹ค. ๋นˆ์นธ์„ ์ฑ„์šฐ์‹œ์˜ค.

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

exports.isNotLoggedIn = (req, res, next) => {
  if (                 ) { // (2) 
    next();
  } else {
    const message = encodeURIComponent('๋กœ๊ทธ์ธํ•œ ์ƒํƒœ์ž…๋‹ˆ๋‹ค.');
    res.redirect(`/?error=${message}`);
  }
};

 
2. Passport ๋ชจ๋“ˆ๋กœ ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๊ณ ์ž ํ•œ๋‹ค. ๋‹ค์Œ passport/index.js ์ฝ”๋“œ์˜ ๋นˆ์นธ์„ ์ฑ„์šฐ์‹œ์˜ค.

const passport = require('passport');
const local = require('./localStrategy');
const kakao = require('./kakaoStrategy');
const User = require('../models/user');

module.exports = () => {
   // (1)
   
  });

  // (2)
  
  
  
  });

  local();
  kakao();
};

Answer

1.
(1) req.isAuthenticated()
(2) !req.isAuthenticated()
2.
(1) passport.serializeUser((user, done) => {
    done(null, user.id);
(2) passport.deserializeUser((id, done) => {
    User.findOne({ where: { id } })
      .then(user => done(null, user))
      .catch(err => done(err));


 
์ถœ์ฒ˜ :  ์กฐํ˜„์˜, ใ€Ž Node.js ๊ต๊ณผ์„œ ๊ฐœ์ • 3ํŒใ€, ๊ธธ๋ฒ—(2022)

Corner Node.js 1
Editor : ๋น ๋‹ค์ฝ”์ฝ”๋„›

728x90

๊ด€๋ จ๊ธ€ ๋”๋ณด๊ธฐ