728x90
    
    
  반응형
    
    
    
  express
$ npm i express 
$ npm i -D nodemon
- nodemon
 
passport
$ npm i passport passport-local passport-kakao bcrypt
// app.js
const passport = require('passport'); 
const passportConfig = require('./passport'); 
const app = express();
passportConfig(); 
app.set('port', process.env.PORT || 3001);
app.use(session({
	resave: false,
	saveUninitialized: false,
	secret: process.env.COOKIE_SECRET,
	cookie: {
		httpOnly: true,
		secure: false,
	},
}));
app.use(passport.initialize());
app.use(passport.session());
- `passport.initialize` 미들웨어: 요청(req 객체)에 `passport` 설정
 - `passport.session` 미들웨어: `req.session` 객체에 `passport` 정보 저장
- `req.session` 객체는 `express-session`에서 생성 
→ `passport` 미들웨어는 `express-session` 미들웨어보다 뒤에 연결해야 함 
 - `req.session` 객체는 `express-session`에서 생성 
 
// passport/index.js
const passport = require('passport');
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);
	});
	
	kakao();
};
- `passport.serializeUser`
- 로그인 시 실행, `req.session`(세션) 객체에 어떤 데이터를 저장할지 정하는 메서드
 - 매개변수로 user를 받고 done 함수에 두 번째 인수로 user.id를 넘기고 있음
 - done 함수의 첫 번째 인수: 에러가 발생했을 대 사용/ 두 번째 인수: 저장하고 싶은 데이터 넣음
 - 로그인 → 사용자 데이터를 세션에 저장, 사용자 정보 모두 저장 시 용량 ↑ + 데이터 일관성 문제 발생 → 사용자 아이디만 저장
 
 
⇒ 사용자 정보 객체에서 아이디만 추려 세션에 저장
- `passport.deserializeUser`
- 각 요청마다 실행, `passport.session` 미들웨어가 이 메서드 호출
 - `serializeUser`의 done의 두 번째 인수로 넣었던 데이터 == `deserializeUser`의 매개변수(사용자 아이디)
 - `serializeUser`에서 세션에 저장했던 아이디 → 데이터베이스에서 사용자 정보 조회 → 조회한 정보를 `req.user`에 저장 
⇒ `req.user` ~> 로그인한 사용자의 정보 가져올 수 있음 
 
⇒ 세션에 저장한 아이디 ~> 사용자 정보 객체 불러옴
→ 세션에 불필요한 데이터 담아두지 않기 위한 과정
전체 과정
- /auth/login 라우터 ~> 로그인 요청 들어옴
 - 라우터에서 `passport.authenticate` 메서드 호출
 - 로그인 전략(`kakaoStrategy`) 수행
 - 로그인 성공 시 사용자 정보 객체와 함께 `req.login` 호출
 - `req.login` 메서드가 `passport.serializeUser` 호출
 - `req.session`에 사용자 아이디만 저장해서 세션 생성
 - `express-session`에 설정한 대로 브라우저에 `connect.sid` 세션 쿠키 전송
 - 로그인 완료
 
로그인 이후 과정
- 요청이 들어옴
 - 라우터에 요청이 도달하기 전 `passport.session` 미들웨어가 `passport.deserializeUser` 메서드 호출
 - `connect.sid` 세션 쿠키를 읽고 세션 객체를 찾아서 `req.session`으로 만듦
 - `req.session`에 저장한 아이디로 데이터베이스에서 사용자 조회
 - 조회된 사용자 정보를 `req.user`에 저장
 - 라우터에서 `req.user` 객체 사용 가능
 
로그인 시 동작 → 전략(strategy): 로그인 과정을 어떻게 처리할지 설명하는 파일
미들웨어
라우터에 접근 권한을 제어
// middlewares/index.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}`);
	}
};
시퀄라이즈(sequelize)
ORM(Object-relational Mapping): 자바스크립트 객체와 데이터베이스의 릴레이션 매핑해주는 도구
자바스크립트 구분을 알아서 SQL로 바꿔줌
⇒ SQL 언어를 직접 사용 X 자바스크립트만으로 MySQL 조작 O
$ npm i express morgan sequelize sequelize-cli mysql2
- `sequelize-cli`: 시퀄라이즈 명령어를 실행하기 위한 패키지
 - mysql2: MySQL과 시퀄라이즈를 이어주는 드라이버
- 데이터베이스 프로그램 X
 
 
$ npx sequelize init
- npx: 전역 설치 X 명령어로 사용
 
⇒ config, models, migrations, seeders 폴더 생성
// models/index.js
const Sequelize = require('sequelize');
const env = process.env.NODE_ENV || 'development';
const config = requrie('../config/config')[env];
const db = {};
const sequelize = new Sequelize(config.database, config.username, config.password, config);
db.sequelize = sequelize;
module.exprts = db; 
- Sequelize: 시퀄라이즈 패키지이자 생성자
 - config/config.json에서 데이터베이스 설정을 불러온 후 new Sequelize ~> MySQL 연결 객체 생성
 - 연결 객체 나중에 재사용 → `db.sequelize`에 넣어둠
 
