elice/WIL

[week_07] Express.js์™€ MongoDB๋กœ ์›น์„œ๋น„์Šค ๋งŒ๋“ค๊ธฐ (2) | Hash, Session, ๋กœ๊ทธ์ธ/ํšŒ์›๊ฐ€์ž…

๊ฑด๋ง๋”” 2022. 2. 23. 19:23

1. Hash

  • ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ €์žฅํ•  ๋•Œ ์‚ฌ์šฉ
  • ๋ฌธ์ž์—ด์„ ๋˜๋Œ๋ฆด ์ˆ˜ ์—†๋Š” ๋ฐฉ์‹์œผ๋กœ ์•”ํ˜ธํ™”ํ•˜๋Š” ๋ฐฉ๋ฒ•
  • hash ์ถœ๋ ฅ๊ฐ’์„ ์ด์šฉํ•ด์„œ ์‚ฌ์šฉ์ž์˜ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์•Œ์•„๋‚ผ ์ˆ˜ ์žˆ๋‹ค.
๋น„๋ฐ€๋ฒˆํ˜ธ์˜ Hash ๊ฐ’์„ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅํ•˜๊ณ , ๋กœ๊ทธ์ธ ์‹œ ์ „๋‹ฌ๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ Hashํ•˜์—ฌ ์ €์žฅ๋œ ๊ฐ’๊ณผ ๋น„๊ตํ•ด์„œ ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ

1-1. Node.js์—์„œ์˜ Hash ์‚ฌ์šฉ๋ฒ•

  • crypto ๋ชจ๋“ˆ์„ ์‚ฌ์šฉํ•ด์„œ hash ๊ฐ’์„ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค.
const hash = crypto.createHash('sha1'); // ํŠน์ • ํ•ด์‰ฌ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์„ค์ •
hash.update(password); // ์ž…๋ ฅ๋ฐ›์€ ํ‰๋ฌธ put
hash.digest('hex'); // 16์ง„์ˆ˜ ๋ฌธ์ž์—ด๋กœ ์ถœ๋ ฅ
  • ๊ฐ„๋‹จํ•˜๊ฒŒ sha1 ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜, ๋ณด๋‹ค ๊ฐ•๋ ฅํ•œ sha224, sha256 ๋“ฑ์„ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์‚ฌ์šฉ ๊ฐ€๋Šฅ

 

2. ํšŒ์›๊ฐ€์ž… ์š”์ฒญ ์ฒ˜๋ฆฌํ•˜๊ธฐ

router.post(... => {
	const { email, name, password } = req.body;
    const pwHash = getHash(password);
    const exist = await User.findOne({ email, });
    
    if (exist) {
    	throw new Error('์ด๋ฏธ ๊ฐ€์ž…๋œ ๋ฉ”์ผ์ž…๋‹ˆ๋‹ค.');
    }
    
    await User.create({
    	email,
        name,
        password: pwHash,
    });
    
    res.redirect('/');
});
  • ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ hash๊ฐ’์œผ๋กœ ์ €์žฅ
  • ์ด๋ฏธ ์กด์žฌํ•˜๋Š” ํšŒ์›์ธ์ง€ ์ฒดํฌ
  • ๊ฐ€์ž… ์„ฑ๊ณต ์‹œ ๋ฉ”์ธํ™”๋ฉด์œผ๋กœ redirect

 

3. Passport.js

  • ์œ ์ € ์„ธ์…˜ ๊ด€๋ฆฌ ๋ฐ ๋‹ค์–‘ํ•œ ๋กœ๊ทธ์ธ ๋ฐฉ์‹ ์ถ”๊ฐ€ ๊ฐ€๋Šฅ

3-1. passport-local

  • passport๋Š” ๋‹ค์–‘ํ•œ ๋กœ๊ทธ์ธ ๋ฐฉ์‹ ๊ตฌํ˜„์„ ์œ„ํ•ด strategy๋ผ๋Š” ์ธํ„ฐํŽ˜์ด์Šค ์ œ๊ณต
  • strategy ์ธํ„ฐํŽ˜์ด์Šค์— ๋งž๊ฒŒ ์„ค๊ณ„๋œ ๋‹ค์–‘ํ•œ ๊ตฌํ˜„์ฒด๋“ค์ด ์žˆ์Œ (facebook, google, ...)
  • passport-local์€ username, password๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋กœ๊ทธ์ธ์˜ ๊ตฌํ˜„์ฒด

1) ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ํ•˜๊ธฐ: passport-local strategy

/* passport-local */
const config = { usernameField: 'email', passwordField: 'password' };
// ์•„์ด๋”” ํŒจ์Šค์›Œ๋“œ ํ•„๋“œ ์„ค์ • ํ•„์ˆ˜

const local = new LocalStrategy(config, ..., async (email, password, done) => {
	try {
    	const user = await User.findOne({ email });
    	if (!user) { // email๋กœ ํšŒ์›์ด ์ฐพ์•„์ง€์ง€ ์•Š์„ ๊ฒฝ์šฐ
        	throw new Error('ํšŒ์›์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.');
        }
        if (user.password !== password) { // ํŒจ์Šค์›Œ๋“œ์˜ ํ•ด์‰ฌ๊ฐ’์„ ๋งŒ๋“ค์–ด์„œ ์‚ฌ์šฉํ•  ๊ฒƒ!
        	throw new Error('๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.');
        }
        
        // ์„ธ์…˜์— ์ €์žฅ๋˜๋Š” ์œ ์ € ์ •๋ณด์˜ ์ตœ์†Œํ™”
        done (null, {
        	shortId: user.shortId,
            email: user.email,
            name: user.name,
        });
    } catch(err) {
    	done(err, null);
    }
});



/* passport use */
const local = require('./strategies/local');
passport.use(local);
  • ์ž‘์„ฑํ•œ strategy๋ฅผ passport.use๋ฅผ ์ด์šฉํ•ด ์‚ฌ์šฉํ•˜๋„๋ก ์„ ์–ธํ•ด์•ผ ํ•œ๋‹ค.
  • passport.use๋ฅผ ์ด์šฉํ•ด strategy ๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ์„ ์–ธํ•œ ํ›„, passport.authenticate๋ฅผ ์‚ฌ์šฉํ•ด ํ•ด๋‹น strategy๋ฅผ ์ด์šฉํ•ด ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

2) ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ํ•˜๊ธฐ: Passport.js๋กœ post ์š”์ฒญ ์ฒ˜๋ฆฌํ•˜๊ธฐ

/* routes/auth.js */
router.post('/', passport.authenticate('local');


/* app.js */
const session = require('express-session');

app.use(session({
	secret: 'secret',
    resave: false,
    saveUninitialized: true
}));
app.use(passport.initialize());
app.use(passport.session());
app.use('/auth', authRouter);
  • passport.authenticate ํ•จ์ˆ˜๋ฅผ http ๋ผ์šฐํŒ…์— ์—ฐ๊ฒฐํ•˜๋ฉด passport๊ฐ€ ์ž๋™์œผ๋กœ ํ•ด๋‹นํ•˜๋Š” strategy๋ฅผ ์‚ฌ์šฉํ•˜๋Š” request handler๋ฅผ ์ƒ์„ฑ
  • express-session๊ณผ passport.session์„ ์‚ฌ์šฉํ•˜๋ฉด passport๊ฐ€ ๋กœ๊ทธ์ธ ์‹œ ์œ ์ € ์ •๋ณด๋ฅผ ์„ธ์…˜์— ์ €์žฅํ•˜๊ณ  ๊ฐ€์ ธ์˜ค๋Š” ๋™์ž‘์„ ์ž๋™์œผ๋กœ ์ˆ˜ํ–‰ํ•ด์ค€๋‹ค.

3) ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ํ•˜๊ธฐ: session ์œ ์ € ํ™œ์šฉํ•˜๊ธฐ

passport.serializeUser((user, callback) => {
	callback(null, user);
});

passport.deserializeUser((obj, callback) => {
	callback(null, obj);
});
  • ์„ธ์…˜์— user ์ •๋ณด๋ฅผ ๋ณ€ํ™˜ํ•˜์—ฌ ์ €์žฅํ•˜๊ณ  ๊ฐ€์ ธ์˜ค๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณต

4) ๋กœ๊ทธ์•„์›ƒ

router.get('/logout', ... {
	req.logout();
    res.redirect('/');
});

