TIL - 220609(목)

Jason Moon·2022년 6월 9일
0

TIL

목록 보기
14/47

오늘 한 일

  • 시퀄라이즈, passport로 회원가입, 로그인 구현

오늘 주요로 한 일은 강의 들으면서 만들었던 sns프로젝트 코드를 하나 하나 뜯어보면서 공부했다. 확실히 흐름을 이해하고 하나하나 왜 썼는지 공부하니까 다음에 내가 쓸 수 있겠다는 생각이 들었다.
항해99에서 갑자기 모여 얘기하는 경우가 많다보니 개인 공부 계획한게 틀어질 때가 있다. 개인 공부는 아침으로 하는게 좋을꺼 같다.
아침에 자바스크립트를 공부해야겠다. 개념 정리는 저녁에 시간 날 때 조금씩 해놓자
항해 99 끝났을 때 자바스크립트에 자신감이 있었으면 좋겠다.


오늘의 노드

노드의 http모듈로 서버를 만들면 http method와 url경로마다 if문으로 나눠줘야 하는데 그럼 코드양도 많아져 복잡해진다. 그래서 express를 사용(깔끔하고 구조적으로 짤 수 있다)

시퀄라이즈를 사용하기 위해 설치해야할 것들

npm i sequelize mysql2 sequelize-cli

