웹 개발

[포스코x코딩온] Sequelize - 종속관계, api.http 사용법

끊임없이 성장중인 개발자 2023. 12. 14. 01:12
728x90
반응형

포스팅 주제

  • 종속 관계, FK 사용법
  • api.http

RDBMS를 보이기 위한 Sequelize 종속관계 표현

 

이번에 각 테이블의 관계를 FK를 주어 종속관계로 만들려고 합니다.

관계는

  • 1:1  -> 서로 한가지 정보만을 공유하는 관계, ( 사용자와 프로필, 주문과 송장 등 )
  • 1:N -> 부모와 자식의 관계와 같이 한쪽이 다른 한쪽 레코드 여러개와 관계, (학교와 학생, 부모와 자식 등)
  • N:N -> 한 쪽 레코드가 다른 쪽 레코드 여러 개와 관련되고, 반대 쪽도 동일한 관계( 학생과 과목, 주문과 제품 등)

 

종속관계, FK 사용법

 

우선 기본 폴더 구조부터 만들고 시작한다.

 

 

기본적으로 사용하는 모듈은 이전 포스팅과 동일하기에  아래와 같이 설치해 준다.

npm init -y
npm express ejs mysql2 sequelize sequelize-cli

 

< config.json >

{
    "development": {
        "username" : "user",
        "password" : "1234",
        "database" : "kdt",
        "host" : "127.0.0.1",
        "dialect" : "mysql",
        "timezone": "+09:00"
    },
    "production" : {},
    "test" :{}
}

 

< app.js >

const express = require('express');
const app = express();
const PORT = 8000;

const db = require('./models/index');

app.set('view engine', 'ejs');
app.set('views', './views');

app.use(express.urlencoded({extended:true}));
app.use(express.json());

app.use('/static', express.static(__dirname + '/static'));

const indexRouter = require('./routes/index');
app.use('/', indexRouter);

app.get('*', (req,res)=>{
    res.render('404');
})

db.sequelize.sync({force:false}).then(() =>{
    app.listen(PORT, (req,res) => {
        console.log(`localhost:${PORT}`);
    })
}).catch((err)=>{
    console.log(err);
})

 

 

router의 기본 경로는 '/'을 사용한다.

force:false로 두어서 테이블이 없으면 생성하도록 한다.

 

< models - index.js >

const Sequelize = require('sequelize');
const config = require(__dirname + '/../config/config.json')['development'];
const db = {};

const sequelize = new Sequelize(
    config.database,
    config.username,
    config.password,
    config
);

//모델 모듈을 불러오기
const Player = require('./player')(sequelize, Sequelize);
const Profile = require('./Profile')(sequelize, Sequelize);
const Team = require('./team')(sequelize, Sequelize);

// 관계형성
// 1) Player : Prorile = 1:1
// 한 선수당 하나의 프로필을 가짐
Player.hasOne(Profile, {
    foreignKey: 'player_id',
    // sourceKey: 'player_id', // 기본키 사용시 생략 가능
    // 연쇄 삭제, 수정
    onDelete: 'CASCADE',
    onUpdate: 'CASCADE'
});
Profile.belongsTo(Player, {
    foreignKey: 'player_id',
    // targetKey: 'player_id' // 기본키 사용시 생략 가능
});

// 2) Team : Player = 1 : N
// 한팀에는 여러 선수가 존재
Team.hasMany(Player, {
    foreignKey: 'team_id',
    // sourceKey: 'team_id'
});
Player.belongsTo(Team,{
    foreignKey: 'team_id',
    // targetKey:'team_id'
});

// player_id라는 칼럼을 기본키라 외래키로 설정
// 그럼 상대방 foreignkey도 player_id
// 근데 다른 이름으로 왜래키를 만들고 싶다면, ex) player_test, <= 에 대해 미리 모델에 정의해줘야 한다.
// 이름이 같을 때는(기본키) sorceKey와 targetKey키는 생략 가능


// 관계를 정의한 모델들을 DB 객체에 저장

db.Player = Player;
db.Profile = Profile;
db.Team = Team;

db.sequelize = sequelize;
db.Sequelize = Sequelize;

module.exports = db;

 