5) ๋กœ๊ทธ์ธ ํ™•์ธ ๋ฏธ๋“ค์›จ์–ด

function loginRequired(req, res, next) {
	if(!req.user) {
    	res.redirect('/');
        return;
    }
    next();
}

app.use('/posts', loginRequired, postsRouter);
  • ์–ด๋–ค ํŽ˜์ด์ง€ ํ˜น์€ ๊ธฐ๋Šฅ์—์„œ ๋กœ๊ทธ์ธ์„ ํ•„์ˆ˜๋กœ ์„ค์ •ํ•˜๊ณ  ์‹ถ์„ ๊ฒฝ์šฐ, ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ฒดํฌ

 

4. Session

  • ์›น ์„œ๋ฒ„๊ฐ€ ํด๋ผ์ด์–ธํŠธ ์ •๋ณด๋ฅผ ํด๋ผ์ด์–ธํŠธ๋ณ„๋กœ ๊ตฌ๋ถ„ํ•˜์—ฌ ์„œ๋ฒ„์— ์ €์žฅํ•˜๊ณ , ํด๋ผ์ด์–ธํŠธ ์š”์ฒญ ์‹œ Session ID๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํด๋ผ์ด์–ธํŠธ์˜ ์ •๋ณด๋ฅผ ๋‹ค์‹œ ํ™•์ธํ•˜๋Š” ๊ธฐ์ˆ 
  • ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ •๋ณด๋ฅผ ์ €์žฅํ•˜๊ณ , ์š”์ฒญ ์‹œ ์ •๋ณด๋ฅผ ๋ณด๋‚ด๋Š” Cookie์™€ ๋Œ€์กฐ๋จ
์„œ๋ฒ„๋Š” ์„ธ์…˜์„ ์ƒ์„ฑ โžก Session ID๋ฅผ ํด๋ผ์ด์–ธํŠธ์— ์ „๋‹ฌ โžก ํด๋ผ์ด์–ธํŠธ๋Š” ์š”์ฒญ ์‹œ Session ID๋ฅผ ํ•จ๊ป˜ ์š”์ฒญ์— ๋‹ด์•„์„œ ์ „์†ก โžก ์„œ๋ฒ„๊ฐ€ ์ „๋‹ฌ๋ฐ›์€ Session ID๋กœ ํ•ด๋‹นํ•˜๋Š” ์„ธ์…˜์„ ์ฐพ์•„ ํด๋ผ์ด์–ธํŠธ ์ •๋ณด ํ™•์ธ
  • express-session ํŒจํ‚ค์ง€๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ž๋™์œผ๋กœ session ๋™์ž‘ ๊ตฌํ˜„ ๊ฐ€๋Šฅ

4-1. Session Store

  • express-session ํŒจํ‚ค์ง€๋Š” session์„ ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ฉ”๋ชจ๋ฆฌ์— ์ €์žฅ
    • ํ˜„์žฌ ๊ตฌํ˜„๋œ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ข…๋ฃŒ ํ›„ ๋‹ค์‹œ ์‹คํ–‰ํ•˜๋ฉด, ๋ชจ๋“  ์œ ์ €์˜ ๋กœ๊ทธ์ธ์ด ํ•ด์ œ๋จ
    • ํ˜น์€ ์„œ๋ฒ„๊ฐ€ ์—ฌ๋Ÿฌ ๋Œ€๊ฐ€ ์žˆ์„ ๊ฒฝ์šฐ, ์„œ๋ฒ„ ๊ฐ„ ์„ธ์…˜ ์ •๋ณด๋ฅผ ๊ณต์œ ํ•  ์ˆ˜ ์—†์Œ
  • ๊ทธ๋ž˜์„œ Session Store๋ฅผ ๊ตฌํ˜„ํ•˜์—ฌ Express Application๋“ค์„ ์—ฐ๊ฒฐํ•˜๋Š” ๋ฐฉ์‹์„ ํ™œ์šฉํ•ด์„œ ์œ„์˜ ๋ฌธ์ œ์  ํ•ด๊ฒฐ ๊ฐ€๋Šฅ
  • connect-mongo ํŒจํ‚ค์ง€๋ฅผ ์ด์šฉํ•ด MongoDB๋ฅผ session store๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์ž๋™์œผ๋กœ session ๊ฐ’์ด ๋ณ€๊ฒฝ๋  ๋•Œ update๋˜๊ณ , session์ด ํ˜ธ์ถœ๋  ๋•Œ find
