909 Devlog

[유튜브 클론코딩] MongoDB 데이터베이스 모델 관계 연결 본문

토이 프로젝트/유튜브 클론코딩

[유튜브 클론코딩] MongoDB 데이터베이스 모델 관계 연결

구공구 2023. 8. 4. 18:28
728x90

지금 제가 만든 프로젝트에는 2가지 데이터 모델이 있습니다.

하나는 Video 모델이고, 하나는 User 모델입니다.

 

지금은 두 데이터베이스 모델들이 연결되어있지 않지만, 곧 구현할 기능을 위해서는 User에는 해당 'user'가 업로드한 'video'들의 정보가 필요하고, Video에는 해당 'video'를 업로드한 'user'의 정보가 필요하니 두 데이터베이스 모델끼리 연결해야 합니다.

 

따라서, User에는 해당 'user'가 업로드한 모든 'video'의 _id를 저장하고, Video에는 해당 'video'를 업로드한 'user'의 _id를 저장해 보겠습니다.

1. 데이터베이스 _id 공유

먼저 videoSchema에 'owner'를 추가해 보도록 하겠습니다.

const videoSchema = new mongoose.Schema({
    title: { type: String, required: true },
    fileUrl: { type: String, required: true },
    description: { type: String, required: true },
    createdAt: { type: Date, required: true, default: Date.now },
    hashtags: [{ type: String }],
    meta: {
        views: { type: Number, default: 0, required: true },
        rating: { type: Number, default: 0, required: true },
    },
    owner: {
        type: mongoose.Schema.Types.ObjectId,
        required: true,
        ref: "User",
    },
});

owner의 옵션을 보면, type이 mongoose.Schema.Types.ObjectId라고 되어 있는 것을 볼 수 있습니다.

owner의 type은 MongoDB에서 부여해준 ObjectId라는 type을 사용하는데, 기본 자바스크립트의 type은 ObjectId와 같은 type이 없으니, mongoose에서 만든 type을 사용합니다.

 

또, ref는 어떤 모델을 연결할지 설정하는 옵션으로, 'User' 모델을 사용한다고 설정해 주었습니다.

위 "User" String은 아래 사진에 빨간 줄로 표시해 놓은 String처럼 Model을 생성할 때 설정해 놓은 이름과 같은 이름을 사용해야 합니다.

이제 postUpload controller에서 영상을 업로드할 때, 사용자의 _id를 보내도록 코드를 작성하겠습니다.

export const postUpload = async (req, res) => {
    const { _id } = req.session.user;
    ...
};

세션에 있는 user값에서 해당 user의 _id를 가져왔으니, 영상 정보를 데이터베이스에 저장할 때, owner도 함께 저장해 주도록 하겠습니다.

export const postUpload = async (req, res) => {
    const { _id } = req.session.user;
    ...
    try {
        await VideoModel.create({
        title: title,
        description: description,
        fileUrl: file.path,
        owner: _id,
        hashtags: VideoModel.formatHashtags(hashtags),
    });
    ...
};

미리 생성해 놓은 계정으로, 영상을 새로 업로드했습니다.

이제 데이터베이스를 보면, 저장되어 있는 user의 _id와, video에 저장되어있는 owner의 _id가 같은 것을 볼 수 있습니다.

1.1 populate

video 모델에, 영상을 업로드한 owner의 정보를 저장해 놓았으니, 이제 영상을 보는 페이지에 영상의 owner 정보를 볼 수 있으면 좋을 것 같습니다.

기존에 했었던 방법인 아래 코드처럼,

const { id } = req.params;
const video = await VideoModel.findById(id);
const owner = await UserModel.findById(video.owner);

video 모델을 찾고, video 모델에 저장되어 있는 owner를 통해 user 모델을 찾아도 동작하지만, populate를 사용하면 더 간편하고 빠르게 동작하는 코드를 작성할 수 있습니다.

 

populate를 사용하는 방법은 아래 코드와 같습니다.

const { id } = req.params;
const video = await VideoModel.findById(id).populate("owner");

video 정보를 찾았던 코드 뒤에 .populate()를 붙여주기만 하면 됩니다.

 

populate가 어떤 역할을 하냐면,

아래처럼 _id만 저장되어 있는 video 데이터의 owner 부분을

통째로 아래처럼 해당 _id를 가진 데이터로 채워줍니다.

해당 _id가 어떤 모델에 저장되어 있는지 확인하기 위해 video 모델의 owner 옵션 부분에 ref를 설정해 주었던 것입니다.

populate에 대한 더 자세한 정보는 공식문서에서 확인하실 수 있습니다.

 

2. user 모델에 video들의 _id 저장

_id들을 저장하기 전에, 모델을 수정해야 하니 데이터베이스에 저장되어 있는 모든 데이터를 지우고 시작합니다.

 

영상은 단 한 명의 업로더만 있지만, 유저는 여러 개의 영상을 가질 수 있으니, video에 저장한 owner와는 다르게  user의 videos를 배열로 만들고, 유저가 업로드한 영상이 없을 수도 있으니, required를 작성하지 않았습니다.

const userschema = new mongoose.Schema({
    email: { type: String, required: true, unique: true },
    avatarUrl: String,
    socialnOnly: { type: Boolean, default: false },
    username: { type: String, required: true, unique: true },
    password: { type: String, required: false },
    name: { type: String, required: true },
    location: String,
    videos: [{ type: mongoose.Schema.Types.ObjectId, ref: "Video" }],
});

이제 영상 업로드를 담당하는 postUpload controller를 다음과 같이 수정합니다.

export const postUpload = async (req, res) => {
    const { _id } = req.session.user;
    const file = req.file;
    const { title, description, hashtags } = req.body;
    try {
        const newVideo = await VideoModel.create({
            title: title,
            description: description,
            fileUrl: file.path,
            owner: _id,
            hashtags: VideoModel.formatHashtags(hashtags),
        });
        const user = await UserModel.findById(_id);
        user.videos.push(newVideo._id);
        user.save();
        return res.redirect("/");
    } catch (error) {
        return res.status(400).render("upload", {
            pageTitle: "Upload Video",
            errorMessage: error._message,
        });
    }
};

위 코드에서 create() 메서드는 create()가 새로 만드는 모델을 return 합니다.

따라서 위 코드는 새로 만든 video 모델을 newVideo라고 저장하고, newVideo를 userschema에 만든 videos 배열에 추가하는 코드입니다.

 

다음으로 업로더가 올린 영상들을 보기 위해, 업로더의 정보를 볼 때 사용하는 컨트롤러인 see controller를 다음과 같이 수정합니다.

export const see = async (req, res) => {
    const { id } = req.params;
    const user = await UserModel.findById(id).populate("videos");
    if (!user) {
        return res.status(404).render("404", { pageTitle: "User not found" });
    }
    return res.render("profile", {
        pageTitle: user.name,
        user: user,
    });
};

video에 populate를 사용했던 것과 비슷하게 user에 "videos"라고 저장되어 있는 부분을 populate 해서 모든 영상 정보들이 담긴 배열이 videos에 나타나게 되었습니다.

 

이제 profile.pug를 다음과 같이 수정하면,

extends base.pug
include mixins/video.pug

block content 
    each video in user.videos 
        +video(video)
    else 
        span Sorry nothing found.

프로필 페이지에 들어갔을 때, 해당 유저가 올린 모든 영상을 볼 수 있게 되었습니다.

 

728x90