node.js 에서 mongodb 를 편하게 사용하는 방법은 Mongoose
로, 설치하여 연결하는 방법은 알아보았습니다.
mongoose 로 데이터 모델을 만들고, CRUD 시스템을 적용하는 방법을 알아봅시다.
위 문서를 바탕으로, 어떤 방식으로 mongoose 를 사용하는지 알아봅시다.
대략적으로는 위와 같습니다.
mongoose 는 스키마
에서 시작합니다. 스키마로 데이터의 형태를 규격화합니다.
모델
은 스키마의 정의에서 컴파일된 생성자로, CRUD 시스템 구현에 사용합니다.
document
는 모델을 이용하여 만들어진 인스턴스로, 개별적인 데이터를 의미합니다.
모델은 데이터 처리를 위해 사용하는 클래스같은 것입니다.
유저 정보를 처리하기 위해서는 User 모델을 만들고, 비디오 정보를 처리하려면 Video 모델을 만듭니다.
만들어진 모델을 이용하여 CRUD 처리를 할 수 있습니다.
모델을 만들기 위해서는 먼저 스키마를 정의해야 합니다.
스키마가 정의한 정보를 구현하여 모델이 만들어지게 됩니다.
Everything in Mongoose starts with a Schema. Each schema maps to a MongoDB collection and defines the shape of the documents within that collection.
(몽구스의 모든 것은 스키마에서 시작합니다. 각 스키마는 MongoDB 컬렉션과 매핑되고 그 컬렉션의 document 형태를 정의합니다.)
mongoose 공식 문서에 나오는 스키마에 대한 설명입니다.
데이터 베이스에는 어떠한 데이터를 정의하는 테이블이 존재하는데, 스키마는 이와 같습니다.
보통, 학생에 대한 정보를 Student 테이블로 정의한다면, Student 테이블에는 학생 이름, 학생 번호, 학생 아이디 등의 속성이 들어갑니다.
이처럼, 한 종류의 데이터를 다루기 위해 그 데이터가 어떤 속성들로 구성되어있는지 정의하는 것이 스키마입니다.
스키마는 mongoose.Schema() 를 통해 생성합니다.
생성된 스키마는 속성과 속성에 대한 정보를 { } 내부에 표현합니다.
import mongoose from "mongoose";
const studentSchema = new mongoose.Schema({
name: String, // 타입만 지정
stunum: { type: Number, maxLength: 10, },
class: [{ type: classSchema, default: "-", required: true }],
// 타입과 함께 세부 옵션 지정
}
학생에 대한 정보를 나타내는 studentSchema 를 만들어보았습니다.
학생 이름, 학생 번호, 수강 과목 등의 정보를 속성으로 가집니다.
속성의 자료형이라고 할 수 있습니다.
문자열(String), 숫자(Number), 배열(Array), 날짜(Date), 다른 스키마 또한 타입이 될 수 있습니다.
배열 타입은 [ ]
내부에 정보를 작성합니다.
{ }
는 document 가 생성될 때 그 데이터에 대한 정보이고, 그를 배열로 감싸고 있으니 들어온 정보가 배열로 처리된다고 직관적으로 알 수 있습니다.
나머지는 공식 문서를 확인합니다.
default
옵션은 그 속성의 기본값을, required
는 그 속성의 값이 필수로 입력되어야 하는지를 나타냅니다.
이러한 옵션을 설정해주면 더 세밀하게 데이터 구조를 짤 수 있습니다.
위 문서에서 각 스키마 타입이 가지는 옵션에 대해 알아볼 수 있습니다.
//Student.js 파일
import mongoose from "mongoose";
const studentSchema = new mongoose.Schema({
name: String, // 타입만 지정
stunum: { type: Number, maxLength: 10, },
class: [{ type: classSchema, default: "-", required: true }],
// 타입과 함께 세부 옵션 지정
}
const studentModel = mongoose.model("Student", studentSchema);
// 모델 생성
export default studentModel;
정의한 스키마를 이용하여 모델을 만들었습니다.
모델이 정의된 Student.js
파일을 import 하면, studentModel
객체를 얻게 되고, 그를 이용하여 CRUD 시스템을 구현할 수 있습니다.
Models are fancy constructors compiled from Schema definitions. An instance of a model is called a document. Models are responsible for creating and reading documents from the underlying MongoDB database.
- 모델은 스키마 정의로 만든 생성자
- 모델의 인스턴스는 도큐먼트
- 모델을 이용하면 MongoDB 데이터베이스의 도큐먼트를 읽어오거나 새로운 도큐먼트를 추가할 수 있음
공식문서에 나온 모델 정의는 위와 같습니다.
더 자세한 정보는 위 문서를 참고합시다.
모델로 document 를 생성하면 고유한 id
가 부여됩니다.
document = { _id: 2834nsd...//(랜덤한 아이디값) ... };
document.id; // 2834nsd... 반환
// id 를 getter 로 사용
이 id 는 ObjetId
라는 타입의 자료입니다.
문서를 읽어보면, ObjectId 는 24-character hexadecimal string
이라는 설명이 나옵니다.
id 는 24문자로 이루어진 16진수 문자열입니다.
모델을 사용하여 컨트롤러에서 DB 에 접근합니다.
데이터를 생성(Create)하고, 제거(Remove)하고, 업데이트(Update)하고, 읽어(Read)옵니다.
mongoose 가 만드는 스키마는 기본적으로 _id 라는 속성을 부여해줍니다.
따라서 생성되는 개별 데이터 (document) 마다 고유한 id 값을 가지게됩니다.
Model 을 이용한 CRUD 시스템은, 이 id
속성을 주로 이용합니다.
주의해야 할 점은, Model 을 이용하는 것은 서로 다른 두 프로세스의 상호작용을 요구한다는 것입니다.
CRUD 시스템에 관련한 코드는 .js
파일에 작성되어, nodeJS
프로세스 상에서 돌아가지만, Model 을 이용하는 코드는 별개의 MongoDB
프로세스에서 돌아갑니다.
즉, nodeJS 에서 요구한 것을 MongoDB 에서 처리해서 결과를 다시 전달해줘야 하는데, 타이밍이 안 맞는 경우가 발생할 수 있습니다.
nodeJS: 데이터 좀 줘
mongo: 기다려~
nodeJS: 데이터 줬지? 다음 코드 진행할게... 함수끝!
mongo: 아직 데이터 안 줬는데???
이런 상황이 발생할 수 있습니다.
이를 해결하려면 mongo
가 데이터를 다 줄 때까지 JS 코드의 진행을 대기해야겠죠?
그를 위해 async-await 를 사용합니다.
const controller = async (req, res) => {
await 모델을 이용한 코드 부분
return ...
}
이렇게 해주면, 모델 처리 부분이 먼저 끝나야 그 이하의 구문을 실행하게 됩니다.
Model 의 API 를 이용해서 데이터를 처리하는 방법을 알아봅시다.
import Student from "models/Student"; // studentModel 을 받아옴
Student.create({
name: "Amy",
stunum: 20201111,
class: ["math", "english"]
});
// Model.create( { 속성: 값 } );
create( )
를 이용하여, 파라미터로 { } 내부에 스키마에 정의된 속성: 값 정보를 보내주면 보내준 정보에 맞는 개별 데이터, document
가 DB 에 생성됩니다.
Student.find({}); // Video 타입으로 존재하는 모든 doc 을 반환
Student.find({ name: "Amy" });
// title 속성의 값이 "Amy" 인 모든 doc 을 반환
// { } 내부에 속성값에 대한 조건을 명시하여 필터링할 수 있음
Student.find({
name: {
$regex: new RegExp("A", "i")
// 정규식 사용. 대소문자를 구분하지 않는 옵션 i
}
});
// name 에 "A" 나 "a" 를 포함하는 모든 doc 을 반환
Student.findById(id);
// mongoose 가 자동으로 부여해주는 id 속성값으로 찾기
find( )
를 이용하여 데이터베이스에서 데이터를 읽어올 수 있습니다.
Student.exists({ name: "Amy" });
// name 이 "Amy" 인 doc 가 있다면, { _id: ... } 형태로 doc 반환
// 없다면 null 반환
데이터 전체를 가져오지 않더라도, 데이터가 DB에 존재하는지의 여부를 exists() 로 확인할 수 있습니다.
const query = { name: "Amy Santiago", stunum: 20201234 };
Student.findByIdAndUpdate(id, query);
// id 에 해당하는 doc 의 정보를 query 의 내용대로 수정
query 를 이용하여 개별 doc 의 수정할 내용을 지정할 수 있습니다.
Student.findByIdAndDelete(id);
해당 id 를 가지는 document 가 데이터베이스에서 삭제됩니다.
Model 을 이용할 때, 해당 코드에 async-await 처리를 사용하는 것을 잊지 맙시다.
CRUD 동작을 수행할 때, 미들웨어를 설정할 수 있습니다.
모델의 동작은 위와 같은 미들웨어를 발동시킵니다.
미들웨어는 모델을 정의한 파일에 같이 작성합니다.
새로운 Student.create( ) 로 새로운 학생 정보를 저장할 때, class 속성에 국어 과목을 더하는 미들웨어를 작성해봅시다.
//Student.js 파일
import mongoose from "mongoose";
const studentSchema = new mongoose.Schema({
name: String, // 타입만 지정
stunum: { type: Number, maxLength: 10, },
class: [{ type: classSchema, default: "-", required: true }],
// 타입과 함께 세부 옵션 지정
}
studentSchema.pre("save", async function () {
if(!this.class.includes("korean"){
this.class.push("korean");
}
// class 에 korean 이 포함되지 않았다면 더해주기
// this 는 현재 save 한 document
});
const studentModel = mongoose.model("Student", studentSchema);
// 모델 생성
export default studentModel;