const MongoStore = require('connect-mongo');

app.use(session({
	secret: 'SeCrEt',
    resave: false,
    saveUninitialized: true,
    store: MongoStore.create({
    	mongoUrl: 'mongoUrl',
    }),
}));
  • connect-mongo ํŒจํ‚ค์ง€๋ฅผ ์‚ฌ์šฉํ•ด exrpess-session ์„ค์ • ์‹œ store ์˜ต์…˜์— ์ „๋‹ฌํ•˜๊ณ , mongoUrl์„ ์„ค์ •
  • ์„ธ์…˜๋ฐ์ดํ„ฐ๋ฅผ ๋ชฝ๊ณ ๋””๋น„์— ์ €์žฅํ•˜๊ณ  ๊ด€๋ฆฌํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ž๋™์œผ๋กœ ์ˆ˜ํ–‰

 

5. ํšŒ์›๊ณผ ๊ฒŒ์‹œ๊ธ€์˜ ์—ฐ๋™

  • ๊ฒŒ์‹œ๊ธ€ ์ž‘์„ฑ ์‹œ ๋กœ๊ทธ์ธ๋œ ํšŒ์› ์ •๋ณด๋ฅผ ์ž‘์„ฑ์ž๋กœ ์ถ”๊ฐ€
  • ๊ฒŒ์‹œ๊ธ€ - ์ž‘์„ฑ์ž๋Š” populateํ•˜์—ฌ ์‚ฌ์šฉํ•˜๋„๋ก ๊ตฌํ˜„
  • ๊ฒŒ์‹œ๊ธ€ ์ˆ˜์ •, ์‚ญ์ œ ์‹œ ๋กœ๊ทธ์ธ๋œ ์œ ์ €์™€ ์ž‘์„ฑ์ž๊ฐ€ ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธ
  • ์ž‘์„ฑ์ž์˜ ๊ฒŒ์‹œ๊ธ€ ๋ชจ์•„๋ณด๊ธฐ ๊ธฐ๋Šฅ ๊ตฌํ˜„
/* PostSchema ์ˆ˜์ • */
author: {
	type: Schema.Types.ObjectId,
    ref: 'User'.
    required: true
},
/* ๊ฒŒ์‹œ๊ธ€์— ์ž‘์„ฑ์ž ์ถ”๊ฐ€ */
const author = await User.find({
	shortId: req.user.shortId,
});
if(!author) {
	throw new Error('No User');
}

await Post.create({
	title,
    content,
    author,
});
  • req.user์—๋Š” strategy์—์„œ ์ตœ์†Œํ•œ์˜ ์ •๋ณด๋กœ ์ €์žฅํ•œ shortId, email, usernam๋งŒ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.
  • Post ์ƒ์„ฑ ์‹œ user์˜ ObjectID๋ฅผ ์ „๋‹ฌํ•ด์•ผ ํ•˜๋Š”๋ฐ, ์ด๋ฅผ ์œ„ํ•ด User์—์„œ shortId๋กœ ํšŒ์›์„ ๊ฒ€์ƒ‰ํ•˜์—ฌ ํ•œ๋ฒˆ ๋” ๊ฒ€์ฆ
  • type: ObjectID๋กœ ์„ ์–ธํ•œ ํ•„๋“œ์— ๊ฐ์ฒด๊ฐ€ ์ฃผ์–ด์ง€๋ฉด ์ž๋™์œผ๋กœ ObjectID ์‚ฌ์šฉ