MySQL 연결
// app.js
const express = require('express');
const path = require('path');
const { sequelize } = require('./models');
const app = express();
app.set('port', process.env.PORT || 3001);
sequelize.sync({ force: false })
 .then(() => {
	 console.log('데이터베이스 연결 성공');
 })
 .catch((err) => {
	 console.log(err);
 });
 
app.use(morgan('dev');
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.json());
app/use(express.urlencoded({ extended: false }));
app.use((req, res, next) => {
});
app.use((err,req, res,next) => {
	res.locals.message = err.message;
	res.locals.error = process.env.NODE_ENV !== 'production' > err : {};
	res.status(err.status || 500);
	res.render('error');
});
app.listen(app.get('port'), () => {
 console.log(app.get('port'), '번 포트에서 대기중');
});
// config/config.json
{
	"development": {
		"username": "root",
		"password": "root 비밀번호",
		"database": "database 이름",
		"host": "127.0.0.1",
		"dialect": "mysql"
	},
	...
}
- test, production: 테스트용, 배포용
 - `process.env.NODE_ENV`가 development일 때 적용
 
모델 정의
const Sequelize = require("sequelize");
class User extends Sequelize.Model {
  static initiate(sequelize) {
    User.init(
      {
        user_id: {
          type: Sequelize.INTEGER,
          autoIncrement: true,
          primaryKey: true,
        },
        nickname: {
          type: Sequelize.STRING(15),
        },
        email: {
          type: Sequelize.STRING(255),
          unique: true,
        },
        kakao_id: {
          type: Sequelize.STRING(30),
          unique: true,
        },
      },
      {
        sequelize,
        modelName: "User",
        tableName: "users",
        timestamps: true,
        charset: "utf8",
        collate: "utf8_general_ci",
      }
    );
  }
  static associate(db) {
    User.hasMany(db.UpbitAccounts, {
      foreignKey: "user_id",
      as: "upbitAccounts",
    });
    User.hasMany(db.BithumbAccounts, {
      foreignKey: "user_id",
      as: "bithumbAccounts",
    });
  }
}
module.exports = User;
- `static initiate` 메서드: 테이블에 대한 설정
- 모델.init의 첫 번째 인수: 테이블 컬럼에 대한 설정
- 알아서 id를 기본키로 연결 → id 컬럼 적어줄 필요 X
 - 여러 데이터베이스 처리 → MySQL 자료형과 다름
- VARCHAR → STRING/ INT → INTEGER/ TINYINT → BOOLEAN/ DATETIME → DATE
 - INT UNSIGNED → INTEGER.UNSIGNED/ NOT NULL → allowNull: false/ UNIQUE → unique: true/ DEFAULT now() → defaultValue: Sequelize:NOW
 
 
 - 두 번째 인수: 테이블 자체의 설정
- `sequelize`: `static initiate` 메서드의 매개변수와 연결되는 옵션 
/ `db.sequelize` 객체 넣어야 함/ model/index.js에서 연결 - `timestamps: true` → 시퀄라이즈는 createdAt과 updatedAt 컬럼 추가
 - `underscored`: 테이블명과 컬럼명을 camel case로 만듦 → snake case로 바꾸는 옵션
- ex. createdAt → created_at
 
 - `modelName`: 모델 이름 설정
 - `tableName`: 실제 데이터베이스의 테이블 이름/ 모델 이름: 소문자 및 복수형
- ex. 모델 이름: User → 테이블 이름: users
 
 - `paranoid: true` → deletedAt 컬럼 추가/ 로우 삭제 → 완전히 지우지 X 지운 시각 기록 
/ 로우 조회 → deletedAt: null(삭제되지 X) 조회 ⇒ 로우 복원 - `charset`과 `collate`: 각각 utf8과 utf8_general_ci로 설정 → 한글 입력 이모티콘까지 입력 
→ utf8mb4, utf8mb4_general_ci 
 - `sequelize`: `static initiate` 메서드의 매개변수와 연결되는 옵션 
 
 - 모델.init의 첫 번째 인수: 테이블 컬럼에 대한 설정
 - `static associate` 메서드: 다른 모델들과의 관계
 
SQL vs. NoSQL
SQL(MySQL) NoSQL(몽고디비)
| 규칙에 맞는 데이터 입력 | 자유로운 데이터 입력 | 
| 테이블 간 JOIN 지원 | 컬렉션 간 JOIN 미지원 | 
| 안정성, 일관성 | 확장성, 가용성 | 
| 용어(테이블 ,로우, 컬럼) | 용어(컬렉션, 다큐먼트, 필드) | 
728x90
    
    
  반응형
    
    
    
  '💻 > Node.js' 카테고리의 다른 글
| [Node.js 교과서 - 개정 3판] 4장 http 모듈로 서버 만들기 (0) | 2024.08.19 | 
|---|---|
| [Node.js 교과서 - 개정 3판] 1장 노드 시작하기 (1) | 2024.08.16 |