먼저 DB를 설정해주어야 한다.

  • hasOne: 한 모델이 다른 모델을 가리키는 1:1 관계 설정
  • belongsTo: 다른 모델이 한 모델을 가리키는 1:1(1:N) 관계 설정

 

  • hasMany: 한 개의 모델(테이블)이 다른 모델과 1:N 관계를 설정
  • belongsTo: 다른 모델이 한 모델을 가리키는 1:1(1:N) 관계를 설정

 

  • belongsToMany: N:N 관계를 설정할 때 사용하는 메서드

 

player 테이블과 profile 테이블을 1:1 관계를 형성

  • 외래키 foreingKey: player_id 로 설정
  • 연새 삭제, 수정
    • onDelete - 삭제
    • onUpdate - 수정
      • "CASCADE" = 종속시킴

 

종속된 데이터를 삭제시 연결되어 있는 테이블에 값이 같이 삭제, 수정된다.

sourceKey와 targetKey는 기본키가 값일 때는 생략이 가능하다.

(default : 기본키)

 

Team 테이블과 Player 테이블은 1:N 관계를 형성

foreingKey : 'team_id' 로 외래키를 설정

 

< models - player.js, team.js, Profile.js >

player.js

const PlayerModel = (sequelize, DataTypes) => {
    const Player = sequelize.define('Player',{
        player_id: {
            type: DataTypes.INTEGER,
            primaryKey : true,
            allowNull: false,
            autoIncrement: true
        },
        name:{
            type: DataTypes.STRING(30),
            allowNull:false
        },
        age:{
            type: DataTypes.INTEGER,
            allowNull: false
        }
    },{
        freezeTableName: true
    });
    return Player;
}

module.exports = PlayerModel;

 

team.js

const TeamModel = (sequelize, DataTypes) => {
    
    const Team = sequelize.define('Team',{
        team_id: {
            type: DataTypes.INTEGER,
            primaryKey : true,
            allowNull: false,
            autoIncrement: true
        },
        name:{
            type: DataTypes.STRING(30),
            allowNull:false
        }
    },{
        freezeTableName: true
    });
    return Team;
}

module.exports = TeamModel;

 

Profile.js

const ProfileModel = (sequelize, DataTypes) => {
    const Profile = sequelize.define('Profile',{
        profile_id: {
            type: DataTypes.INTEGER,
            primaryKey : true,
            allowNull: false,
            autoIncrement: true
        },
        position: {
            type: DataTypes.STRING(30),
            allowNull:false
        },
        salary: {
            type: DataTypes.INTEGER,
            allowNull: false
        }
    },{
        freezeTableName: true
    });
    return Profile;
}

module.exports = ProfileModel;

 

 

< controller - Cuser.js >

const { Op } = require('sequelize');
const {Player , Profile, Team} = require('../models/index');

exports.getPlayers = async (req, res) =>{
    try{
        const players =await Player.findAll();
        res.send(players);
    }catch(err){
        console.error(err);
        res.send('Internal Server Error!')
    }
}

exports.getPlayer = async (req, res) => {
    try{
        const { player_id } = req.params;
        const player = await Player.findOne({
            where:{
                player_id: player_id
            }
        })
        res.send(player);
    }
    catch(err){
        console.error(err);
        res.send('Internal Server Error!');
    }
}

exports.postPlayer = async (req,res) => {
    try{
        const {name, age, team_id} = req.body;
        const new_palyer = await Player.create(
            {
                name,
                age,
                team_id
            }
        );
        res.send(new_palyer);
    }
    catch(err){
        console.error(err);
        res.send('Internal Server Error!');
    }
}

exports.patchPlayer = async (req, res) => {
    try{
        const {player_id} = req.params;
        const {team_id} = req.body;

        const updatedPlayer = await Player.update(
            {team_id},
            {where : { player_id}}
            );
        res.send(updatedPlayer);
    }
    catch(err){
        console.error(err);
        res.send('Internal Server Error!');
    }
}

exports.deletePlayer = async (req,res) => {
    try{
        const {player_id} = req.params;
        const isDeleted = await Player.destroy({
            where:{
                player_id
            }
        });
        // 성공 시 1, 실패시 0
        //성공시 if문 진입
        if(isDeleted){
            res.send({isDeleted: true});
        }else{
            res.send({isDeleted:false});
        }
    }
    catch(err){
        console.error(err);
        res.send('Internal Server Error!');
    }
}

