익스프레스로 SNS 서비스 만들기

익스프레스로 SNS 서비스 만들기

프로젝트 구조 갖추기

NodeBird SNS 서비스

기능 : 로그인 , 이미지 업로드 , 게시글 작성 , 해시태그 검색 , 팔로잉

express-generator 대신 직접 구조를 갖춤 프런트엔드 코드보다 노드 라우터 중심으로 볼 것 관계형 데이터베이스 MySQL 선택

nodebird 완성 화면

프로젝트 시작하기

nodebird 폴더를 만들고 package.json 파일 생성

노드 프로젝트의 기본

package.json 시퀄라이즈 폴더 구조 생성

폴더 구조 설정

views( 템플릿 엔진 ), routes( 라우터 ), public( 정적 파일 ), passport( 패스포트 ) 폴더 생성

app.js와 .env 파일도 생성

nodebird 폴더 구조

패키지 설치와 nodemon

npm 패키지 설치 후 nodemon 도 설치

nodemon은 서버 코드가 변경되었을 때 자동으로 서버를 재시작해줌

nodemon은 콘솔 명령어이기 때문에 글로벌로 설치

package.json

노드 서버의 핵심인 app.js 파일 작성

소스 코드는 https://github.com/ZeroCho/nodejs-book/blob/master/ch9/9.1/nodebird/app.js

.env도 같이 추가

라우터 생성

소스 코드는 https://github.com/ZeroCho/nodejs-book/tree/master/ch9/9.1/nodebird/routes

routes/page.js: 템플릿 엔진을 렌더링하는 라우터

views/layout.html: 프론트 엔드 화면 레이아웃(로그인/유저 정보 화면)

views/main.html: 메인 화면(게시글들이 보임)

views/profile.html: 프로필 화면(팔로잉 관계가 보임)

views/error.html: 에러 발생 시 에러가 표시될 화면

public/main.css: 화면 CSS

npm start 로 서버 실행 후 http://localhost:8001 접속

NodeBird 메인 화면

데이터베이스 세팅하기

모델 생성

소스 코드는 https://github.com/ZeroCho/nodejs-book/tree/master/ch9/9.2/nodebird/models

models/user.js: 사용자 테이블과 연결됨

- provider: 카카오 로그인인 경우 kakao, 로컬 로그인(이메일/비밀번호)인 경우 local

- snsId: 카카오 로그인인 경우 주어지는 id

models/post.js: 게시글 내용과 이미지 경로를 저장(이미지는 파일로 저장)

models/hashtag.js: 해시태그 이름을 저장(나중에 태그로 검색하기 위해서)

models/index.js

시퀄라이즈가 자동으로 생성해주는 코드 대신 다음과 같이 변경

모델들을 불러옴(require)

모델 간 관계가 있는 경우 관계 설정

User(1):Post(다)

Post(다):Hashtag(다)

User(다):User(다)

associate 작성하기

모델간의 관계들 associate 에 작성

1 대다 : hasMany 와 belongsTo

다대다 : belongsToMany

foreignKey: 외래키

as: 컬럼에 대한 별명

through: 중간 테이블명

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

// models/post.js static associate(db) { db.Post.belongsTo(db.User); db.Post.belongsToMany(db.Hashtag, { through: 'PostHashtag' }); }

// models/user.js 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', }); }

팔로잉-팔로워 다대다 관계

User( 다 ):User( 다 )

다대다 관계이므로 중간 테이블(Follow) 생성됨

모델 이름이 같으므로 구분 필요함(as가 구분자 역할, foreignKey는 반대 테이블 컬럼의 프라이머리 키 컬럼)

시퀄라이즈는 as 이름을 바탕으로 자동으로 addFollower, getFollowers, addFollowing, getFollowings 메서드 생성

시퀄라이즈 설정하기

시퀄라이즈 설정은 config/ config.json 에서

개발환경용 설정은 development 아래에

설정 파일 작성 후 nodebird 데이터베이스 생성

모델과 서버 연결하기

sequelize.sync () 가 테이블 생성

IF NOT EXIST(SQL문)으로 테이블이 없을 때만 생성해줌

app.js

npm start 로 서버 실행 시 콘솔에 SQL 문이 표시됨

Passport 모듈로 로그인

패스포트 설치하기

로그인 과정을 쉽게 처리할 수 있게 도와주는 Passport 설치하기

비밀번호 암호화를 위한 bcrypt도 같이 설치

설치 후 app.js와도 연결

passport.initialize(): 요청 객체에 passport 설정을 심음

passport.session(): req.session 객체에 passport 정보를 저장

express-session 미들웨어에 의존하므로 이보다 더 뒤에 위치해야 함