npx sequelize init(npx 명령어를 사용하는 이유는 전역 설치(npm i -g)를 피하기 위해

npx sequelize init 이 명령어를 치면 sequelize 세팅이 된다.

시퀄라이즈 모델 설정

models ⇒ index.js

const env = process.env.NODE_ENV || ‘development’ ;
// 환경변수로 NODE_ENV가 있는데 배포할 때는 process.env.NODE_ENV 이 부분을 production이라는 문자열로 바꾼다.
// 지금은 아무것도 설정 안돼있으면 기본값 ‘development’가 된다.

const config = require(../config/config’)[env];
// config.js를 require했다. 거기에 development를 가져와라는 말
{
    "username": "root",
    "password": "password",
    "database": "nodebird",
    "host": "127.0.0.1",
    "dialect": "mysql"
  }
// 이 객체가 config라는 변수에 담긴다

const sequelize = new Sequelize(
  config.database,
  config.username,
  config.password,
  config
);
// 이렇게 하면 sequelize가 노드랑 mysql을 연결해준다. sequelize는 내부적으로
// mysql2를 사용하고 있다. sequelize가 mysql2라는 드라이버에 config.database,
// config.username,config.password,config 설정 정보를 보내줘서 노드랑 mysql을
// 연결할 수 있게 도와준다.
// 연결 성공하면 const sequelize에 그 연결 정보가 담겨있다.
// 연결을 했으면 이제 테이블을 만들어 줘야한다.

시퀄라이즈에서는 테이블을 모델이라고 부른다. mysql에서는 테이블이고 시퀄라이즈에서는 모델이다.

기본적인 모델들 만들어 놓고 모델(테이블)들 간에 관계를 설정해주는게 좋다.

한 유저가 게시글 여러개을 쓸수 있나? ok 게시글 하나에 유저가 여러명일 수 있나? 있을 수도 있지만 지금 만드는 sns에는 여러명일 수 없다. 그러므로 유저와 게시글은 1:N 관계이다.

게시글과 댓글의 관계도 1:N의 관계이다.

belongsTo한 곳에 UserId, PostId 컬럼을 만들어준다. 댓글에 어떤 사용자가 작성했는지, 어떤 게시글에 작성됐는지 사용자 id와 게스글 id가 추가된다.

hasMany한 곳에 생기면 안되나 생각할 수 있는데

한 사람이 게시글을 여러개 달 수 있는데 그럼 PostId 1,3,4,5이렇게 4개 작성했다 치면 한 컬럼에 저렇게 4개의 데이터가 들어갈 수 없다. 한 칸에 하나의 정보만 들어가야한다.

해쉬태그랑 게시글 관계는 다대다 관계이다.

해쉬태그도 여러개의 게시글을 가질 수 있고 게시글도 여러개의 해쉬태크를 가질 수 있다.

다대대 관계일 때는 belongsToMany를 쓴다. 중간 테이블은 설정해주지 않으면 자동으로 예를들어 Post 와 Hashtag이면 PostHashtag이렇게 생긴다.

<예시>

db.Post.belongsToMany(db.Hashtag)
db.Post.belongsToMany(db.User, { through: 'Like' }) // through로 중간 테이블 이름을 직접 만들 수도 있다.
// 중간 테이블 이름을 안만들면 UserPost이렇게 되는데 좋아요 기능을 설명하기에는 충분하지 않아 
// 예제처럼 Like로 만들어 줌

<as 쓸 때의 예시>

db.Post.belongsTo(db.User); //포스트의 작성자
//db.Post.belongsToMany(db.User, { through: 'Like' })
//위와 같이 있을 경우 위에꺼가 뭐고 밑에꺼가 뭔지 헷갈린다 그럴때 아래와 같이 안헷갈리게 
//as를 붙여주면 된다.
db.Post.belongsToMany(db.User, { through: 'Like', as: 'Likers' }) // 포스트에 좋아요를 누른 사람들
// 나중에 as에 따라서 post.getLikers처럼 게시글 좋아요 누른 사람을 가져오게 된다.

//user.js에서도
db.User.belongsToMany(db.Post, { through: 'Like', as: 'Liked' }) // 내가 좋아요를 누른 애들
// through랑 as 대문자로 해주는게 좋다.

같은 테이블 내의 관계(예) 팔로잉 팔로우 관계

db.User.belongsToMany(db.User, { through: 'Follow', as: 'Followers', foreignKey: 'FollowingId' });
db.User.belongsToMany(db.User, { through: 'Follow', as: 'Followings', foreingKey: 'FollowerId'});   // 내가 팔로잉하는 사람들을 찾으려면 나를 먼저 찾아야한다. 나는 follower이다

foreignkey를 써주는 이유는 예를 들어 User테이블과 Post 테이블이 있다고 치면 중간 테이블로

Like를 만들어 주면 Like에 UserId, PostId이렇게 생긴다.

근데 같은 테이블간의 관계에서 위의 예로 치면 User테이블과 User테이블에서 중간에 Follow가 있으면 foreignKey를 지정해주지 않으면 UserId, UserId이렇게 생기게 된다. 그럼 구별이 안된다. 그래서 foreignkey로 바꿔줄 수 있다.

서버 실행하기 전에

npx sequelize db:create로 데이터베이스를 만들어준다.

모델들 다 만들고 관계설정도 다 해줬다면

express에서 squelize를 등록해줘야한다.

app.js에

const db = require('./models')
// db안에다 sequelize 넣어뒀기 때문에

db.sequelize.sync() // 프로미스여서 then catch
 .then(() => {
	console.log('db 연결 성공');
)}
.catch(console.error);
// 이렇게 하면 서버 연결할 때 시퀄라이즈 연결도 같이 해준다.

bcrypt도 비동기라서 await 붙여줘야 한다.

비동기인지 아닌지는 공식문서 보고 찾아야한다.

router.post('/', async (req, res, next) => {
	try{
		const exUser = await User.findOne({
			where: {
				email: req.body.email,
			}
		});
		if(exUser){
			return res.status(403).send('이미 사용 중인 아이디입니다.');
		}
		const hashedPassword = await bcrypt.hash(req.body.password, 10);
		await User.create({
			email: req.body.email,
			nickname: req.body.nickname,
			password: hashedPassword,
		});
		res.status(201).send('ok')
	
	} catch (error) {
		console.error(error);
		next(error);
	}
});

cors 문제

브라우저에서 다른 서버로 요청을 보냈을 때 발생하는 문제

서버에서 서버로 요청보냈을 때는 cors가 안생긴다.

브라우저에서 다른 도메인 서버로 보냈을때만 cors문제가 생긴다.

cors문제 해결하는 방법이 여러가지 있는데 서버에서 서버로 요청보낼땐 cors 문제가 안생기니까

브라우저 서버에서 프론트 서버로 요청을 보낸다. 같은 도메인이기 때문에 문제 안생긴다.

프론트 서버에서 백엔드로 보내면 된다. 응답도 그러면 백에서 프론트서버로 보내면 된다.

이걸 proxy라고 한다.

미들웨어로 처리할 수도 있다.

npm i cors

app.js에서

const cors = require(’cors’)

app.use(cors()); 이렇게 하면 모든 요청에 다 설정을 넣어준다.

실무에서는 cors(origin: ‘ 경로') 써서 설정해준다.

passport

로그인은 카카오, 구글, 네이버, 깃허브 등등 로그인 전략들이 있기 때문에 복잡하다.

이걸 한 번에 관리해주는 passport라는게 있다.

이메일 비밀번호로 로그인 하기 때문에 passport-local을 설치

npm i passport passport-local

passport-local이 이메일 혹은 아이디 비밀번호로 로그인할 수 있게 도와준다.

passport 셋팅

passport폴더 만들고 index.js만든다.

index.js(passport 설정 파일)

const passport = require('passport');
const local = require('./local'); //로컬이란 파일을 만들어서 
module.exports = () => {
 passport.serializeUser(() => {

});

	passport.deserializeUser(() => {
	
	});

	local(); // passport-local 설치한 걸 실행
};

serialize와 deserialize가 있다.

두개가 설정이다. 그리고 local을 실행

local.js (로그인 전략을 세울 수 있다)

const passport = require('passport');
const {Strategy: LocalStrategy} = require('passport-local');
// 구조분해 할당으로 Strategy이름을 LocalStrategy로 바꿀 수 있다.
// 그냥 { Strategy }로 해서 Strategy를 써도 되지만 나중에 Kako로그인 등을 할 때
// 구별 해줄 수 있다.
const bcrypt = require('bycrypt');
const { User } = require('../models');
 
module.exports = () => {
	passport.use(new localStrategy({
	  usernameField: 'email', (req.body.email을 적어준 것, 만약 아이디가 id면 'id'로 바꿔줘야한다.)
		passwordField: 'password', (req.body.password를 적어준 것)
}, async (email, password, done) => { // 위에서 설정한게 그대로 인자로 들어간다.
		// 여기서 로그인 전략을 수행, 먼저 이메일이 있는지 확인한다.
		try{
				const user = await User.findOne({
				where: { email }
				})
				if(!user) {
				return done(null, false, {reason: '존재하지 않는 사용자입니다!'}) // res.status(403)이렇게 하는게 아니라 done으로 넘겨준다. passport에서는 응답을 보내주진 않는다.
				// done으로 결과를 판단, 첫번째 자리는 서버에러, 두번쨰 자리 성공, 세번쨰 자리 클라이언트 에러(보내는 측에서 잘못보내서 에러)
				} // 사용자가 존재하면 이제 비밀번호 확인
				const result = await bycrypt.compare(password, user.passowrd)
				// 앞의 password는 로그인할 때 적은 비밀번호(req.body.password)고 뒤에 password는 db에 저장된 패스워드
				if(result){
				return done(null, user); 두번쨰 자리가 성공이니까 성공에다가 사용자 정보를 넘겨준다.
				}
				return done(null, false, { reason: '비밀번호가 틀렸습니다.'});
				// 비밀번호가 틀렸을 경우 틀렸다고 보내준다.
			}catch(error){
				console.error(error);
				return done(error); // 서버 에러난경우에는 done으로 처리
			}
})); 
};

// 비동기 처리하면 항상 서버에러가 날 수 있다. 그걸 대비해 try catch로 감싸줘야한다.

// 이 module.exports가 index.js의 local()에서 실행된다.
// 인자로 두개가 들어간다. 하나는 객체 하나는 함수, 사용 방법이기때문에 걍 따라해야함

이 패스포트 전략을 언제쓰느냐

유저 로그인할 때 쓴다.

routes ⇒ user.js

const passport = require('passport');

// 로그인 api
router.post('/login', passport.authenticate('local'), (err, user, info) => {
if(err) { // 매겨변수 err, user, info는 로그인 전략의 done으로 부터 온건다. 매개변수 이름은 마음대로 정해도 된다.
		console.error(err);
		next(err);  // passport.authenticate('local') 구조상 next를 쓸 수 없다 
								// 이럴 경우 미들웨어 확장을 해서 쓸수 있다.
})); 

// 미들웨어 확장
router.post('/login',(req,res,next) => {
  passport.authenticate('local', (err, user, info) => {
    if(err){
      console.error(err);
      return next(err); // 서버에러 있으면
    }
    if(info){ // 클라이언트 에러가 있으면
      return res.status(401).send(info.reason); // info 객체 안에 메세지를 보냄
    }
    return req.login(user, async (loginErr) => {
      if(loginErr) { // 우리서비스 로그인이 아니라 passport로그인 에러를 말한다. 우리 서비스 로그인할 때 다 통과하면 passport로그인을 한 번 더 한다 그때 에러가 나면 그 에러를 말함
        console.error(loginErr)  
        return next(loginErr)
      }
      return res.json(user)
    }) // 성공하면 user에 사용자객체가 들어있다. req.login으로 로그인할 수 있다.
    // 이게 진짜 로그인한 것 passport에 들어있는  passport에서 로그인할 수 있게 허락을 해준다.
  })(req, res, next);// 마지막에 (req, res, next);붙여서 확장할 수 있다. 
  //passport.authenticate미들웨어는 (req, res, next) 쓸 수 없는 미들웨어인데 확장해서 쓸 수 있다.
})

passport의 index.js는

app.js에 연결해준다.

app.js

const passportConfig = require('./passport');

passportConfig();

오늘의 자바스크립트

고차함수 ⇒ 함수를 반환하는 함수

반복적인 걸 매개변수를 이용해 줄일 수 있다.

<예시>

const sayMyName = (name) => () => {
	console.log(name);
}

const name = sayMyName('jason');
name(); // 'jason'

궁금한 것들

  • mysql2
    • 노드랑 mysql을 연결해주는 드라이버
  • sequelize-cli
  • sequelize
    • 자바스크립트로 sql을 조작할 수 있게 해주는 라이브러리(sql을 몰라도 자바스크립트를 알고있으면 자바스크립트를 알아서 sql로 바꿔준다.
  • npx
profile
어려워 보여도 시간을 들여서 해보면 누구나 할 수 있는 일이다

0개의 댓글