SNS 만들기 -2(with Node, MySQL, Nunjucks) ★

백지연·2022년 1월 28일
2

NodeJS

목록 보기
16/26
post-thumbnail

본격적으로 백엔드 부분을 구현해보자! 이번 포스팅은 이 전 게시글과 이어진다. 이전에 작성한 코드(sns)와 일부 내용이 달라지므로 git에 올라가있는 sns 폴더를 sns2 폴더로 복사해 작업했다. 구현하는 부분이 생길수록 계속 복사할 것이다.( sns3, sns4 ...)

책 Node.js 교과서(개정 2판) 책의 9장의 내용을 참고했다.
+모든 코드는 github주소에 있다.

지금까지 구현한 것

  1. 프로젝트 기본 뼈대 잡기
  2. 프론트엔드 화면 구현하기

이번 포스팅에서 구현할 것

  1. DB 세팅하기

3. DB 세팅하기

Github: https://github.com/delay-100/study-node/tree/main/ch9/sns2

본격적인 기능을 구현하기 전에, 각각의 모델이 있어야 DB에 저장하기 좋다. 쉽게말해, 로그인을 하려면 user 모델이, 게시글을 작성하려면 post 모델이, 해시태그를 쓰려면 hashtag 모델이 필요하다. 모델의 규격이 생기면 사용자가 모델에 값을 입력하게 된다. 이 정보들이 서버의 DB에 저장되어야 하는데 그 때 도와주는 라이브러리가 Sequelize이다.

따라서, 데이터베이스로는 MySQL을 사용하고 Sequelize를 이용하겠다.

[model 구성]

  • User : 사용자 정보
  • Post : 게시글
  • Hashtag : 해시태그
  • Follow, PostHashtag (관계 모델)
    **관계 모델들도 db.sequelize.models.PostHashtag, db.sequelize.models.Follow로 접근 가능, 쿼리 호출이나 관계 메서드 사용도 가능

[model 간 관계]

  • 1(User) : N(Post)
  • N(User) : M(User) -> Follow
  • N(Post) : M(Hashtag) -> PostHashtag

자기 참조 M:N 모델 이해하기

N(User) : M(User) -> Follow

팔로우의 예를 들어보자! (팔로워, 팔로잉)

하나의 User 모델이 존재할 때, user1이 user2를 팔로우 하는 상황이다.
직관적으로 그림으로 표현해놓았지만 설명을 하자면,

  1. user1가 user2를 팔로우하면 user1의 팔로잉은 1이 된다. (user2는 팔로워가 1이 됨)
  2. belongsToMany 코드를 보면 foreignKey에 followerId는 user2에서 1이 된 팔로워를 가리킨다. (followingId는 user1의 팔로잉이 1이 된 것을 나타낸다.)
  3. M:N에서는 참조 모델을 새로 생성하는데, 결국 그림처럼 Follow 모델에 저장된다. (그림에 user2에 대한 Follow는 없지만 user1과 동일하게 저장된다고 생각하면 된다.)

해당 내용을 제외하고는 주석으로 설명해두었으니 참고하면 좋을 것 같다!

Git [sns2/models/user.js]

// 사용자 정보를 저장하는 모델
const Sequelize = require('sequelize');

module.exports = class User extends Sequelize.Model { // User 모델을 만들고 모듈로 exports함(User 모델은 Sequelize.Model을 확장한 클래스)
    static init(sequelize) { // 테이블에 대한 설정 <-> static associate: 다른 모델과의 관계
        return super.init({ // super.init의 첫 번째 인수: 테이블에 대한 컬럼 설정
            email: { // 이메일
                type: Sequelize.STRING(40),
                allowNull: true, // null 허용 설정
                unique: true,
            },
            nick: { // 닉네임
                type: Sequelize.STRING(15),
                allowNull: false,
            },
            password: { // 비밀번호
                type: Sequelize.STRING(100),
                allowNull: true,
            },
            provider: { // 로그인 방식
                type: Sequelize.STRING(10),
                allowNull: false,
                defaultValue: 'local', // 기본 값 local 로그인, sns으로 로그인 한 경우는 kakao 저장 
            },
            snsId: { // snsId
                type: Sequelize.STRING(30),
                allowNull: true,
            },
        }, { // super.init의 두 번째 인수: 테이블 자체에 대한 설정(테이블 옵션)
            sequelize, // static init 메서드의 매개변수와 연결되는 옵션, db.sequelize 객체를 넣어야 함 -> 추후에 models/index.js에서 연결
            timestamps: true, // true: Sequelize가 자동으로 createdAt과 updatedAt, deletedAt 컬럼을 추가
            underscored: false, // true: create_at같이(스네이크 케이스), false: createdAt같이(캐멀 케이스) 
            modelName: 'User',
            tableName: 'users',
            paranoid: true, // 컬럼을 지워도 완전히 지워지지 않고 deletedAt이라는 컬럼이 생김(지운 시각이 기록됨)
            charset: 'utf8',
            collate: 'utf8_general_ci',
        });
    }
    static associate(db) {  // 다른 모델과의 관계 <-> static init: 테이블에 대한 설정
        db.User.hasMany(db.Post); // user과 post는 1:N관계
        // User과 User는 N:M관계 (팔로잉 기능 - 팔로워, 팔로우)
        // 같은 테이블 간 N:M은 모델 이름과 컬럼 이름을 따로 정해야 함
        db.User.belongsToMany(db.User, { 
            foreignKey: 'followingId', // user1에게 생기는 following
            as: 'Followers', // 생성된 Follow라는 테이블을 이름을 바꿔서 가져옴 - user.getFollowers, user.getFollowings 같은 관계 메소드 사용 가능
                            // include 시에도 as에 넣은 값을 넣으면 관계 쿼리가 작동함
            through: 'Follow', // 생성할 테이블 이름 , 유저-테이블 -유저, 특정 유저의 팔로잉/팔로워 목록이 저장됨
        });
        db.User.belongsToMany(db.User, {
            foreignKey: 'followerId', // user2에게 생기는 follower
            as: 'Followings',
            through: 'Follow', 
        });
    }
}

