[Back-end๐Ÿฆ] #42 node.js - SNS ์„œ๋ฒ„ ๋งŒ๋“ค๊ธฐ

๋˜์ƒยท2021๋…„ 12์›” 28์ผ
1

front-end

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

๋ฐ˜๋ ค๋™๋ฌผ SNS ๋งŒ๋“ค๊ธฐ

ํšŒ์›๊ฐ€์ž…, ๋กœ๊ทธ์ธ
๊ธ€์“ฐ๊ธฐ / ์กฐํšŒ / ์ˆ˜์ • / ์‚ญ์ œ
์ข‹์•„์š”, ๋Œ“๊ธ€
์‹ค์‹œ๊ฐ„ ์ฑ„ํŒ… (socket io)


1. ํ”„๋กœ์ ํŠธ ํ™˜๊ฒฝ ์„ค์ •

1. package ์„ค์น˜

npm install multer multer -s3 mongoose socket.io jsonwebtoken bcrypt aws-sdk
# multer : ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ
# multer -s3, aws-sdk : ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ๋ฅผ ์šฐ๋ฆฌ ์„œ๋ฒ„๊ฐ€ ์•„๋‹ˆ๋ผ ์•„๋งˆ์กด S3 ์ €์žฅ์†Œ์— ์—…๋กœ๋“œํ•˜๊ธฐ ์‰ฝ๊ฒŒ ํ•ด์ฃผ๋Š” ํŒจํ‚ค์ง€.
# mongoose : mongoDB ์‚ฌ์šฉ์„ ์‰ฝ๊ฒŒ ํ•ด์คŒ.
# socket.io : ์‹ค์‹œ๊ฐ„ ์ฑ„ํŒ… ์„œ๋ฒ„๋ฅผ ์‰ฝ๊ฒŒ ํ•ด์ฃผ๋Š”!!
# jsonwebtoken : ๋กœ๊ทธ์ธ ์„ธ์…˜ ๋Œ€์‹  ์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ ์ธ์ฆ์„ JWT ๋กœ ์ง„ํ–‰
# bcrypt : ๋น„๋ฐ€๋ฒˆํ˜ธ ํ•ด์‹œํ•จ์ˆ˜ ์ ์šฉ์„ ์œ„ํ•œ ํŒจํ‚ค์ง€

2. html, css ํŒŒ์ผ

ํ”„๋ก ํŠธ์—”๋“œ ejs, css ํŒŒ์ผ์€ ์ „๋ถ€ ๋ณต๋ถ™ํ•˜๊ณ (!!!) ๋“ค์–ด๊ฐ”๋‹ค. ๋ฐฑ์—”๋“œ ๊ฐ•์˜๋ผ์„œ ์ž์„ธํžˆ๋Š” ๋ชฐ๋ผ๋„ ๋œ๋‹ค๊ณ  ํ•˜์…จ์ง€๋งŒ... ์šฐ๋ฆฌ๋Š” ํ”„๋ก ํŠธ์—”๋“œ ๊ณผ์ •์ด๋‹ˆ๊นŒ ์•Œ์•„์•ผ๊ฒ ์ง€... ๊ทธ์น˜๋งŒ ์„œ๋ฒ„ ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•œ ๊ฐ„๋‹จํ•œ ๊ตฌ์กฐ์˜€์–ด์„œ ์–ด๋ ค์šด ๊ตฌ์กฐ๋Š” ์—†์„ ๋“ฏ ํ•˜๋‹ค! ejs ํŒŒ์ผ์ด๋ผ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ๋กœ์ง ๋“ค์–ด๊ฐ€๋Š” ๋ถ€๋ถ„๋งŒ ๋” ๋ณด๋ฉด ๋ ๋“ฏ!!


3. routes ๊ฒฝ๋กœ ์ง€์ •.

express ๊ธฐ๋ณธ ์„ค์ •์ธ routes/users.js ์‚ญ์ œ, app.js์—์„œ users.js ๊ด€๋ จ ์ฝ”๋“œ ์‚ญ์ œํ•œ๋‹ค.

routes/auth/index.js, routes/posts/index.js ๋กœ ํšŒ์›๊ฐ€์ž…, ๋กœ๊ทธ์ธ๊ณผ ๊ฒŒ์‹œ๋ฌผ ๊ด€๋ จ ์ฝ”๋“œ๋ฅผ ๋‹ค๋ฅธ ํด๋”๋กœ ๊ด€๋ฆฌํ•œ๋‹ค. ๊ฐœ๋ฐœ ์ค‘์—๋Š” ํŒŒ์ผ์„ ์™”๋‹ค๊ฐ”๋‹ค ํ•ด์•ผํ•ด์„œ

// routes/index.js
// ์–ด์ œ ํ”„๋กœ์ ํŠธ๋Š” app.js์— ๋ฐ”๋กœ ๋‚˜๋ˆ ์„œ ์ฒ˜๋ฆฌ ํ–ˆ์—ˆ๋Š”๋ฐ
// routes ํด๋”๋ฅผ ์ •๋ฆฌํ•˜๋ฉด์„œ app.js์—์„œ ์–ด๋–ค ๋กœ์ง์ด ํ•„์š”ํ•  ๋•Œ, index.js๋กœ ๊ฐ€๊ฒŒ ๋งŒ๋“ค๊ณ  ๊ฑฐ๊ธฐ์„œ ๋‹ค์‹œ ๋‚˜๋‰˜๊ฒŒ ์ฒ˜๋ฆฌ๋ฅผ ํ–ˆ๋‹ค. 
var express = require('express');
var router = express.Router();

const authRouter = require('./auth');
const postRouter = require('./posts');
const postCtr = require('../controller/postCtr');