exports.getTeams = async (req,res) => {
    try{
        // 쿼리 스트링으로 조회 기준 설정
        const {sort, search} = req.query;
        let teams;

        // sort 키가 있는 경우 name 기준으로 오름차순 정렬
        if(sort === 'name_asc'){
            teams = await Team.findAll({
                attributes: ['team_id', 'name'],
                order: [['name', 'asc']]
            })
        } else if(search){
            // search key에 대한 값이 있다면
            teams = await Team.findAll({
                attributes: ['team_id', 'name'],
                where: {
                    name: {[Op.like] : `%${search}%`}
                }
            })
        }else {
            // sort, search 둘다 없는 경우
            teams = await Team.findAll({
                attributes: ['team_id', 'name']
            })
        }
        res.send(teams);
    }  catch(err){
        console.error(err);
        res.send('Internal Server Error!');
    }
}

exports.getTeam = async (req,res) =>{
    try{
        const {team_id} = req.params;
        const team = await Team.findOne({
            attributes: ['team_id','name'],
            where: {
                team_id
            }
        });
        res.send(team);
    }
    catch(err){
        console.error(err);
        res.send('Internal Server Error!');
    }
}

exports.getTeamPlayers = async (req,res) =>{
    try{
        const {team_id} = req.params;
        const team = await Team.findOne({
            where : {team_id},
            include: [{model:Player}] // join과 같은 역할
        });
        res.send(team);
    }
    catch(err){
        console.error(err);
        res.send('Internal Server Error!');
    }
}

 

각 exports들에 들어가는 값들은 api.http에서 부여한다.

 

특정 몇개만 살펴보면

getPlayer는 /:player_id로 값을 받기 때문에 params를 사용한다.

 

getTeams는 sort === 'name_asc' 라면, order을 사용해서 name(이름)이 asc(오름차순)으로 정렬한다.

Op.like는 SQL문에서 LIKE로 %{serch}% => %lg% (~~~lg~~~) 처럼 lg 앞,뒤에 값이 있거나 없는 항목들을 선택

 

getTeamPlayer 에서 include: [{model:Player}]를 보면 해당 값이 join처럼 추가된다.

team_id값이 해당하는 곳들만

 

< api.http >

@server = http://localhost:8000
@player_id = 10

@team_id = 2

### 전체 선수 조회
GET {{server}}/players

### 특정 선수 조회
GET {{server}}/players/{{player_id}}

### 선수 추가
POST {{server}}/players
Content-Type: application/json; charset=UTF-8

{
    "name":"손흥민",
    "age": 30,
    "team_id":2
}

### 특정 소속팀 변경

PATCH {{server}}/players/{{player_id}}/team
Content-Type: application/json; charset=UTF-8

{
    "team_id" : 1
}

### 특정 선수 삭제 (player_id에 해당하는 Player, Profile 연쇄 삭제)
DELETE {{server}}/players/{{player_id}}

### 전체팀 조회
GET {{server}}/teams

### 전체 팀 조회 - 이름순 오름차순
GET {{server}}/teams?sort=name_asc

### 전체 팀 조회 - 팀 이름 검색
GET {{server}}/teams?search=lg

### 특정 팀 조회
GET {{server}}/teams/{{team_id}}

### 특정 팀의 모든 선수 조회G
GET {{server}}/teams/{{team_id}}/players

 

선수들을 추가하거나 변경할 때 Content-Type을 추가해줘야 한다.

또한 추가하거나 변경할 값들을 지정해 준다.

 

< routes - index.js >

const express = require('express');
const router = express.Router();

const controller = require('../controller/Cmain');

//localhost:PORT / 기본주소
// router.get('/',controller.main);

//get으로 players에 요청이간다.
// GET {{server}}/players 전체선수조회
router.get('/players', controller.getPlayers);

// GET /players/:player_id - 특정 선수 조회
router.get('/players/:player_id', controller.getPlayer);

// POST /players - 선수 추가
router.post('/players',controller.postPlayer);

//PATCH /payers/:player_id/team - 특정 선수의 소속 팀 변경
router.all('/players/:player_id/team',controller.patchPlayer);

//Delete /players/:player_id - 특정 선수 삭제
router.delete('/players/:player_id',controller.deletePlayer);


// ------ 팀 관련 API -------------
// GET /teams - 전체 팀 조회
router.get('/teams', controller.getTeams);

// GET /teams/:team_id
router.get('/teams/:team_id',controller.getTeam);

// GET /teams/:team_id/players = 특정 팀의 모든 선수
router.get('/teams/:team_id/players',controller.getTeamPlayers);

module.exports = router;

 

 

 

반응형