대외활동/코드잇부스트_백엔드 1기

codeIt! DEMO day! 개발 3일차) models_mongoDB사용하기, 시딩하기

피스타0204 2024. 8. 23. 23:33

 

 

블로그 목차 서식 5

1. mongoose 사용하기

2.

 

 

 

1. mongoose 사용하기

 

터미널에서 npm install mongoose로 mongoose라이브러리를 사용할 수 있습니다.

npm i mongoose

 

Mongoose는 자바스크립트를 사용해서 MongoDB 데이터베이스와 소통할 수 있게 해 주는 라이브러리입니다. Mongoose가 제공하는 API를 이용해서 데이터베이스에 접속하고 CRUD(Create, Read, Update, Delete) 연산을 할 수 있습니다.

 

mongoose를 사용하면 강제 스키마를 사용할 수 있고 join같은 역할을 하는 populate를 쓸 수 있습니다. promise, callback사용이 가능합니다. 또, 편리한 쿼리 빌더를 사용할 수 있기 때문에 많이 사용됩니다.

 

2.mongoDB Atlas를 통해 데이터베이스를 생성하고 연결 URL을 준비하는 방법

MongoDB를 사용하는 방법은 두가지가 있습니다. 로컬 MongoDB를 직접 설치하거나 MongoDB Atlas를 사용하는 것입니다. MongoDB Atlas를 사용하면 간단히 데이터베이스를 생성할 수 있고 쉽게 배포할 수 있고 schema를 만들 필요가 없기 때문에 우리는 이번에 MongoDB Atlas를 이용해 프로젝트를 진행할 것입니다.

  • 로컬 MongoDB: MongoDB 소프트웨어를 컴퓨터에 직접 설치하는 방법
  • 클라우드(원격) MongoDB: MongoDB 회사는 MongoDB Atlas라는 클라우드 서비스를 제공합니다. 이 서비스를 이용하면 클라우드에 MongoDB 데이터베이스를 생성하고 인터넷으로 데이터베이스에 연결해서 사용할 수 있습니다.

아래 링크를 참고하여 mongoDB Atlas를 만들어봅시다. 만약 같이 협업하는 사람이 이미 데이터베이스를 준비해놓았다면 mongoDB Atlas 설정할 필요없이 그 사람에게 비밀번호와 링크를 가져다가 사용하면 됩니다.

https://turtle0204.tistory.com/entry/mongoDB-Atlas-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0

 

mongoDB Atlas 사용하기

1. mongoDB Atlas를 통해 데이터베이스를 생성하고 연결 URL을 준비하는 방법1) mongoDB ATlas 홈페이지에 가서 회원가입을 하고 Overview 페이지로 이동합니다.https://cloud.mongodb.com/v2/667e798b2ad38554850cc0ea#/overv

turtle0204.tistory.com

 

협업하는 사람에게 env.js에 들어갈 내용을 받아와서 사용해봅시다.

export const DATABASE_URL =
  'mongodb+srv://codeit:<password>@mongodb-cluster.t5eg2vz.mongodb.net/todo-api?retryWrites=true&w=majority';

붙여 넣을 때 <password> 부분을 기록해 둔 비밀번호로 대체하고 mongodb.net/ 뒤에 데이터베이스 이름(예: todo-api)을 써줘야 합니다. 예를 들어 아이디가 codeit, 비밀번호가 test123이고 데이터베이스 이름이 todo-api라면 아래와 같은 형태가 되는 거예요.

 

app.js 파일에서 아래와 같이 작성해 주세요.

//env.js
import mongoose from 'mongoose';
import { DATABASE_URL } from './env.js';

// ...

mongoose.connect(DATABASE_URL).then(() => console.log('Connected to DB'));


이제 Express나 Next.js 같은 프로그램에서 이 URL을 통해 MongoDB 데이터베이스에 접속할 수 있는 겁니다. 복사해서 .env 파일에 붙여넣고 app.js에서 import하면 내 MongoDB Atlas와 내 vscode의 코드가 연결됩니다.

3. models, routes, data 폴더 작성하기- 코드 작성하기

models, routes, data폴더는 각각, 데이터스키마 관리, 기능별 파일관리, 시딩파일 모음의 역할 합니다. 

routes는 저번 포스트 때 설명했고 이번에 데이터베이스를 사용하게 해주는 models부터 설명해보겠습니다.

 

1) models

models 폴더는 mongoose Atlas에서 컬렉션을 저장합니다. 예를 들어 models 폴더 안에 post.js와 comment.js가 있다면 데이터베이스 안에는 Post, Comment(이름은 개발자가 정의함)라는 두 컬렉션이 생기게 됩니다. 마치 우리가 폴더를 나눠서 파일을 저장하는 것처럼 데이터베이스>컬렉션>게시글JSON데이터 가 저장되게 됩니다.

 