// ๊ฒŒ์‹œ๋ฌผ ๋ฆฌ์ŠคํŠธ ์กฐํšŒ : ๋ฉ”์ธํ™”๋ฉด์— ๋“ค์–ด๊ฐ€์ž๋งˆ์ž ๊ฒŒ์‹œ๋ฌผ ๋ฆฌ์ŠคํŠธ๊ฐ€ ๋œจ๋‹ˆ๊นŒ.
router.get("/", postCtr.list);

// auth, posts ๊ด€๋ จ ๋‚ด์šฉ์€ ํ•ด๋‹น ๋ผ์šฐํ„ฐ๋กœ ์ด๋™ํ•˜๋„๋ก.
router.use('/auth', authRouter);
router.use('/posts', postRouter);

module.exports = router;
// routes/auth/index.js
// ํšŒ์›๊ฐ€์ž…, ๋กœ๊ทธ์ธ ๊ด€๋ จ ๋กœ์ง์„ ์ฒ˜๋ฆฌ.
const express = require("express");
const router = express.Router();

// ๋กœ์ง์„ ์•„์ง ๊ตฌํ˜„ํ•˜์ง€ ์•Š์•„์„œ ํŽ˜์ด์ง€๋งŒ ๋จผ์ € ๋ณด์—ฌ์ฃผ๋Š” ์‹์œผ๋กœ ์ฒ˜๋ฆฌ.
router.get('/login', (req, res) => {
    res.render("login");
});
router.get('/register', (req, res) => {
    res.render("register");
});

module.exports = router;
// routes/posts/index.js
// ๊ฒŒ์‹œ๋ฌผ CRUD, ์ข‹์•„์š”, ๋Œ“๊ธ€ ๋กœ์ง์„ ๋‹ด๋‹น.
const express = require("express");
const { route } = require("..");
const router = express.Router();

// ์—ญ์‹œ ์•„์ง ๋กœ์ง์„ ์งœ์ง€ ์•Š์•˜์œผ๋ฏ€๋กœ ํ™”๋ฉด์„ ๋ณด์—ฌ์ฃผ๊ธฐ๋งŒ ํ•œ๋‹ค.
router.get('/upload', (req, res) => {
    res.render('upload');
});
router.get('/:id', (req, res) => {
    res.render('update');
});

module.exports = router;

4. mongoDB ์—ฐ๊ฒฐ

์–ด์ œ๋„ ํ–ˆ์—ˆ๊ธฐ ๋•Œ๋ฌธ์—, ์ž์„ธํ•œ ์„ค๋ช…์€ ์ƒ๋žตํ•œ๋‹ค.

// bin/www
const mongoose = require("mongoose");
const db = mongoose.connection;
db.on("error", console.error);
db.once("open", () => {
  console.log("Connected to mongodb server!!");
});

mongoose.connect(
  `mongodb+srv://ddosang:${dbConfig.password}@first-project.7rouh.mongodb.net/${dbConfig.name}?retryWrites=true&w=majority`
);

5. schema ์ž‘์„ฑ

mongoDB์˜ ์žฅ์ ์€ noSQL์ด์ง€๋งŒ SQL์ฒ˜๋Ÿผ ๋นก๋นกํ•˜๊ฒŒ๋Š” ์•„๋‹ˆ๋”๋ผ๋„ ํ˜•์‹์„ ์ •ํ•ด๋‘์–ด์•ผ ์š”์ฒญ/์‘๋‹ต, ํ˜‘์—… ๊ด€๋ฆฌ๊ฐ€ ํŽธํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์žก์•˜๋‹ค. ํฌ๊ฒŒ ๊ฒŒ์‹œ๋ฌผ ๊ด€๋ จ ๋ชจ๋ธ๊ณผ ๋กœ๊ทธ์ธ ๊ด€๋ จ ๋ชจ๋ธ์ด ์žˆ๋Š”๋ฐ, ์ˆ˜์—… ๋‚ด์šฉ์„ ๋”ฐ๋ผ๊ฐˆ ๋•Œ๋Š” ์•„ ์ด๊ฑฐ ํ•„์š”ํ•˜๊ฒ ๋„ค~ ์‹ถ์€๋ฐ ํ˜ผ์ž ์งœ๋ณด๋ฉด ๋ญ”๊ฐ€ ๋†“์น˜๋Š” ๋ถ€๋ถ„์ด ์žˆ์„ ๊ฒƒ ๊ฐ™๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ค์—ˆ๋‹ค.

// model/post.js
const mongoose = require("mongoose");
const Schema = mongoose.Schema;

// ๊ฒŒ์‹œ๊ธ€ : ์ œ๋ชฉ, ๋‚ด์šฉ, ์‚ฌ์ง„, ์—…๋กœ๋“œ ๋‚ ์งœ, ์ข‹์•„์š” ์ˆ˜, ์ข‹์•„์š” ํ•œ ์‚ฌ์šฉ์ž ๋ชฉ๋ก, ๋Œ“๊ธ€ ๋ชฉ๋ก, ๊ฒŒ์‹œ๊ธ€์„ ์“ด ์‚ฌ์šฉ์ž ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.
const postSchema = new Schema({
    title: String,
    content: String,
    image: String,
    publishedDate: String,
    likeCount: {
        type: Number,
        default: 0
    },
    // ๋Œ“๊ธ€ ๋‹จ user ์ •๋ณด๋ฅผ Array๋กœ ๊ด€๋ฆฌํ•˜๋Š”๊ฒŒ ๋จธ๋ฆฌ๋กœ๋Š” ์ดํ•ด๊ฐ€ ๊ฐ€๋Š”๋ฐ ์ด๋ ‡๊ฒŒ ํƒ€์ž…, default๋ฅผ ์ฃผ๋‹ˆ๊นŒ ์‹ ๊ธฐํ•˜๋‹ค..
    likeUser: {
        type: Array,
        default: []
    },
    comment: {
        type: Array,
        default: []
    },
    user: {
        // user ์ •๋ณด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ฐ€์ ธ์˜ด.
        _id: mongoose.Types.ObjectId, 
        username: String
    }
});

