이미지 업로드
multer 패키지로 이미지 업로드를 구현해보자.
npm i multer
이미지를 어떻게 저장할 것인지는 서비스의 특성에 따라 달라진다.
이번 프로젝트에서는 input 태그를 통해 이미지를 선택할 때 바로 업로드를 진행하고, 업로드된 사진 주소를 다시 클라이언트에 알릴 것이다. db에는 경로만 저장한다.
우선 라우터를 작성하자.
const express = require('express');
const multer = require('multer');
const path = require('path');
const fs = require('fs');
const {Post, Hashtag} = require('../models');
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},
});
router.post('/img', isLoggedIn, upload.single('img'), (req, res) => {
console.log(req.file);
res.json({url: `/img/${req.file.filename}`});
});
const upload2 = multer();
router.post('/', isLoggedIn, upload2.none(), 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(err){
console.error(err);
next(err);
}
});
module.exports = router;
routes/post.js
POST /post/img 라우터에서는 이미지 하나를 업로드받은 뒤 이미지의 저장 경로를 클라이언트로 응답한다.
POST /post 라우터는 게시글 업로드를 처리하는 라우터이다.
그다음 page라우터를 수정하자.
const express = require('express');
const {isLoggedIn, isNotLoggedIn} = require('./middlewares');
const{ Post, User, Hashtag } = require('../models');
const router = express.Router();
router.use((req, res, next) => {
res.locals.user = req.user;
res.locals.followerCount = req.user ? req.user.Followers.length : 0;
res.locals.followingCount = req.user ? req.user.Followings.length : 0;
res.locals.followerIdList = req.user ? req.user.Followings.map(f => f.id) : [];
next();
});
router.get('/profile', (req, res) => {
res.render('profile', {title: '내 정보 - NodeBird'});
})
router.get('/join', (req, res) => {
res.render('join', {title: '회원가입 - NodeBird'});
})
router.get('/', 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);
}
});
router.get('/hashtag', async (req, res, next) => {
const query = req.body.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',{
tile: `${query} | NodeBird`,
twits: posts,
});
}catch(err){
console.error(err);
next(err);
}
});
module.exports = router;
routes/page.js
데이터베이스에서 게시글을 조회한 뒤 결과를 twits에 넣어 렌더링한다.
마지막으로 팔로잉과 해시태그 검색 기능만 추가하면 된다.
팔로우 기능을 위한 user 라우터를 만들어보자.
const express = require('express');
const { isLoggedIn } = require('./middlewares');
const User = require('../models/user');
const router = express.Router();
router.post('/:id/follow', isLoggedIn, async (req, res, next) => {
try{
const user = await User.findOne({where: {id: req.user.id}});
if(user){
await user.addFollowing(parseInt(req.params.id, 10));
res.send('success');
}else{
res.status(404).send('no user');
}
}catch(err){
console.error(err);
next(err);
}
});
module.exports = router;
routes/user.js
POST /user/:id/follow 라우터에서 :id 부분이 req.params/id가 된다.
팔로우할 사용자를 데이터베이스에서 조회한 후, 시퀄라이즈에서 추가한 addFollowing 메서드로 현재 로그인한 사용자와의 관계를 지정한다.
passport.deserializeUser((id, done) => {
User.findOne({
where: {id},
include: [{
model: User,
attributes: ['id', 'nick'],
as: 'Followers',
}, {
model: User,
attributes: ['id', 'nick'],
as: 'Followings',
}],
})
.then(user => done(null, user))
.catch(err => done(err));
});
passport/index.js
req.user를 바꾸기 위해 deserializeUser를 수정했다.
세션에 저장된 아이디로 사용자 정보를 조회할 때 팔로잉 목록과 팔로워 목록도 같이 조회한다.
팔로잉/ 팔로워 숫자와 팔로우 버튼을 표시하기 위해 routes/page.js를 수정하자.
router.use((req, res, next) => {
res.locals.user = req.user;
res.locals.followerCount = req.user ? req.user.Followers.length : 0;
res.locals.followingCount = req.user ? req.user.Followings.length : 0;
res.locals.followerIdList = req.user ? req.user.Followings.map(f => f.id) : [];
next();
});
해시태그로 게시물을 조회하게도 수정해보자.
router.get('/hashtag', async (req, res, next) => {
const query = req.body.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',{
tile: `${query} | NodeBird`,
twits: posts,
});
}catch(err){
console.error(err);
next(err);
}
});
쿼리스트링으로 해시태그 이름을 받고 해시태그 값이 없는 경우 메인 페이지로 돌려보낸다.
데이터베이스에서 해당 해시태그를 검색한 후, 해시태그가 있다면 시퀄라이즈에서 제공하는 getPosts 메서드로 모든 게시글을 가져온다.
이제 구현한 라우터를 연결하고 테스트해보자.