/* ./routes/posts.js */
router.get('/', ... {
	...
    const posts = await Post.find({})
    	...
    	.populate('author');
    res.render('posts/list', {posts});
}

/* ./views/posts/list.pug */
...
  td post.author.name
  • ๊ฒŒ์‹œ๊ธ€ find ์‹œ populate๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ObjectID๋กœ ์ €์žฅ๋œ author๋ฅผ ๊ฐ ๊ฒŒ์‹œ๊ธ€์— ์ฃผ์ž…
  • ์‚ฌ์šฉ ์‹œ post.author.{field}๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
/* ์ˆ˜์ •, ์‚ญ์ œ ์‹œ ์œ ์ € ํ™•์ธ */
const post = await Post.find({
	shortId,
}).populate('author');

if (post.author.shortId !== req.user.shortId) {
	throw new Error('Not Authorized');
}
/* index ์‚ฌ์šฉ */
author: {
	type: Schema.Types.ObjectId,
    ref: 'User'.
    required: true,
    index: true,
},
  • index: true => MongoDB์— ์ธ๋ฑ์Šค ์ƒ์„ฑ
  • ์ด๋ฏธ ๋ฐ์ดํ„ฐ๊ฐ€ ๋งŽ์€ ์ƒํƒœ์—์„œ ์ธ๋ฑ์Šค๋ฅผ ์ถ”๊ฐ€ํ•  ์‹œ ์ž‘์—… ์‹œ๊ฐ„์ด ๊ธธ์–ด์ ธ์„œ MongoDB๊ฐ€ ์‘๋‹ตํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ์˜ˆ์ƒ๋˜๋Š” ์ธ๋ฑ์Šค๋ฅผ ๋ฏธ๋ฆฌ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Œ
/* ./routes/users.js */
...
router.get('/:shortId/posts', ... => {
	...
    const { shortId } = req.params;
    const user = await User.find({ shortId });
    const posts = await Post
    					.find({ author: user })
                        .populate('author');
    res.render('posts/list', { posts, user });
});
...
/* ๊ฒŒ์‹œ๊ธ€ ๋ชฉ๋กํ™”๋ฉด */
h2= user ? `${user.name}์˜ ๊ฒŒ์‹œ๊ธ€`: "์ „์ฒด ๊ฒŒ์‹œ๊ธ€"
...

td: a(href=`/users/${post.author.shortId}/posts`)= post.author.name
  • ๊ฒŒ์‹œ๊ธ€์˜ ์‚ฌ์šฉ์ž ์ด๋ฆ„์— ์œ ์ €์˜ ๊ฒŒ์‹œ๊ธ€ link ์ถ”๊ฐ€

 

6. CSR๋กœ ๋Œ“๊ธ€ ๊ตฌํ˜„ํ•˜๊ธฐ

  • ํŽ˜์ด์ง€ ๋กœ๋“œ ์‹œ ํ•„์š”ํ•œ ๋ฆฌ์†Œ์Šค๋ฅผ ํด๋ผ์ด์–ธํŠธ์— ์„ ์–ธ
  • ํด๋ผ์ด์–ธํŠธ์—์„œ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋น„๋™๊ธฐ ํ˜ธ์ถœ
  • ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ „๋‹ฌ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€๊ณต, ๋ฆฌ์†Œ์Šค๋ฅผ ์‚ฌ์šฉํ•ด์„œ ํ™”๋ฉด์— ํ‘œ์‹œ

1) ๊ฒŒ์‹œ๊ธ€์— ๋Œ“๊ธ€ ์ถ”๊ฐ€ํ•˜๊ธฐ

/* PostSchema */
const CommenSchema = new Schema({
	content: String,
    author: {
    	type: Schema.Types.ObjectId,
        ref: 'User',
    },
}, {
	timestamps: true,
});

const PostSchema = new Schema({
	...
    comments: [CommentSchema],
});
  • mongoose์˜ sub-schema๋ฅผ ์ด์šฉํ•˜์—ฌ Post ์Šคํ‚ค๋งˆ์— Comment๋ฅผ ๋ฐฐ์—ด๋กœ ์ถ”๊ฐ€
  • populate๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ, ObjectID๋งŒ ์ €์žฅํ•˜๋Š” ๊ฒƒ๊ณผ๋Š” ๋‹ค๋ฅด๊ฒŒ Comment์˜ ๋‚ด์šฉ์„ ๊ฒŒ์‹œ๊ธ€์ด ํฌํ•จํ•˜๊ฒŒ ๋จ

2) API ์ž‘์„ฑํ•˜๊ธฐ - ๋Œ“๊ธ€ ์ž‘์„ฑ