module.exports = mongoose.model("post", postSchema);
// model/auth.js
const mongoose = require("mongoose");
const Schema = mongoose.Schema;

// ๋กœ๊ทธ์ธ์€ ์œ ์ € ์ด๋ฆ„๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์žˆ์œผ๋ฉด ๋œ๋‹ค.
const authSchema = new Schema({
    username: String,
    password: String
});

module.exports = mongoose.model("auth", schema);

2. ๊ฒŒ์‹œ๋ฌผ CRUD

๊ฒŒ์‹œ๋ฌผ ์ƒ์„ฑ
๊ฒŒ์‹œ๋ฌผ ์ „์ฒด ์กฐํšŒ / ๊ฒŒ์‹œ๋ฌผ ํ•˜๋‚˜ ์กฐํšŒ
๊ฒŒ์‹œ๋ฌผ ์ˆ˜์ •
๊ฒŒ์‹œ๋ฌผ ์‚ญ์ œ

1. AWS s3

์ €๋ฒˆ์—๋Š” ์ด๋ฏธ์ง€๋ฅผ ์„œ๋ฒ„์— ์ €์žฅ์„ ํ–ˆ์—ˆ์ง€๋งŒ, ์ด๋ฒˆ์—๋Š” AWS S3์— ์—…๋กœ๋“œ๋ฅผ ํ•˜๊ณ  ๋ฐ›์•„์™”๋‹ค. -> multer-s3 ํŒจํ‚ค์ง€ ์‚ฌ์šฉ.

๊ทธ๋Ÿฐ๋ฐ.. ๋‚ด AWS ๊ณ„์ •์˜ free-tier ๊ธฐ๊ฐ„์ด ํ•œ ๋‹ฌ ์ฐจ์ด๋กœ ๋๋‚˜์„œ... ๊ทธ๋ƒฅ ์ง€์ผœ๋ณด๋ฉด์„œ ์ฝ”๋“œ์™€ ๋กœ์ง์„ ์ดํ•ดํ•˜๋Š” ๋ฐฉํ–ฅ์œผ๋กœ ์ˆ˜์—…์„ ๋“ค์—ˆ๋Š”๋ฐ, ์•„๋งˆ ์„œ๋ฒ„ ์ผœ๊ณ  ์‹ค์Šต์„ ํ–ˆ์œผ๋ฉด... ์˜ค๋ฅ˜ ์žก๋Š๋ผ ์‹œ๊ฐ„ ๋‚ด๋กœ ๊ฐ•์˜๋ฅผ ๋‹ค ๋ชป๋“ค์—ˆ์„ ๊ฒƒ ๊ฐ™๋‹ค.

  1. AWS ๊ฐ€์ž… ํ›„ s3 ๋ฒ„ํ‚ท ์ƒ์„ฑ
  2. ์‚ฌ์šฉ์ž ์ถ”๊ฐ€ : ๊ถŒํ•œ์—์„œ ๊ธฐ์กด ์ •์ฑ… ์ง์ ‘ ์—ฐ๊ฒฐ -> AmazonS3FullAccess - ์—‘์„ธ์Šคํ‚ค, ๋น„๋ฐ€ ์—‘์„ธ์Šค ํ‚ค ์ž˜ ๊ด€๋ฆฌํ•˜๊ธฐ!!!!!!! ์–ด์ œ gitignore์— config ์ถ”๊ฐ€ํ•˜๋Š”๊ฑฐ ์žŠ์–ด๋ฒ„๋ ค์„œ ๋˜ ๋ถ€๋žด๋ถ€๋žด ๊นƒํ—™์— ์ปค๋ฐ‹ ์ด๋ ฅ ์‚ญ์ œํ•˜๋Š” ๋ฐฉ๋ฒ• ์ฐพ๊ณ .. ๋‚œ๋ฆฌ์˜€๋‹ค.
// module/multer.js
// AWS S3 ์— ๋Œ€ํ•œ ์„ค์ •์„ ๋‹ด๋‹นํ•˜๋Š” ๋ชจ๋“ˆ
const mutler = require("multer");
const multerS3 = require("multer-s3");
const aws = require("aws-sdk");
aws.config.loadFromPath(__dirname + "/../config/s3Info.json"); // AWS ํ‚ค๋ฅผ ์ €์žฅํ•œ ํŒŒ์ผ s3Info
const s3 = new aws.S3();


// upload๋ฅผ ํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” multer ๋ชจ๋“ˆ
const upload = multer({
    storage: multerS3({
        s3: s3,
        bucket: 'bucketname',
        acl: 'public-read-write',
        key: (req, file, cb) => {
            cb(null, Date.now() + "." + file.originalname.split(".").pop()); // ํŒŒ์ผ ์ด๋ฆ„์„ ์œ ๋‹ˆํฌํ•œ์ด๋ฆ„.ํ™•์žฅ์ž ํ˜•ํƒœ๋กœ
        }
    })
});

module.exports = upload;

2. controller

routes ํด๋” ์•ˆ์— ์žˆ๋Š” ๊ฒƒ์—๋Š” ์ •๋ง route์— ๊ด€ํ•œ ์ฝ”๋“œ๋งŒ ์ž‘์„ฑํ•˜๊ณ , ๋กœ์ง์€ Controller๋กœ ๋นผ์„œ ์ž‘์„ฑํ–ˆ๋‹ค. MVC๊ฐ€ ์ƒ๊ฐ๋‚ฌ๋‹ค.. ์–ด์ œ ํ–ˆ๋˜ ์ฝ”๋“œ๋„ controller์—์„œ ๋กœ์ง์„ ๋‹ด๋‹นํ•˜๋Š” ๊ตฌ์กฐ๋กœ ๋ฐ”๊ฟ”๋ณด๋ฉด ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค.

