개인 개발 프로젝트/Graphql, MongoDB 실습

[Graphql, MongoDB 실습] 7. 1:N 관계 예시 (1)

종범2 2020. 8. 8. 22:48

User에 Booking의 Id를 배열로 저장하는 방법 구현

이번에는 User와 Booking이 1:N 관계일 때 User에 Booking의 Id를 배열로 저장하는 예시를 구현한다.

 

models/user1.js

const mongoose = require('mongoose');
const { Schema } = mongoose;
const user1Schema = new Schema({
  name: {
    type: String,
    required: true
  },
  booking1Ids: [{
    type:Schema.Types.ObjectId,
    ref:'Booking1'
  }],
  createdAt: {
    type: Date,
    default: Date.now,
  },
});

module.exports = mongoose.model('User1', user1Schema);

models/booking.js

const mongoose = require('mongoose');
const { Schema } = mongoose;
const booking1Schema = new Schema({
  name: {
    type: String,
    required: true
  },
  createdAt: {
    type: Date,
    default: Date.now,
  },
});

module.exports = mongoose.model('Booking1', booking1Schema);

우선 mongoose를 이용하여 스키마를 정의한다. 위에서 설명한 대로 user1에는 booking1Ids라는 속성이 있고 booking1의 ID를 배열로 저장한다.

 

graphql/schema/index.js

const { gql } = require('apollo-server');
const typeDefs = gql`
  type Query {
    user1s: [User1]
    booking1s: [Booking1]
  }
  type Booking1{
    _id: ID
    name: String
    user1: User1
    createdAt: String
  }
  input Booking1Input{
    name: String
    user1Id: String
  }
  type User1{
    _id: ID
    name: String
    booking1s: [Booking1]
    createdAt: String
  }
  input User1Input{
    name: String
  }
  type Mutation{
    createUser1(user1Input: User1Input): User1!
    createBooking1(booking1Input: Booking1Input): Booking1!
  }
`;

module.exports = typeDefs;

Query, Booking1, User1, Mutation 타입을 정의하고 Booking1Input, User1Input 인풋을 정의한다. mongoose에서 정의한 스키마에서는 Booking1에 User1의 Id가 없었지만 Graphql에서는 Booking1이 어떤 User1에 포함되는지까지 요청할 수 있기 때문에 다음과 같이 작성할 수 있다.

 

graphql/resolvers/index.js

const User1 = require('../../models/user1');
const Booking1 = require('../../models/booking1');
const { startSession } = require('mongoose');
const resolvers = {
  Query: {
    async user1s(_, args){
      try {
        const user1s = await User1.find();
        return user1s;
      } catch (err) {
        console.log(err);
        throw err;
      }
    },
    async booking1s(_, args) {
      try {
        const booking1s = await Booking1.find();
        return booking1s;
      } catch (err) {
        console.log(err);
        throw err;
      }
    },
  },
  User1: {
    async booking1s(_,args){
      const booking1s = await Booking1.find({ _id: { $in: _.booking1Ids } })
      return booking1s
    },
  },
  Booking1: {
    async user1(_, args) {
      try {
        const user1 = await User1.findOne({ booking1Ids: { $in: _._id } })
        console.log(user1);
        return user1;
      } catch (err) {
        console.log(err);
        throw err;
      }
    },
  },
  Mutation: {
    async createUser1(_, args){
      try {
        const user1 = new User1({
          ...args.user1Input
        })
        const result = await user1.save();
        return result;
      } catch (err) {
        console.log(err);
        throw err;
      }
    },
    async createBooking1(_, args) {
      const session = await startSession();
      try {
        session.startTransaction();
        const booking1 = new Booking1({
          name: args.booking1Input.name
        })
        const result = await booking1.save({ session });
        await User1.findByIdAndUpdate(args.booking1Input.user1Id,
          { $push: { booking1Ids: result._id } },
          { session: session, useFindAndModify: false }
        );
        await session.commitTransaction();
        return result;
      } catch (error) {
        await session.abortTransaction();
        console.log(error);
        throw error;
      } finally {
        await session.endSession();
      }
    },
  }
};

Query의 user1s 함수는 모든 User1를 반환하고, booking1s 함수는 모든 Booking1을 반환한다. User1 Type에 대한 요청은 User1에서 담당한다. 이때 User1의 booking1s를 요청할 때 Booking1의 Id를 배열로 반환하지 않고 Id로 조회한 Booking1 배열로 반환해야 한다. 이를 위해 booking1s의 경우는 별도의 로직을 작성한다. Booking1의 user1을 요청할 때에도 마찬가지로 별도의 로직을 작성한다. Mutation의 createUser1는 새로운 User1를 생성한다. createBooking1에서는 새로운 Booking1을 저장하고, 생성된 Booking1의 Id를 User1의 booking1Ids에 추가해야 한다. 두 로직은 항상 같이 수행되어야 하므로 transaction으로 묶었다.

 

Playground 실행

애플리케이션을 실행하고 playground에서 우선 user를 생성한다.

mutation{
  createUser1(user1Input:{
    name:"Kim"
  }){
    _id
    name
  }
}

결과는 다음과 같다.

 

다음으로는 생성된 User1의 _id를 이용하여 Booking1을 생성한다.

mutation{
  createBooking1(booking1Input:{
    name:"Booking Movie"
    user1Id:"5f2eabcb3d54a918f461cdf8"
  }){
    _id
    name
    user1{
      _id
      name
    }
  }
}

결과는 다음과 같다.

 

이제 생성된 User1나 Booking1의 정보를 조회한다.

query{
  user1s{
    _id
    name
    booking1s{
      _id
      name
    }
  }
}

query{
  booking1s{
    _id
    name
    user1{
      _id
      name
    }
  }
}