/* routes/api.js */
router.post('/posts/:shortId/comments', ... {
	const { shortId } = req.params;
    const { content } = req.body;
    const author = await User.findOne({ shortId: req.user.shortId });
    
    await Post.updateOne({ shortId }, {
    	$push: { comments: {
        	content, author
        } },
    });
    
    res.json({ result: 'success' });
});
  • ๊ฒŒ์‹œ๊ธ€ ์—…๋ฐ์ดํŠธ ์‹œ $push๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ comments ๋ฐฐ์—ด์— ์ƒˆ๋กœ ์ž‘์„ฑ๋œ ๋Œ“๊ธ€ ์ถ”๊ฐ€
    • ๋™์‹œ์— ๋“ค์–ด์˜ค๋Š” ์š”์ฒญ์— ๋Œ€ํ•ด ์ •ํ™•ํ•˜๊ฒŒ ์ฒ˜๋ฆฌ

3) API ์ž‘์„ฑํ•˜๊ธฐ - ๋Œ“๊ธ€ ๋ชฉ๋ก

/* routes/api.js */
router.get('/posts/:shortId/comments', ... {
	const { shortId } = req.params;
    const post = await Post.findOne({ shortId });
    
    await User.populate(post.comments, {
    	path: 'author'
    });
    
    res.json(post.comments);
});

4) fetch๋กœ API ํ˜ธ์ถœํ•˜๊ณ  ์ฒ˜๋ฆฌํ•˜๊ธฐ - ๋Œ“๊ธ€ ์ž‘์„ฑํ•˜๊ธฐ

script
  function writeComment() {
    const input = document.querySelector('#content')
    const content = input.value;
    fetch('/api/posts/#{post.shortId}/comments', {
      method: 'post',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ content }),
    })
    .then(() => {
      if(res.ok) {
        input.value = '';
        loadComments();
      } else {
        alert('ERROR');
      }
    });
  }
  • ๋Œ“๊ธ€ ์ž‘์„ฑ ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ writeComment() ์‹คํ–‰
  • input#content์—์„œ ๋‚ด์šฉ์„ ์ฝ์–ด fetch๋กœ ๋Œ“๊ธ€ ์ž‘์„ฑ api ํ˜ธ์ถœ
  • ํ˜ธ์ถœ ๊ฒฐ๊ณผ์˜ ์„ฑ๊ณต ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•˜์—ฌ, ๋Œ“๊ธ€ ๋‹ค์‹œ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ ์‹คํ–‰

 

7. MongoDB Aggregation

  • MongoDB์—์„œ Document๋“ค์„ ๊ฐ€๊ณตํ•˜๊ณ  ์—ฐ์‚ฐํ•˜๋Š” ๊ธฐ๋Šฅ
  • ๋‹ค๋ฅธ Collection์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ฑฐ๋‚˜, ๊ฒ€์ƒ‰๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ทธ๋ฃนํ™”ํ•˜๋Š” ๋“ฑ์˜ ์ž‘์—…์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ ์‚ฌ์šฉ
  • aggregation์€ Stage๋“ค์˜ ๋ฐฐ์—ด๋กœ ์ด๋ฃจ์–ด์ง€๊ณ  ๊ฐ Stage๋Š” ์ˆœ์ฐจ์ ์œผ๋กœ ์ˆ˜ํ–‰๋จ
db.posts.aggregate([
	{ $group: { _id: '$author', count: { $sum: 1 } } },
    { $match: { sum: { $gt: 10 } } },
    { $lookup: { from: 'users', localField: '_id', foreighField: '_id', as 'users' } }
]);
  • ์ž‘์„ฑ์ž๋ณ„ ๊ฒŒ์‹œ๊ธ€ ์ˆ˜๋ฅผ ์ทจํ•ฉํ•˜๊ณ , ๊ฒŒ์‹œ๊ธ€ ์ˆ˜๊ฐ€ 10๊ฐœ๋ณด๋‹ค ๋งŽ์€ ์ž‘์„ฑ์ž๋ฅผ ์ฐพ์•„์„œ ํ•ด๋‹น ์ž‘์„ฑ์ž๋ฅผ ํšŒ์› collection์—์„œ ๊ฒ€์ƒ‰ํ•œ๋‹ค.