// controller/postCtr.js
// ๊ฒŒ์‹œ๋ฌผ CRUD ๋กœ์ง์„ ๋‹ด๋‹นํ•˜๋Š” ์ปจํŠธ๋กค๋Ÿฌ
const Post = require("../model/post");

// Date๋ฅผ 2021-12-28 ํ˜•์‹์œผ๋กœ ๋ฐ”๊ฟ”์ฃผ๋Š” ํ•จ์ˆ˜.
const formatDate = (date) => {
    let d = new Date(date);
    let month = ("" + (d.getMonth() + 1)).padStart(2, "0");
    let day = ("" + d.getDate()).padStart(2, "0");
    let year = d.getFullYear();
  
    return [year, month, day].join('-');
}

// ๊ฒŒ์‹œ๋ฌผ ์ปจํŠธ๋กค๋Ÿฌ ๊ฐ์ฒด
const postCtr = {
  
    // 1. create : upload func
    upload: async (req, res) => {
        const { title, content } = req.body;
        const image = req.file.location; // image ๋Š” S3์—์„œ ๋ถˆ๋Ÿฌ์˜จ๋‹ค.
        const publishedDate = formatDate(new Date());
        const post = new Post({
            title: title,
            content: content,
            image: image,
            publishedDate: publishedDate,
            user: req.userInfo // ํ•ญ์ƒ jwt ํ† ํฐ์„ ์š”์ฒญ์— ํฌํ•จํ•ด์„œ ๋ณด๋‚ด๊ธฐ ๋•Œ๋ฌธ์— req์— userInfo๊ฐ€ ์žˆ๋‹ค.
        });

        try {
            await post.save();
            res.redirect("/");
        } catch(error) {
            res.status(500).send("upload error!");
        }
    },
  

    // 2. read : ๋ฉ”์ธ ํŽ˜์ด์ง€์—์„œ ๊ธ€ ์ „์ฒด list ์กฐํšŒ ํ•ด์•ผํ•จ.
    list : async (req, res) => {
        const posts = await Post.find({}); // ์ „์ฒด ๊ฒŒ์‹œ๋ฌผ ๊ฐ€์ ธ์˜ฌ๊ฑฐ๋ผ {}๋กœ ์ฐพ๋Š”๋‹ค.
        res.render("index", { postList: posts });
    },
  
    // 2. read : ๊ธ€ ํ•˜๋‚˜ํ•˜๋‚˜๋ฅผ ์กฐํšŒ
    detail: async (req, res) => {
        const { id } = req.params;
        const post = await Post.findById(id);
        res.render("detail", { post : post });
    },
  
    // 3. update : update ํ•  ๋•Œ update ํŽ˜์ด์ง€์— ๊ธฐ์กด ๊ธ€์˜ ์ •๋ณด๊ฐ€ ๋„˜์–ด์˜ค๋„๋ก
    updateLayout: async (req, res) => {
        const {id} = req.params;
        const post = await Post.findById(id);
        res.render("update", { post : post });
    },
  // ์—…๋ฐ์ดํŠธ ๋ฒ„ํŠผ ๋ˆ„๋ฅด๋ฉด ์—…๋ฐ์ดํŠธ ๋จ.
    update: async (req, res) => {
        try {
            const { id } = req.params;
            const { title, content } = req.body;
            await Post.findByIdAndUpdate(
                id, 
                { title, content }, 
                { new: true } // ์—…๋ฐ์ดํŠธ ๋˜๋ฉด ์ƒˆ๋กœ ๋ฐ›์•„์˜ฌ๊ฑด์ง€. ์•ˆํ•˜๋ฉด ๊ธฐ์กด post๋ฅผ ๋ฐ›์•„์˜ค๊ฒŒ ๋จ.
            );
            res.redirect("/");
        } catch(error) {
            res.status(500).send("update error");
        }
    },
  
    // 4. delete
    delete: async (req, res) => {
        try {
            const {id} = req.params;
            await Post.findByIdAndDelete(id);
            res.redirect("/");
        } catch(error) {
            res.status(500).send("delete error");
        }

    }
}

module.exports = postCtr;

3. routes/posts/index.js

// routes/posts/index.js
const express = require("express");
const { route } = require("..");
const router = express.Router();
const upload = require("../../module/multer");
const postCtr = require('../../controller/postCtr');

// 1. create
router.get('/upload', checkUser, (req, res) => {
    res.render('upload');
});

// 2. read 
router.get("/", postCtr.list);  // ๊ฒŒ์‹œ๋ฌผ ์ „์ฒด ์กฐํšŒ.
router.get("/:id", postCtr.detail);  // ๊ฒŒ์‹œ๋ฌผ ํ•˜๋‚˜ ์กฐํšŒ

// 3. update
// web์—์„œ form ํ˜•ํƒœ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•˜๋ฉด get / post ๋‘๊ฐ€์ง€ ๋ฐ–์— ์“ธ ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์—
// update์— PUT์ด ์•„๋‹ˆ๋ผ POST ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์š”์ฒญ ๋ณด๋ƒ„.
router.post('/update:id', postCtr.updateLayout);
router.post('/', upload.single("image"), postCtr.upload);
router.post('/update:id', postCtr.update);

// 4. delete
router.post('/delete/:id', postCtr.delete);

module.exports = router;




3. JWT

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

JWT๋Š” json Web Token์œผ๋กœ json ํ˜•ํƒœ์˜ ํ† ํฐ์„ ์„œ๋ฒ„๊ฐ€ ์•„๋‹ˆ๋ผ ๋ธŒ๋ผ์šฐ์ €(์ฟ ํ‚ค)์— ์ €์žฅํ•ด์„œ ์ธ์ฆํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•œ๋‹ค.

