Mongoose는 Node.js에서 MongoDB와 상호작용하기 위해 사용하는 Object Data Modeling(ODM) 라이브러리이다.
MongoDB는 스키마 없이 데이터를 저장할 수 있는 NoSQL 데이터베이스이지만, Mongoose를 사용하면 스키마를 정의하여 데이터 구조를 체계적으로 관리할 수 있다.
스키마 정의
데이터를 저장하기 전에 구조를 정해 관리할 수 있다.
유효성 검사
스키마를 정의하면서 데이터의 조건을 설정할 수 있다.
간단한 쿼리 작성
데이터를 검색하거나 수정하는 과정을 간소화할 수 있다.
미들웨어 제공
데이터 저장, 업데이트 등의 작업 전후에 특정 로직을 실행할 수 있다.
가상 필드 지원
데이터베이스에 저장되지 않는 계산된 값을 사용할 수 있다.
Mongo와 Mongoose를 연결하는 방법은 다음과 같다.
Mongoose를 사용하려면 먼저 패키지를 설치해야 한다.
npm install mongoose
MongoDB 서버가 실행 중이어야 한다.
로컬 MongoDB를 사용할 경우, 기본적으로 mongodb://localhost:27017 경로에서 실행된다.
MongoDB Atlas와 같은 클라우드 서비스를 사용할 경우, 제공되는 연결 URL을 사용한다.
Mongoose를 사용하여 MongoDB와 연결하는 기본 코드는 다음과 같다.
const mongoose = require('mongoose');
// MongoDB 연결
mongoose.connect('mongodb://localhost:27017/mydatabase', {
useNewUrlParser: true,
useUnifiedTopology: true
})
.then(() => console.log('MongoDB 연결 성공'))
.catch(err => console.error('MongoDB 연결 실패:', err));
옵션 설명
useNewUrlParser: URL 구문 분석을 새 방식으로 처리하기 위한 옵션이다.
useUnifiedTopology: MongoDB 드라이버의 새 연결 관리 엔진을 사용하기 위한 옵션이다.
Mongoose에서는 find, findOne 메서드로 데이터를 검색할 수 있다.
const User = mongoose.model('User', userSchema);
// 모든 데이터 찾기
User.find({})
.then(users => console.log(users))
.catch(err => console.error(err));
// 특정 조건에 맞는 데이터 찾기
User.find({ age: { $gte: 18 } }) // 나이가 18 이상인 데이터
.then(users => console.log(users))
.catch(err => console.error(err));
// 한 개의 데이터만 찾기
User.findOne({ name: 'Alice' })
.then(user => console.log(user))
.catch(err => console.error(err));
// ID로 찾기
User.findById('ObjectId값')
.then(user => console.log(user))
.catch(err => console.error(err));
Mongoose는 updateOne, updateMany, findByIdAndUpdate 메서드로 데이터를 업데이트할 수 있다.
// 한 개의 데이터 업데이트
User.updateOne({ name: 'Alice' }, { age: 26 })
.then(result => console.log('업데이트 성공:', result))
.catch(err => console.error(err));
// 여러 데이터 업데이트
User.updateMany({ age: { $lt: 18 } }, { isMinor: true }) // 18세 미만 데이터에 `isMinor: true` 추가
.then(result => console.log('업데이트 성공:', result))
.catch(err => console.error(err));
// ID로 데이터 업데이트
User.findByIdAndUpdate('ObjectId값', { email: 'updated@example.com' }, { new: true }) // 업데이트된 결과 반환
.then(updatedUser => console.log('업데이트된 데이터:', updatedUser))
.catch(err => console.error(err));
옵션 설명
new: 업데이트된 값을 반환하기 위해 사용한다. (기본값은 업데이트 이전 값 반환)
rawResult: MongoDB가 반환하는 원본 결과를 얻고 싶을 때 사용한다.
Mongoose에서는 deleteOne, deleteMany, findByIdAndDelete로 데이터를 삭제할 수 있다.
// 한 개의 데이터 삭제
User.deleteOne({ name: 'Alice' })
.then(result => console.log('삭제 성공:', result))
.catch(err => console.error(err));
// 여러 데이터 삭제
User.deleteMany({ isMinor: true }) // 조건에 맞는 모든 데이터 삭제
.then(result => console.log('삭제 성공:', result))
.catch(err => console.error(err));
// ID로 데이터 삭제
User.findByIdAndDelete('ObjectId값')
.then(deletedUser => console.log('삭제된 데이터:', deletedUser))
.catch(err => console.error(err));
const mongoose = require('mongoose');
// MongoDB 연결
mongoose.connect('mongodb://localhost:27017/mydatabase', {
useNewUrlParser: true,
useUnifiedTopology: true
})
.then(() => console.log('MongoDB 연결 성공'))
.catch(err => console.error('MongoDB 연결 실패:', err));
// 스키마 및 모델 정의
const userSchema = new mongoose.Schema({
name: String,
age: Number,
email: String,
isMinor: Boolean
});
const User = mongoose.model('User', userSchema);
// 데이터 작업 예제
async function main() {
try {
// 1. 데이터 찾기
console.log('\n=== 데이터 찾기 ===');
const allUsers = await User.find({});
console.log('모든 사용자:', allUsers);
const adultUsers = await User.find({ age: { $gte: 18 } });
console.log('성인 사용자:', adultUsers);
const singleUser = await User.findOne({ name: 'Alice' });
console.log('Alice 사용자:', singleUser);
const userById = await User.findById('64bfc456a789123456789abc'); // ID를 실제 값으로 변경해야 함
console.log('ID로 찾은 사용자:', userById);
// 2. 데이터 업데이트
console.log('\n=== 데이터 업데이트 ===');
const updatedUser = await User.updateOne({ name: 'Alice' }, { age: 26 });
console.log('업데이트 결과:', updatedUser);
const multipleUpdates = await User.updateMany({ age: { $lt: 18 } }, { isMinor: true });
console.log('여러 데이터 업데이트 결과:', multipleUpdates);
const updatedById = await User.findByIdAndUpdate(
'64bfc456a789123456789abc', // ID를 실제 값으로 변경해야 함
{ email: 'updated@example.com' },
{ new: true }
);
console.log('ID로 업데이트된 사용자:', updatedById);
// 3. 데이터 삭제
console.log('\n=== 데이터 삭제 ===');
const deletedUser = await User.deleteOne({ name: 'Alice' });
console.log('한 개 데이터 삭제 결과:', deletedUser);
const deletedMultipleUsers = await User.deleteMany({ isMinor: true });
console.log('여러 데이터 삭제 결과:', deletedMultipleUsers);
const deletedById = await User.findByIdAndDelete('64bfc456a789123456789abc'); // ID를 실제 값으로 변경해야 함
console.log('ID로 삭제된 사용자:', deletedById);
} catch (err) {
console.error('오류 발생:', err);
} finally {
// MongoDB 연결 종료
await mongoose.disconnect();
console.log('MongoDB 연결 종료');
}
}
// 함수 실행
main();
Mongoose는 스키마를 정의할 때 데이터의 타입(type)과 기타 조건을 설정하여 기본적인 유효성 검사를 제공한다.
| 옵션 | 설명 | 예제 | 비고 |
|---|---|---|---|
required | 필수 필드 여부를 지정. 값이 없으면 에러 발생. | { name: { type: String, required: true } } | 필수 필드 |
type | 필드 데이터의 타입을 지정 (String, Number, Boolean 등). | { age: { type: Number } } | 데이터 타입 |
min | 숫자 타입에 적용. 최소값 설정. | { age: { type: Number, min: 0 } } | 최소값 조건 |
max | 숫자 타입에 적용. 최대값 설정. | { age: { type: Number, max: 120 } } | 최대값 조건 |
match | 문자열 타입에 적용. 정규식을 이용해 형식 검사. | { email: { type: String, match: /.+\@.+\..+/ } } | 정규식 검사 |
enum | 문자열 값이 특정 목록에 포함되어야 함. | { role: { type: String, enum: ['admin', 'user'] } } | 허용된 값 |
default | 값이 없는 경우 기본값을 지정. | { status: { type: String, default: 'active' } } | 기본값 설정 |
const userSchema = new mongoose.Schema({
name: { type: String, required: true }, // 필수 값
age: { type: Number, min: 0, max: 120 }, // 최소값, 최대값
email: { type: String, match: /.+\@.+\..+/ }, // 정규식 조건
});
const User = mongoose.model('User', userSchema);
const newUser = new User({ name: 'Alice', age: -5, email: 'invalidEmail' });
newUser.save()
.then(() => console.log('유효성 검사를 통과한 데이터 저장'))
.catch(err => console.error('유효성 검사 실패:', err.message));
mongoose는 업데이트 유효성 검사를 사용 안 하면 데이터 업데이트시 유효성 검사를 하지 않는다.
updateOne, updateMany, findOneAndUpdate와 같은 메서드에서 유효성 검사를 하려면 runValidators 옵션을 true로 설정해야 한다.
runValidators : true로 설정하면 업데이트 시에도 유효성 검사를 수행한다.
new : 업데이트된 결과를 반환받을지 여부를 결정. true로 설정하면 업데이트된 데이터를 반환하고, false로 설정하면 업데이트 전 데이터를 반환한다.
const mongoose = require('mongoose');
// 스키마 정의
const userSchema = new mongoose.Schema({
name: { type: String, required: true },
age: { type: Number, min: 18, max: 100 }
});
const User = mongoose.model('User', userSchema);
// 데이터 업데이트 예시
async function updateUser() {
try {
const updatedUser = await User.findOneAndUpdate(
{ name: 'Alice' },
{ age: 15 }, // 15는 유효하지 않음
{ runValidators: true, new: true } // 유효성 검사 실행
);
console.log('업데이트된 사용자:', updatedUser);
} catch (err) {
console.error('업데이트 오류:', err.message);
}
}
updateUser();
인스턴스 메서드는 Mongoose 문서 인스턴스에 바인딩되어 있다.
즉, 문서 객체에서 호출할 수 있는 메서드로, 특정 문서에 대해 작업을 수행하는 데 사용된다.
인스턴스 메서드는 스키마 인스턴스에서 methods 객체를 사용하여 정의한다.
const mongoose = require('mongoose');
// 스키마 정의
const userSchema = new mongoose.Schema({
name: { type: String, required: true },
age: { type: Number, required: true }
});
// 인스턴스 메서드 정의
userSchema.methods.sayHello = function () {
return `안녕하세요, ${this.name}입니다.`;
};
const User = mongoose.model('User', userSchema);
// 인스턴스 메서드 사용
async function createUser() {
const user = new User({ name: 'Alice', age: 25 });
console.log(user.sayHello()); // "안녕하세요, Alice입니다."
}
createUser();
정적 메서드는 모델 자체에 바인딩된 메서드로, 문서 인스턴스가 아닌 모델 클래스에서 호출된다.
정적 메서드는 모델을 통해 데이터베이스 작업을 처리하거나 모델에 관련된 작업을 정의할 때 사용된다.
정적 메서드는 스키마의 statics 객체를 사용하여 정의한다.
const mongoose = require('mongoose');
// 스키마 정의
const userSchema = new mongoose.Schema({
name: { type: String, required: true },
age: { type: Number, required: true }
});
// 정적 메서드 정의
userSchema.statics.findByName = function (name) {
return this.find({ name: name });
};
const User = mongoose.model('User', userSchema);
// 정적 메서드 사용
async function searchUser() {
const users = await User.findByName('Alice'); // User 모델에서 호출
console.log(users); // 'Alice' 이름을 가진 사용자들 출력
}
searchUser();
this의 차이
- 인스턴스 메서드는 특정 문서 인스턴스에 대해 동작하며, this는 인스턴스를 참조한다.
- 정적 메서드는 모델 자체에 대해 동작하며, this는 모델을 참조한다.
가상 속성은 schema.virtual 메서드를 사용하여 정의한다.
get과 set을 사용해 가상 속성의 값을 가져오거나 설정할 수 있다.
get 예제const mongoose = require('mongoose');
// 스키마 정의
const userSchema = new mongoose.Schema({
firstName: { type: String, required: true },
lastName: { type: String, required: true },
});
// 가상 속성 정의: fullName
userSchema.virtual('fullName').get(function () {
return `${this.firstName} ${this.lastName}`;
});
const User = mongoose.model('User', userSchema);
// 데이터 생성 및 조회 예시
async function createUser() {
const user = new User({ firstName: 'John', lastName: 'Doe' });
console.log(user.fullName); // "John Doe"
}
createUser();
set 예제const mongoose = require('mongoose');
// 스키마 정의
const userSchema = new mongoose.Schema({
firstName: { type: String, required: true },
lastName: { type: String, required: true },
});
// 가상 속성 정의: fullName
userSchema.virtual('fullName')
.get(function () {
return `${this.firstName} ${this.lastName}`;
})
.set(function (value) {
const [firstName, lastName] = value.split(' ');
this.firstName = firstName;
this.lastName = lastName;
});
const User = mongoose.model('User', userSchema);
// 데이터 생성 및 조회 예시
async function createUser() {
const user = new User({ firstName: 'John', lastName: 'Doe' });
// 가상 속성 설정
user.fullName = 'Alice Wonderland';
console.log(user.firstName); // "Alice"
console.log(user.lastName); // "Wonderland"
}
createUser();
pre 미들웨어pre 미들웨어는 특정 작업이 실행되기 전에 코드를 실행할 수 있게 한다.
예를 들어, 문서를 저장하거나 업데이트하기 전에 데이터 검증 작업을 할 수 있다.
const mongoose = require('mongoose');
// 스키마 정의
const userSchema = new mongoose.Schema({
name: { type: String, required: true },
age: { type: Number, required: true }
});
// `save` 전 미들웨어 정의
userSchema.pre('save', function (next) {
if (this.age < 18) {
next(new Error('나이는 18세 이상이어야 합니다.'));
} else {
next(); // 나이가 18세 이상이면 저장을 계속 진행
}
});
const User = mongoose.model('User', userSchema);
// 데이터 생성 및 저장 예시
async function createUser() {
const user = new User({ name: 'John', age: 17 });
try {
await user.save();
} catch (err) {
console.error('오류:', err.message); // "나이는 18세 이상이어야 합니다."
}
}
createUser();
post 미들웨어post 미들웨어는 작업이 완료된 후에 실행된다.
예를 들어, 데이터베이스에 문서를 저장한 후에 알림을 보내거나 로그를 기록하는 작업을 할 수 있다.
const mongoose = require('mongoose');
// 스키마 정의
const userSchema = new mongoose.Schema({
name: { type: String, required: true },
age: { type: Number, required: true }
});
// `save` 후 미들웨어 정의
userSchema.post('save', function (doc) {
console.log(`${doc.name}님이 저장되었습니다.`);
});
const User = mongoose.model('User', userSchema);
// 데이터 생성 및 저장 예시
async function createUser() {
const user = new User({ name: 'Alice', age: 30 });
await user.save(); // "Alice님이 저장되었습니다." 출력
}
createUser();