models가 데이터베이스에서 무슨 역할을 하는지 알아보았다면 이제부터는 models안, post.js의 코드를 설명하겠습니다. post.js에서는 데이터의 스키마를 정의합니다. 데이터베이스 스키마(database schema)는 데이터베이스 내에서 데이터가 어떤 구조로 저장되는지를 나타냅니다. 테이블 이름, 필드, 데이터 형식 및 이러한 엔티티 간의 관계를 스키마에서 정의할 수 있습니다. 

post.js에서 정의한 데이터의 스키마는 데이터베이스에 데이터를 저장하는 코드를 작성할 때 불려와 데이터의 형식을 틀처럼 찍어내줍니다. 마치 c++(객체지향)에서 class가 그런 것처럼 nickname, password, title, content 등을 가진 post라는 형식으로 데이터들을 정리할수 있습니다.

 

이제 직접 코드를 보면서 설명해볼까요?


 

코드 상세설명

import mongoose from 'mongoose';

 

우리가 살펴볼 코드에서는 MongoDB와 Node.js의 Mongoose 라이브러리를 사용해 데이터베이스에 저장할 문서(Document)의 구조를 정의합니다.

// 게시글 스키마 정의
const PostSchema = new mongoose.Schema(
  {
    groupId: {
      type: Number,
      required: true,
    },
    nickname: {
      type: String,
      required: true,
    },
    title: {
      type: String,
      required: true,
    },
    content: {
      type: String,
      required: true,
    },
    postPassword: {
      type: String,
      required: true,
    },
    groupPassword: {
      type: String,
      required: true,
    },
    imageUrl: {
      type: String,
      required: false, // 선택적 필드
    },
    tags: {
      type: [String],
      required: false, // 선택적 필드
    },
    location: {
      type: String,
      required: false, // 선택적 필드
    },
    moment: {
      type: Date,
      required: true,
    },
    isPublic: {
      type: Boolean,
      required: true,
    },
    likeCount: {
      type: Number,
      default: 0,
    },
    commentCount: {
      type: Number,
      default: 0,
    }
  },
  {
    timestamps: true, // createdAt, updatedAt 자동 생성
  }
);

 

groupId, nickname, title같은 각 필드(field) 또는 속성(attribute)이 어떤 데이터 유형(type)을 가질 수 있는지, 필수(required) 여부 등을 설정합니다.

mongoose에서는 String, Number, Date, Boolean, Array, ObjectId 6가지 type을 제공합니다.

required가 true로 설정된 field는 반드시 값을 가져야 하고 false로 설정되면 선택적으로 값을 가져도 됩니다.

default는 해당 필드에 값이 주어지지 않았을 때, 기본적으로 할당될 값을 정의합니다. 예를들어 likeCount는 문서 생성 시 제공되지 않으면 자동으로 0이 할당됩니다.

 

  • type: 필드에 저장될 데이터의 유형을 정의합니다.
  • required: 필수 여부를 정의하여 필드가 문서에 반드시 포함되어야 하는지 결정합니다.
  • default: 필드에 값이 주어지지 않았을 때 사용할 기본값을 정의합니다.

또, 각 게시물을 나타내는 id필드는 mongoose에서 자동으로 관리해주므로 작성할 필요가 없습니다.

마지막으로 timestamps를 정의해주면 createdAt, updatedAt도 mongoose에서 자동으로 관리해줍니다.

// Post 모델 정의
const Post = mongoose.model('Post', PostSchema);

 

 

mongoose.model(컬렉션이름, 정의한스키마);로 모델을 정의할 수 있습니다. 

export default Post;

 

정의한 모델을 export합니다.

import express from 'express';
import Post from '../models/post.js';  // Mongoose Post 모델을 가져옵니다.
const postRouter = express.Router();

//...


export한 모델은 routes/post.js에서 사용할 것입니다.

 

전체 models/post.js 코드▼

더보기
import mongoose from 'mongoose';

// 게시글 스키마 정의
const PostSchema = new mongoose.Schema(
  {
    groupId: {
      type: Number,
      required: true,
    },
    nickname: {
      type: String,
      required: true,
    },
    title: {
      type: String,
      required: true,
    },
    content: {
      type: String,
      required: true,
    },
    postPassword: {
      type: String,
      required: true,
    },
    groupPassword: {
      type: String,
      required: true,
    },
    imageUrl: {
      type: String,
      required: false, // 선택적 필드
    },
    tags: {
      type: [String],
      required: false, // 선택적 필드
    },
    location: {
      type: String,
      required: false, // 선택적 필드
    },
    moment: {
      type: Date,
      required: true,
    },
    isPublic: {
      type: Boolean,
      required: true,
    },
    likeCount: {
      type: Number,
      default: 0,
    },
    commentCount: {
      type: Number,
      default: 0,
    }
  },
  {
    timestamps: true, // createdAt, updatedAt 자동 생성
  }
);

// Post 모델 정의
const Post = mongoose.model('Post', PostSchema);

export default Post;

2) routes