๊ตฌ์กฐ

  • header : ํ† ํฐ์˜ ์œ ํ˜•(JWT), ์•”ํ˜ธํ™” ์•Œ๊ณ ๋ฆฌ์ฆ˜ ํฌํ•จ
  • payload : ํ† ํฐ์— ๋‹ด์„ ์ •๋ณด ํฌํ•จ
  • signature : secret key๋ฅผ ์ด์šฉํ•ด์„œ ์•”ํ˜ธํ™”

ํด๋ผ์ด์–ธํŠธ๊ฐ€ id, pw ๋ฅผ ์„œ๋ฒ„์— ๋ณด๋‚ด๊ณ  ์ธ์ฆ์ด ๋˜์—ˆ๋‹ค๋ฉด, ์„œ๋ฒ„๋Š” ์ž์‹ ์˜ ๋น„๋ฐ€ํ‚ค๋ฅผ ์ด์šฉํ•ด์„œ JWT ํ† ํฐ์„ ์•”ํ˜ธํ™”ํ•ด์„œ ๋ณด๋‚ธ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ํด๋ผ์ด์–ธํŠธ๋Š” JWT๋ฅผ ์ฟ ํ‚ค๋‚˜ ๋ธŒ๋ผ์šฐ์ €์— ์ €์žฅํ•ด๋†“๊ณ , ๋ชจ๋“  ์š”์ฒญ์— JWT ํ† ํฐ์„ ๊ฐ™์ด ๋ณด๋‚ธ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ์„œ๋ฒ„๊ฐ€ ํ† ํฐ์„ ๋ณตํ˜ธํ™”ํ•ด๋ณด๊ณ  ์ •๋ณด๊ฐ€ ๋งž์œผ๋ฉด ๊ทธ์— ๋งž๋Š” ์‘๋‹ต์„ ํ•œ๋‹ค.


1. ๋กœ๊ทธ์ธ

๋กœ๊ทธ์ธ - ํ† ํฐ ๋ฐœ๊ธ‰
์‚ฌ์šฉ์ž ํ™•์ธ - ํ† ํฐ ํ™•์ธ
๋กœ๊ทธ์•„์›ƒ - ํ† ํฐ ์‚ญ์ œ

1. routes/auth/index.js

// routes/auth/index.js
const express = require("express");
const router = express.Router();
const authCtr = require('../../controller/authCtr');

// ํšŒ์›๊ฐ€์ž…
router.get('/register', (req, res) => {
    res.render("register");
});
router.post('/register', authCtr.register);

// ๋กœ๊ทธ์ธ
router.get('/login', authCtr.login);

// ๋กœ๊ทธ์•„์›ƒ
router.post("/logout", (req, res) => {
    res.clearCookie("access_token");
    res.redirect("/");
});

module.exports = router;

2. controller/authCtr.js

// controller/authCtr.js
const bcrypt = require("bcrypt");
const User = require('../model/auth');
const secretKey = require('../config/secretKey.json');
const jwt = require("jsonwebtoken");

const authCtr = {
  
    // ํšŒ์›๊ฐ€์ž…
    register: async (req, res) => {
        const { username, password } = req.body;

        const exist = await User.findOne({ username: username }); // username ๊ณผ ์ผ์น˜ํ•˜๋Š” ํ•ญ๋ชฉ์ด ์žˆ๋Š”์ง€
        // user๊ฐ€ ์žˆ์œผ๋ฉด ํšŒ์›๊ฐ€์ž… ๋ถˆ๊ฐ€๋Šฅ
        if (exist) {
            res.status(504).send("user exists");
            return
        }

        const user = new User({
            username: username
        });
        const hashedPassword = await bcrypt(password, 10); // hash 10๋ฒˆ
        user.password = hashedPassword;
        await user.save();
        res.redirect('/');
    }, 
  
    // ๋กœ๊ทธ์ธ
    login : async (req, res) => {
        const { username, password } = req.body;

      // ๋กœ๊ทธ์ธ์ด๋‹ˆ๊นŒ ์œ ์ €๋ฅผ ์ฐพ์•„์™€์„œ.
        const user = await User.findOne({ username : username });
        if (!user) {
            res.status(500).send("user not found");
            return;
        }

      // ๋‚˜๋Š” === ์œผ๋กœ ๋น„๊ตํ•  ์ƒ๊ฐ์„ ํ–ˆ๋Š”๋ฐ, compare๋ผ๋Š” ๋ฉ”์„œ๋“œ๊ฐ€ ์žˆ์—ˆ๋‹ค. ๊ทผ๋ฐ hash 10๋ฒˆํ–ˆ๋Š”๋ฐ ๊ทธ๊ฒƒ๋„ ๋ฐ˜์˜์ด ๋˜๋Š”๊ฒŒ ์‹ ๊ธฐํ•˜๋‹ค.
        const valid = await bcrypt.compare(password, user.password); 
        if (!valid) {
            res.status(500).send("password invalid");
            return;
        }

        // ์„ฑ๊ณต. JWT ํ† ํฐ ๋ฐœ๊ธ‰ํ•ด์„œ ๋ณด๋‚ธ๋‹ค.
        // secretKey๋„ config์— ์žˆ๋‹ค. ์•”ํ˜ธํ™”ํ•  ํ‚ค ๊ฐ’์ด๋‹ค.
        const data = user.toJSON();
        delete data.password;
        const token = jwt.sign({
            _id: data.id,
            username: data.username
        }, secretKey.key,
        {
            // ์‹ค๋ฌด์—์„œ๋Š” 30๋ถ„ ์ •๋„๋กœ ์งง๊ฒŒ ์ค˜์•ผํ•˜์ง€๋งŒ ํ…Œ์ŠคํŠธ์šฉ์ด๋‹ˆ ๊ธธ๊ฒŒ
            expiresIn: "7d"
        });

        res.cookie("access_token", token, {
            maxAge: 1000 * 60 * 60 *  24 * 7,
            httpOnly: true
        });
        res.redirect("/");
    }
};