app.js 에 passport 적용 (참고 : https://github.com/ZeroCho/nodejs-book/blob/master/ch9/9.3/nodebird/app.js)

패스포트 모듈 작성

passport/index.js 작성

passport.serializeUser: req.session 객체에 어떤 데이터를 저장할 지 선택, 사용자 정보를 다 들고 있으면 메모리를 많이 차지하기 때문에 사용자의 아이디만 저장

passport.deserializeUser: req.session에 저장된 사용자 아이디를 바탕으로 DB 조회로 사용자 정보를 얻어낸 후 req.user에 저장

// 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(); };

패스포트 처리 과정

로그인 과정

로그인 요청이 들어옴 passport.authenticate 메서드 호출 로그인 전략 수행(전략은 뒤에 알아봄) 로그인 성공 시 사용자 정보 객체와 함께 req.login 호출 req.login 메서드가 passport.serializeUser 호출 req.session에 사용자 아이디만 저장 로그인 완료

로그인 이후 과정

모든 요청에 passport.session() 미들웨어가 passport.deserializeUser 메서드 호출 req.session에 저장된 아이디로 데이터베이스에서 사용자 조회 조회된 사용자 정보를 req.user에 저장 라우터에서 req.user 객체 사용 가능

로컬 로그인 구현하기

passport-local 패키지 필요

로컬 로그인 전략 수립

로그인에만 해당하는 전략이므로 회원가입은 따로 만들어야 함

사용자가 로그인했는지, 하지 않았는지 여부를 체크하는 미들웨어도 만듦

// routes/middlewares.js exports.isLoggedIn = (req, res, next) => { if (req.isAuthenticated()) { next(); } else { res.status(403).send('로그인 필요'); } }; exports.isNotLoggedIn = (req, res, next) => { if (!req.isAuthenticated()) { next(); } else { const message = encodeURIComponent('로그인한 상태입니다.'); res.redirect(`/?error=${message}`); } };

// routes/page.js const express = require('express'); const { isLoggedIn, isNotLoggedIn } = require('./middlewares'); const router = express.Router(); router.use((req, res, next) => { res.locals.user = req.user; res.locals.followerCount = 0; res.locals.followingCount = 0; res.locals.followerIdList = []; next(); }); router.get('/profile', isLoggedIn, (req, res) => { res.render('profile', { title: '내 정보 - NodeBird' }); }); router.get('/join', isNotLoggedIn, (req, res) => { res.render('join', { title: '회원가입 - NodeBird' }); }); router.get('/', (req, res, next) => { const twits = []; res.render('main', { title: 'NodeBird', twits, }); }); module.exports = router;

회원가입 라우터

routes/auth.js 작성

bcrypt.hash로 비밀번호 암호화

hash의 두 번째 인수는 암호화 라운드

라운드가 높을수록 안전하지만 오래 걸림

적당한 라운드를 찾는 게 좋음

?error 쿼리스트링으로 1회성 메시지

소스코드 참고 -> https://github.com/ZeroCho/nodejs-book/blob/master/ch9/9.3/nodebird/routes/auth.js

로그인 라우터

routes/auth.js 작성

passport.authenticate(‘local’): 로컬 전략

전략을 수행하고 나면 authenticate의 콜백 함수 호출됨

authError: 인증 과정 중 에러,

user: 인증 성공 시 유저 정보

info: 인증 오류에 대한 메시지

인증이 성공했다면 req.login으로 세션에 유저 정보 저장

소스코드 참고 -> https://github.com/ZeroCho/nodejs-book/blob/master/ch9/9.3/nodebird/routes/auth.js

로컬 전략 작성

passport/localStrategy.js 작성

usernameField와 passwordField가 input 태그의 name(body-parser의 req.body)

사용자가 DB에 저장되어있는지 확인한 후 있다면 비밀번호 비교(bcrypt.compare)

비밀번호까지 일치한다면 로그인

소스코드 참고 -> https://github.com/ZeroCho/nodejs-book/blob/master/ch9/9.3/nodebird/passport/localStrategy.js

카카오 로그인 구현

passport/kakaoStrategy.js 작성

clientID에 카카오 앱 아이디 추가

callbackURL: 카카오 로그인 후 카카오가 결과를 전송해줄 URL

accessToken, refreshToken: 로그인 성공 후 카카오가 보내준 토큰(사용하지 않음)

profile: 카카오가 보내준 유저 정보

profile의 정보를 바탕으로 회원가입

소스코드 참고-> https://github.com/ZeroCho/nodejs-book/blob/master/ch9/9.3/nodebird/passport/kakaoStrategy.js

카카오 로그인용 라우터 만들기

회원가입과 로그인이 전략에서 동시에 수행됨

passport.authenticate(‘kakao’)만 하면 됨

/kakao/callback 라우터에서는 인증 성공 시(res.redirect)와 실패 시(failureRedirect) 리다이렉트할 경로 지정

// routes/auth.js router.get('/logout', isLoggedIn, (req, res) => { req.logout(); req.session.destroy(); res.redirect('/'); }); router.get('/kakao', passport.authenticate('kakao')); router.get('/kakao/callback', passport.authenticate('kakao', { failureRedirect: '/', }), (req, res) => { res.redirect('/'); }); module.exports = router;

app.js 에 auth 추가

카카오 로그인 앱 만들기

https://developers.kakao.com 에 접속하여 회원가입

NodeBird 앱 만들기

카카오 앱 키 저장하기

REST API 키를 저장해서 .env 에 저장

카카오 웹 플랫폼 추가

웹 플랫폼을 추가해야 callbackURL 등록할 수 있음

카카오 동의항목 설정

이메일, 생일 등의 정보를 얻기 위해 동의항목 설정

카카오 로그인 시도

카카오톡 로그인 버튼을 누르면 카카오 로그인 창으로 전환

계정 동의 후 다시 NodeBird 서비스로 리다이렉트

https://www.inflearn.com/course/%EB%85%B8%EB%93%9C-%EA%B5%90%EA%B3%BC%EC%84%9C/dashboard/

본글의 모든 내용은 위 강의를 토대로 작성됩니다.

from http://jhg3410.tistory.com/29 by ccl(A) rewrite - 2021-11-15 01:00:55