이미지 업로드
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 메서드로 모든 게시글을 가져온다.
이제 구현한 라우터를 연결하고 테스트해보자.

이미지 업로드
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 메서드로 모든 게시글을 가져온다.
이제 구현한 라우터를 연결하고 테스트해보자.