2. ์‚ฌ์šฉ์ž ์ •๋ณด ํ™•์ธ

์‚ฌ์šฉ์ž ์ •๋ณด๋Š” CUD์—์„œ ๋ชจ๋‘ ํ™•์ธํ•ด์•ผ ํ•˜๋ฏ€๋กœ, middleware ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค์–ด์„œ ์ด์šฉํ•œ๋‹ค.

// app.js
const jwtMiddleware = require('./module/jwtMiddleware');
// ๋ชจ๋“  ๋™์ž‘์„ ํ•  ๋•Œ ํ† ํฐ์„ ํ™•์ธํ•œ๋‹ค.
app.use(jwtMiddleware);
<!-- navbar.ejs -->
<!-- ๋กœ๊ทธ์ธ์ด ๋˜์–ด ์žˆ์œผ๋ฉด ๋ฒ„ํŠผ์ด ๊ธ€์“ฐ๊ธฐ๋กœ ์•ˆ๋˜์–ด ์žˆ์œผ๋ฉด ๋กœ๊ทธ์ธ์œผ๋กœ ๋ฐ”๋€Œ๊ฒŒ-->
<div class="navbar_menu">
    <div style="font-weight: bolder;font-size:1.5rem;">
        <a style="color:#000000;text-decoration: none;" href="/">SNS</a>
    </div>
    <div class="auth_wrap">
        <% if (isAuthenticated.username) { %>
        <button onclick="location.href='/posts/upload'" style="width: 100%; margin-top: 0" class="ui secondary button">
            ๊ธ€์“ฐ๊ธฐ
        </button>
        <form action="/auth/logout", method="POST">
            <button type="submit" style="margin-top: 0;" class="ui secondary button">๋กœ๊ทธ์•„์›ƒ</button>
        </form>
        <% } else { %>
        <button onclick="location.href='/auth/login'" style="width: 100%; margin-top: 0" class="ui secondary button">
            ๋กœ๊ทธ์ธ
        </button>
        <% } %>
    </div>
</div>
// modlue/jwtMiddleware.js
// ์‚ฌ์šฉ์ž ํ™•์ธ ๊ธฐ๋Šฅ
const  jwt = require("jsonnwebtoken");
const secretKey = require("../config/secretKey.json");

const jwtMiddlware = async (req, res, next) => {
    const token = req.cookies.access_token;
    if(!token) {
        // isAuthenticated ์— ๋นˆ ๊ฐ์ฒด๋ฅผ ๋„˜๊น€ (ํ† ํฐ์ด ์—†์œผ๋‹ˆ๊นŒ)
        res.locals.isAuthenticated = {};
        return next();
    }

    try {
        // ํ† ํฐ์ด ์žˆ์œผ๋ฉด ๊ฒ€์ฆ.
        const decoded = jwt.verify(token, secretKey.key);
        req.userInfo = {
            _id: decoded.id,
            username: decoded.username
        };
        res.locals.isAuthenticated = { username : decoded.username };
        return next();
    } catch(error) {
        res.status(500).send("jwt error");
    }
}

์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ํ™•์ธํ•œ ํ›„์—๋งŒ ๊ธ€์“ฐ๊ธฐ๋ฅผ ํ•  ์ˆ˜ ์žˆ๊ฒŒ.

// module/checkUser.js
// posts/index.js ์˜ ๊ธ€์“ฐ๊ธฐ, ์ˆ˜์ •, ์‚ญ์ œ ์— middleware๋กœ checkUser ๋ถ™์ด๊ธฐ.
const checkUser = (req, res, next) => {
    if (!req.userInfo) {
        res.status(400).send("user not login!");
        return;
    }
    return next();
}

module.exports = checkUser;




4. ์ข‹์•„์š”, ๋Œ“๊ธ€

1. ์ข‹์•„์š”

// index.ejs
// ํ•˜ํŠธ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด, /posts/like/${id} ๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด๊ณ ,
// ์„ฑ๊ณต์ธ ๊ฒฝ์šฐ ๋ฐ›์€ ์‘๋‹ต์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜ํŠธ๊ฐ€ ์ฑ„์›Œ์ง€๊ณ  ์•ˆ์ฑ„์›Œ์ง€๊ฒŒ.
$(".heart-count").click(function (e) {
  const id = $(this).attr("key");
  $.ajax({
    url: `/posts/like/${id}`,
    type: "POST",
    cache: false,
    dataType: "json",
    success: function (data) {
      $(`#${id} > span`).text(data.post.likeCount);
      if (!data.check) {
        $(`#${id}`).addClass("checked");
      } else {
        $(`#${id}`).removeClass("checked");
      }
    },
    error: function (request, status, error) {
      alert(
        error
      );
    },
  });
});
// routes/posts/index.js
// ์ข‹์•„์š”
router.post('/like/:id', postCtr.like);
// controller/postCtr.js/postCtr
    like: async (req, res) => {
        const { id } = req.params;
        const post = await Post.findById(id);
        // jwt ์ƒ์„ฑ์‹œ req.userInfo ๋ฅผ ๋„ฃ์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ์‚ฌ์šฉ ๊ฐ€๋Šฅ.
        const check = post.likeUser.some(userId => userId === req.userInfo._id);
        if (check) {
            post.likeCount -= 1;
            const idx = post.likeUser.indexOf(req.userInfo._id);
            if (idx > -1) {
                post.likeUser.splice(idx, 1);
            }
        } else {
            post.likeCount += 1;
            post.likeUser.push(req.userInfo._id);
        }
        const result = await post.save();
        res.status(200).json({
            check: check,
            post: result
        });
    }