routes는 각 기능별로 코드를 나누어 관리하기 위해 사용합니다. 따라서 API 경로를 바탕으로 라우터를 나누는 것보다

  • routes/posts.js: 게시글 관련 API
  • routes/comments.js: 댓글 관련 API

다음과 같이 기능을 기준으로 라우터를 나누는 것이 선호됩니다.


 

post, get, put, delete에 대해서 routes/comments.js의 코드를 보면서 설명해보겠습니다.

4가지 모두 try-catch문을 사용해 500번으로 나머지 오류를 제외합니다.

 

post)

            const { nickname, content, password } = req.body;
			// 새로운 댓글 생성
            const newComment = new Comment({ nickname, content, password, postId });
            // 댓글 저장
            await newComment.save();

const {변수1, 변수2,...}= req.body;로 보낼 데이터들을 변수에 저장하고, import한 Comment모델에 따라 데이터를 생성하여 newComment에 할당합니다.

그리고 할당한 데이터를 실제 데이터베이스에 저장합니다. 


            const { postId } = req.params; // URL 파라미터에서 postId 추출

url에서 가져올 수 있는 정보를 req.params로 저장할 수 있습니다.


            // 모든 필드 검증
            if (!nickname || !content || !password || !postId) {
                return res.status(400).json({ message: "잘못된 요청입니다" });
            }

required : true인 필드들이 모두 있는지 검사하고 없다면 400에러를 발생시킵니다.


            // 성공적인 응답
            return res.status(200).json({
                id: newComment._id,
                nickname: newComment.nickname,
                content: newComment.content,
                createdAt: newComment.createdAt
            });

성공적인 응답을 받으면 돌려줄 response입니다.



전체 코드 ▼

더보기
import express from 'express';
import Comment from '../models/comment.js';  // Mongoose Comment 모델을 가져옵니다.
const commentsRouter = express.Router();

commentsRouter.route('/posts/:postId/comments')
    .post(async (req, res) => {
        try {
            const { nickname, content, password } = req.body;
            const { postId } = req.params; // URL 파라미터에서 postId 추출

            // 모든 필드 검증
            if (!nickname || !content || !password || !postId) {
                return res.status(400).json({ message: "잘못된 요청입니다" });
            }

            // 새로운 댓글 생성
            const newComment = new Comment({ nickname, content, password, postId });

            // 댓글 저장
            await newComment.save();

            // 성공적인 응답
            return res.status(200).json({
                id: newComment._id,
                nickname: newComment.nickname,
                content: newComment.content,
                createdAt: newComment.createdAt
            });
        } catch (error) {
            // 에러 처리
            return res.status(500).json({ message: "서버 오류가 발생했습니다", error });
        }
    });

export default commentsRouter;

get)
get에서는 페이지네이션이라는 개념이 등장합니다.

commentId, page, pageSize를 사용하여 페이지네이션을 처리합니다.

 

나머지는 아래 링크로 코드를 봐주세요.

https://github.com/A0-J/codeit_demo

 

GitHub - A0-J/codeit_demo

Contribute to A0-J/codeit_demo development by creating an account on GitHub.

github.com

 

 

 


4. 시딩하기

 

//data/mock.js
const posts = [
    {
        groupId: 123,
        nickname: 'JohnDoe',
        title: 'My First Post',
        content: 'This is the content of the post.',
        postPassword: '1234',
        groupPassword: 'abcd',
        imageUrl: 'http://example.com/image.png',
        tags: ['tag1', 'tag2'],
        location: 'Seoul',
        moment: new Date('2024-02-21'),
        isPublic: true,
        likeCount: 0,
        commentCount: 0,
    },
    {
        groupId: 123,
        nickname: 'JaneDoe',
        title: 'Another Post',
        content: 'Here is some different content.',
        postPassword: '5678',
        groupPassword: 'abcd',
        imageUrl: 'http://example.com/image2.png',
        tags: ['tag3'],
        location: 'Busan',
        moment: new Date('2024-03-01'),
        isPublic: true,
        likeCount: 2,
        commentCount: 5,
    }
];

export default posts;

 

//seed.js  //루트 디렉터리
import mongoose from 'mongoose';
import Post from './models/post.js';  // Post 모델을 임포트합니다.
import posts from './data/mock.js';  // 시드 데이터를 임포트합니다.
import { DATABASE_URL } from './env.js';  // 데이터베이스 URL을 임포트합니다.

mongoose.connect(DATABASE_URL, { useNewUrlParser: true, useUnifiedTopology: true })
    .then(async () => {
        console.log('Connected to the database.');

        // 기존의 Post 컬렉션 데이터를 모두 삭제합니다.
        await Post.deleteMany({});
        console.log('Old posts removed.');

        // 시드 데이터를 추가합니다.
        await Post.insertMany(posts);
        console.log('Seed data added.');

        mongoose.disconnect();  // 데이터베이스 연결을 종료합니다.
        console.log('Disconnected from the database.');
    })
    .catch(error => {
        console.error('Error connecting to the database:', error);
    });

 

node seeds.js