Git [sns2/models/post.js]

const Sequelize = require('sequelize');

module.exports = class Post extends Sequelize.Model {
    static init(sequelize) {
        return super.init({
            content: {
                type: Sequelize.STRING(140),
                allowNull: false,
            },
            img: {
                type: Sequelize.STRING(200),
                allowNull: true,
            },
        }, {
            sequelize,
            timestamps: true,
            underscored: false,
            modelName: 'Post',
            tableName: 'posts',
            paranoid: false,
            charset: 'utf8mb4',
            collate: 'utf8mb4_general_ci',
        });
    }
    static associate(db) {
        db.Post.belongsTo(db.User); // 1(User):N(Post) 관계, 게시글의 작성자를 알 수 있게 됨 - post.getUser, post.addUser 같은 관계 메서드가 생김
        db.Post.belongsToMany(db.Hashtag, {through: 'PostHashtag'}); // N(Post):M(Hashtag) 관계, PostHashtag 테이블(중간 모델) 생성
                                                                    // PostHashtag에는 postId, hashtagId라는 foreignKey가 생성됨, 
                                                                    // as는 따로 지정하지 않았으므로 post.getHashtags, post.addHashtags, hashtags.getPosts 같은 기본 이름의 관계 메서드들이 생성됨  
    }
};

Git [sns2/models/hashtag.js]

const Sequelize = require('sequelize');

module.exports = class Hashtag extends Sequelize.Model {
    static init(sequelize){
        return super.init({
            title: {
                type: Sequelize.STRING(15),
                allowNull: false,
                unique: true,
            },
        }, {
            sequelize,
            timestamps: true,
            underscored: false,
            modelName: 'Hashtag',
            tableName: 'hashtags',
            paranoid: false,
            charset: 'utf8mb4',
            collate: 'utf8mb4_general_ci',
        });
    }
    static associate(db) {
        db.Hashtag.belongsToMany(db.Post, {through: 'PostHashtag'});
    }
};

Git [sns2/models/index.js] - 기존의 코드 모두 삭제 후 해당 내용으로 수정!

const Sequelize = require('sequelize');
const env = process.env.NODE_ENV || 'development';
const config = require('../config/config')[env]; // config/config.json에서 필요한 데이터베이스 설정을 불러옴(배열 값을 불러왔는데, 지금은 개발용 - development)
const User = require('./user');
const Post = require('./post');
const Hashtag = require('./hashtag');

const db = {};
const sequelize = new Sequelize(
  config.database, config.username, config.password, config,
); // new Sequelize를 통해 MySQL 연결 객체 생성

db.sequelize = sequelize;
db.User = User;
db.Post = Post;
db.Hashtag = Hashtag;

// 각 객체 실행
User.init(sequelize);
Post.init(sequelize);
Hashtag.init(sequelize);

// 관계 연결
User.associate(db);
Post.associate(db);
Hashtag.associate(db);

module.exports = db;

Git [sns2/config/config.json] 中 development 부분 수정

{
  "development": {
    "username": "root",
    "password": "[root 비밀번호]",
    "database": "sns",
    "host": "127.0.0.1",
    "dialect": "mysql"
  },
  "test": {
    "username": "root",
    "password": null,
    "database": "database_test",
    "host": "127.0.0.1",
    "dialect": "mysql"
  },
  "production": {
    "username": "root",
    "password": null,
    "database": "database_production",
    "host": "127.0.0.1",
    "dialect": "mysql"
  }
}

Git [sns2/app.js] 中 윗부분 수정

...

dotenv.config(); // .env 파일을 쓸 수 있게 함
const pageRouter = require('./routes/page');
const { sequelize } = require('./models');

const app = express();
app.set('port', process.env.PORT || 8001);
app.set('view engine', 'html');
nunjucks.configure('views', {
    express: app,
    watch: true,
});

// sequelize와 db 연결
sequelize.sync({ force: false })
    .then(() =>{
        console.log('데이터베이스 연결 성공');
    })
    .catch((err)=> {
        console.error(err);
    });

...

입력(console)

npx sequelize db:create

실행화면(console)

입력(console)

npm start

실행화면(console)

이제 사용자 정보를 저장할 수 있게 되었다!


다음 포스팅에서는 지금까지 구현한 것을 바탕으로 로그인 기능을 구현해보겠다!!

profile
TISTORY로 이사중! https://delay100.tistory.com

0개의 댓글