### 2. ๋Œ“๊ธ€
// index.ejs
// ๋Œ“๊ธ€ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ๋Œ“๊ธ€ ์ฐฝ์ด ๋‚ด๋ ค์˜ค๊ฒŒ.
$(".comment_button").click(function (e) {
  const id = $(this).attr("key");
  $(`#comment_wrap_${id}`).slideToggle();
});
// ๋Œ“๊ธ€์ฐฝ์— ์ž…๋ ฅํ•˜๊ณ  ์—”ํ„ฐ๋ฅผ ๋ˆ„๋ฅด๋ฉด /posts/comment/${id}๋กœ ์ ‘๊ทผํ•˜์—ฌ
// ์‘๋‹ต์„ ๋ฐ›๊ณ  ํ•ด๋‹น ์ •๋ณด๋ฅผ ํ™”๋ฉด์— ๋ณด์—ฌ์ฃผ๊ฒŒ ๋œ๋‹ค.
$(".comment_input").keydown(function (e) {
  if (e.keyCode === 13) {
    e.preventDefault();
    const id = $(this).attr("key");
    const inputCommnet = $(this).val();
    $(this).val("");
    $.ajax({
      url: `/posts/comment/${id}`,
      type: "POST",
      cache: false,
      dataType: "json",
      data: {
        comment: inputCommnet,
      },
      success: function (data) {
        const comment = data.post.comment;
        let comment_body = "";
        const arr = comment.reverse();
        arr.forEach((e) => {
          console.log("EEE");
          comment_body += `
        <div class="comment_body_item">
          <div style="font-weight: bolder;">
            ${e.user.username}
          </div>
          <div>
            ${e.comment} 
          </div>
        </div>
        `;
        });
        $(`#comment_${id}`).html(comment_body);
      },
      error: function (request, status, error) {
        alert(
          error
        );
      },
    });
  }
});
// routes/posts/index.js
// ๋Œ“๊ธ€
router.post('comment/:id', checkUser, postCtr.comment);
// controller/postCtr.js
    comment: async (req, res) => {
        const { id } = req.params;
        const post = await Post.findById(id);
        const user = req.userInfo;
        const { comment } = req.body;
        const commentWrap = {
            comment: comment,
            user: user
        };

        post.comment.push(commentWrap);
        const result = await post.save();
        res.status(200).json({post: result});
    },




5. ์‹ค์‹œ๊ฐ„ ์ฑ„ํŒ…

๊ธฐ์กด์— ํ•˜๋˜ ๋ฐฉ๋ฒ•์œผ๋กœ๋Š” ์‹ค์‹œ๊ฐ„ ์ฑ„ํŒ…์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์—†๋‹ค. ๊ธฐ์กด์˜ ๋ฐฉ์‹์€ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์š”์ฒญ์„ ๋ณด๋‚ด์•ผ๋งŒ ์„œ๋ฒ„๊ฐ€ ์‘๋‹ต์„ ํ•ด์ฃผ๊ธฐ ๋•Œ๋ฌธ! ๋ˆ„๊ตฐ๊ฐ€ ์ฑ„ํŒ…์„ ๋ณด๋‚ด๋ฉด ์„œ๋ฒ„๊ฐ€ ๋ชจ๋“  ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์ฑ„ํŒ… ๋‚ด์šฉ์„ ์‘๋‹ตํ•ด์•ผํ•˜๋Š”๋ฐ, ๋‹ค๋ฅธ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์š”์ฒญ์„ ๋ณด๋‚ด์ง€๋„ ์•Š์•˜๋Š”๋ฐ ์‘๋‹ต? ๋ถˆ๊ฐ€๋Šฅ.

-> WebSocket์ด ๋“ฑ์žฅํ•˜๋ฉด์„œ ๊ฐ€๋Šฅํ•ด์กŒ๋‹ค.

socket.io : WebSocket์„ ์‰ฝ๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ๊ฒŒ ๋งŒ๋“ค์–ด์คŒ.

emit, on๋งŒ ์•Œ๋ฉด ์‰ฝ๊ฒŒ ์ฑ„ํŒ…์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค. emit์œผ๋กœ ๋ณด๋‚ด๊ณ , on์œผ๋กœ ๋ฐ›๊ณ .

// index.ejs
$(() => {
  // ์ธ์ฆ๋ฐ›์€ ์ •๋ณด๋กœ ์ด๋ฆ„ ์„ค์ •.
  const name = "<%= isAuthenticated.username %>";
  const socket = io(); 
  
  // ์ฑ„ํŒ… ํผ์„ ์ œ์ถœํ–ˆ์„ ๋•Œ, chat-msg๋ฅผ emit์œผ๋กœ ์„œ๋ฒ„์— ๋ณด๋ƒ„.
  $("#chat_form").submit(() => {
    socket.emit("chat-msg", name, $(".chat_input").val());
    $(".chat_input").val("");
    return false;
  });
  
  // chat-msg๋ฅผ on์œผ๋กœ ๋‹ค์‹œ ๋ฐ›์•„์„œ ํ™”๋ฉด์— ๋ณด์ด๊ฒŒ ์ฒ˜๋ฆฌ
  socket.on("chat-msg", (name, msg) => {
    let chat = `<div class="chat_block">
    <span style="font-weight: bolder">${name}</span> :
      ${msg}
    </div>`;
    $(".chat_content").append(chat);
  });
});
// bin/www
app.io.attach(server);

// app.js
app.io = require("socket.io");

    // server์—์„œ๋„ on์œผ๋กœ ๋ฐ›๋Š”๋‹ค.
app.io.on("connection", (socket) => {
  socket.on("chat-msg", (user, msg) => {
    app.io.emit("chat-msg", user, msg);
  });
});




์ž‘์€ ํšŒ๊ณ 

1. ์˜ค๋Š˜ ์ •๋ง ๊ณต๋ถ€๊ฐ€ ์•ˆ๋๋‹ค... ๊ทธ๋ž˜๋„ ๋ชฉํ‘œํ–ˆ๋˜ ๊ฐ•์˜๋ผ๋„ ๋‹ค ๋“ค์–ด์„œ ๋‹คํ–‰์ธ๊ฑด์ง€..

์˜ค๋Š˜์€ ๋นจ๋ฆฌ ์ž์•ผ๊ฒ ๋‹ค.

2. node.js sns ์„œ๋ฒ„ ๋งŒ๋“ค๊ธฐ ๊ฐ•์˜๋ฅผ ๋“ค์—ˆ๋‹ค.

node๊ฐ€ ๋ฐฑ์—”๋“œ ์ค‘์—” ๊ฐ€์žฅ ์‰ฝ๋‹ค๊ณ  ํ•˜๋˜๋ฐ, ๊ทธ๋ž˜์„œ ๊ทธ๋Ÿฐ์ง€ ๊ฒ๋ƒˆ๋˜ ๊ฒƒ ๋ณด๋‹ค๋Š” ์ •๋ง ํ• ๋งŒํ–ˆ๋‹ค. ๋ฌผ๋ก  ํ˜ผ์ž ํ•˜๋ผ๊ณ  ํ•˜๋ฉด..? ํ•  ์ˆ˜ ์žˆ์„์ง€๋Š” ์ž˜ ๋ชจ๋ฅด๊ฒ ๋‹ค.

์–ด๋–ค ๊ฑธ ์„œ๋ฒ„๋กœ ๋ณด๋‚ด๊ณ  ์–ด๋–ค ๊ฑธ ๋ฐ›์„ ๊ฒƒ์ด๋ผ req res์— ๋“ค์–ด๊ฐ€๋Š” ์ •๋ณด๊ฐ€ ๋ฌด์—‡์ธ์ง€ ๋ฐ”๋กœ๋ฐ”๋กœ ํŒŒ์•…ํ•˜๋Š” ๊ฒƒ๋„ ๋Šฅ์ˆ™ํ•ด์ ธ์•ผ ๊ฐ€๋Šฅํ•  ๊ฒƒ ๊ฐ™๋‹ค. ํ† ํฐ ์ƒ์„ฑํ•  ๋•Œ req.userInfo ๋„ฃ์–ด๋†“๊ณ  ๋‚˜์ค‘์— ์ด๊ฑธ ์–ด๋””์„œ ๋„ฃ์—ˆ๋”๋ผ.....??? ํ•˜๋ฉด์„œ ํ—ท๊ฐˆ๋ ธ๋‹ค.

๋„คํŠธ์›Œํฌ ์—ฐ๊ฒฐ ๋ถ€๋ถ„์ด ๊ฐ๋„ ์ž˜ ์•ˆ์˜ค๊ณ  swift์˜ ๊ฒฝ์šฐ๋Š” ๋‚ด ์ƒํ™ฉ์— ๋งž๋Š” ์˜ˆ์ œ ์ฝ”๋“œ๋„ ์ฐพ๊ธฐ๊ฐ€ ํž˜๋“ค์—ˆ๋Š”๋ฐ, ๋ฐฑ์—”๋“œ๋ฅผ ๋งŒ๋“ค๋ฉด์„œ ์‘๋‹ต์„ ๋งŒ๋“ค์–ด๋ณด๋‹ˆ๊นŒ ์ข€ ๊ฐ์ด ์˜จ๋‹ค. ๋ฉ‹์‚ฌ ๊ต์œก๊ณผ์ • js์—์„œ ์ฝœ๋ฐฑํ•จ์ˆ˜, Promise, async await, closure์— ๋Œ€ํ•ด์„œ ์ œ๋Œ€๋กœ ๋‹ค๋ฃจ๊ณ , ์—ฌ๊ธฐ์„œ ์‚ฌ์šฉ๋„ ํ•ด๋ณด๋‹ˆ๊นŒ ์ด์ œ ๋‚ด ์•ฑ์ด ์™œ ์„œ๋ฒ„์˜ ์‘๋‹ต๋„ ๋ฐ›๊ธฐ ์ „์— ํ˜ผ์ž ์‹คํ–‰์ด ๋ผ์„œ ์˜ค๋ฅ˜๊ฐ€ ๊ณ„์† ๋‚ฌ๋Š”์ง€ ์•Œ๊ฒ ๋‹ค.. async await๋ฅผ ์ž˜ ์‚ฌ์šฉํ•ด๋ณด์ž.




์ถœ์ฒ˜
์ฝ”๋“œ๋ผ์ด์–ธ ์ผ๋‹จ ๋งŒ๋“œ๋Š” node.js - ์ด๋™ํ›ˆ ๊ฐ•์‚ฌ๋‹˜

profile
0๋…„์ฐจ iOS ๊ฐœ๋ฐœ์ž์ž…๋‹ˆ๋‹ค.

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

comment-user-thumbnail
2021๋…„ 12์›” 29์ผ

์šฐ์˜ค์•™ ๋…ธ๋“œ ๊ณต๋ถ€ ์งฑ์—ด์‹ฌํžˆ ํ•˜์…จ๋„ค์š”!! ๋ฐ˜์„ฑํ•˜๊ตฌ ์ €๋„ ์ˆ˜์—… ๋“ค์œผ๋Ÿฌ ๊ฐ‘๋‹ˆ๋‹ค,,

๋‹ต๊ธ€ ๋‹ฌ๊ธฐ