Juni_Dev_log

(node.js) [Part.6] 데이터 베이스 사용하기 - 몽구스로 데이터베이스 다루기 본문

Theorem (정리)/node.js

(node.js) [Part.6] 데이터 베이스 사용하기 - 몽구스로 데이터베이스 다루기

Juni_K 2020. 12. 18. 20:16

몽고디비를 사용하려면 하나의 컬렉션 안에 똑같은 속성을 가진 문서 객체를 반복적으로 넣어 둔다면 데이터를 조회할 때도 어떤 속성들이 들어 있는지 미리 알고 있는 상태에서 조회할 수 있다.

따라서 관계형 데이터베이스의 테이블이나 엑셀의 시트(Sheet)처럼 쉽게 다룰 수 있다.

이와 같이 일정한 틀을 제공하는 모듈 중에서 대표적인 것이 바로 몽구스(mongoose)이다. 그러면 이 모듈을 사용하면 데이터를 저장하거나 조회하는 과정이 어떻게 달라지는지 알아보자.

 

몽구스 모듈 사용하기

NoSQL 데이터베이스 중 하나인 몽고디비를 사용하면 문서 객체 안에 들어가는 속성을 마음대로 바꿀 수 있으므로 매우 유연하게 데이터를 저장할 수 있다.

하지만, 컬렉션 안에 들어있는 여러개의 객체를 조회할 때는 제약이 생길 수도 있다.

 

예를 들어, 같은 users 컬렉션 안에 들어있는 문서 객체라도 어떤 문서 객체에는 name 속성이 있는데 반해 다른 문서 객체에는 name 속성이 없을 수도 있다.

 

그래서 관계형 데이터베이스처럼 조회 조건을 공통 적용하기 어려운 문제점이 있다.

이런 문제 때문에 스키마(Schema)를 만들고, 그 스키마에 따라 문서 객체를 저장하는 것이 때로는 편리하다.

특히, 일정한 틀에 맞는 자바스크립트 객체를 그대로 데이터베이스에 저장하거나 일정한 틀에 맞게 구축된 데이터베이스의 문서 객체를 자바스크립트 객체로 바꿀 수 있다면 훨씬 편하게 데이터베이스를 다룰 수 있다.

 

이렇게 자바스크립트 객체와 데이터베이스 객체를 서로 매칭하여 바꿀 수 있게 하는 것오브젝트 매퍼(Object Mapper)라고 한다.

이중에서 가장 많이 사용하는 것이 몽구스이다. 이 모듈을 사용하면 스키마를 만들고 이 스키마에 맞는 모델을 만들어서 데이터를 손쉽게 저장하거나 조회할 수 있다.

 

몽구스를 사용하려면 먼저 명령 프롬프트에서 다음과 같이 mongoose 모듈을 설치한다.

 

% npm install mongoose --save

 

모듈을 설치했으면 mongoose 를 사용해서 데이터베이스에 연결할 수 있다.

app2.js 를 복사해서 app3.js 를 만들고 다음과 같이 require() 메소드로 mongoose 모듈을 불러들이는 코드를 입력한다.

 

// mongoose 모듈 불러들이기
var mongoose = require('mongoose');
메소드 이름 설명
connect(uri(s), [options], [callback]) mongoose를 사용해 데이터베이스에 연결한다.
연결 후에는 mongoose.connection 객체를 사용해서
연결 관련 이벤트를 처리할 수 있다.
Schema() 스키마를 정의하는 생성자이다.
model(name, [schema], [colllection], [skipInit]) 모델을 정의한다.
[collection] 이 지정되면, 이 컬렉션을 사용하며,
지정하지않으면 name으로 유추한 컬렉션을 사용한다.

 

몽구스도 데이터베이스에 연결한 후에 사용할 수 있다.

따라서 connectDB() 함수의 코드를 다음과 같이 수정하여 mongoose 모듈로 데이터베이스에 연결하고, 연결했을 때 전달받는 이벤트를 처리하도록 만든다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
...
// 데이터베이스 객체를 위한 변수 선언
var database;
 
// 데이터베이스 스키마 객체를 위한 변수 선언
var UserSchema;
 
// 데이터베이스 모델 객체를 위한 변수 선언
var UserModel;
 
// 데이터베이스에 연결
function connectDB(){
    // 데이터베이스 연결 정보
    var databaseUrl = 'mongodb://localhost:27017/local';
    
    // 데이터베이스에 연결
    console.log('데이터베이스 연결을 시도합니다.');
    mongoose.Promise = global.Promise;
    mongoose.connect(databaseUrl);
    database = mongoose.connection;
    
    database.on('error'console.error.bind(console'mongoose connection error'));
    database.on('open'function(){
        console.log('데이터베이스에 연결되었습니다. : ' + databaseUrl);
        // 스키마 정의
        UserSchema = mongoose.Schema({
            id: String,
            nameString,
            password: String
        });
        console.log('UserSchema 정의함.');
        
        // UserModel 모델 정의
        UserModel = mongoose.model("users", UserSchema);
        console.log('UserModel 정의함.');
    });
    
    // 연결 끊어졌을 때 5초 후 재연결
    database.on('disconnected'function(){
        console.log('연결이 끊어졌습니다. 5초 후 다시 연결합니다.');
        setInterval(connectDB, 5000);
    });
    
}
...
cs

데이터베이스 연결 정보는 mongodb 모듈로 연결했을 때의 연결정보와 같다.

mongoose 모듈로 데이터베이스에 연결할 때는 connect() 메소드를 호출하면서 동시에 데이터베이스 연결 정보를 파라미터로 넘겨준다. 데이터베이스에 연결되었는지 여부는 mongoose 객체에 들어있는  connection 객체로 전달되는 이벤트를 통해 확인할 수 있다.

 

open 이벤트는 데이터베이스가 연결되었을 때 발생하며, error 이벤트는 데이터베이스에 연결이 제대로 되지 않을 때 발생한다.

disconnected 이벤트는 데이터베이스 연결이 끊어졌을 때 호출되는데 데이터베이스 연결이 끊어져서 disconnected 이벤트가 발생하면 5초 후에 데이터베이스에 다시 연결되도록 connectDB 함수를 호출한다.

 

데이터베이스에 연결되면 이미 만들어 둔 users 컬렉션을 위해 스키마(Schema)를 정의한다.

스키마는 컬렉션이 가지고 있는 속성을 정의한 것이므로 mongoose.Schema() 메소드를 호출하면 새로운 스키마를 만들 수 있다. 스키마를 만들 때는 각 속성의 정보를 넣을 수 있는데, users 컬렉션에서 사용하는 id, name, password 속성을 넣어준다.

 

스키마에 들어가는 각 속성은 스키마 타입(Schema Type)을 가진다. 데이터 타입과 같은 역할을 하는 스키마 타입은 다음과같다.

 

스키마 타입 설명
String 문자열
Number 숫자
Boolean 이진
Array 배열
Buffer 버퍼, 바이너리 데이터를 저장할 수 있다.
Date 날짜
ObjectId 각 문서마다 만들어지는 ObjectId를 저장할 수 있는 타입
Mixed 혼합

 

사용자 정보를 저장하는 스키마에 id , password, name 이외에 나이, 데이터 생성날짜, 데이터 수정 날짜까지 정하고 싶다면 다음과 같이 정의하면된다.

 

var UserSchema = new mongoose.Schema({
id : {type:String, required:true, unique:true},
password : {type:String, required:true},
name : String,
age : Number,
created_at : Date,
updated_at : Date
});

문자열이 들어가는 속성의 스키마 타입을 단순히 String 이라고 지정할 수 있다.

하지만, 더 구체적으로 지정하기 위해 자바스크립트 객체를 전달할 수도 있다. 자바스크립트 객체는 중괄호를 사용해 만들게 되므로 중괄호 안에 각 속성이 가져야 하는 정보를 구체적으로 지정한다.

중괄호 안에 넣은 속성 이름이 갖는 의미는 다음과 같다.

 

속성 이름 설명
type 자료형을 지정한다.
required 값이 true이면 반드시 들어가야하는 속성이 된다.
unique 값이 true 이면 이 속성에 고유한 값이 들어가야한다.

id 속성의 경우 문자열이 들어가면서 값을 NULL로 둘 수 없고 각 데이터들이 각 고유한 값을 가져아한다면, type, required, unique 속성이 모두 들어 있는 자바스크립트 객체를 만들어 스키마 타입으로 지정할 수 있다.

 

여기에서는 이미 만들어 둔 users 컬렉션의 문서 속성과 같도록 맞추기 위해서 id, password, name 속성만으로 정의한다. 스키마는 단순히 구조만 정의하므로 데이터베이스에 들어 있는 컬렉션을 지정하려면 모델을 만들어야한다.

mongoose 객체의 model() 메소드를 사용하면 데이터베이스의 컬렉션을 지정하는 모델 객체를 만들 수 있다.

 

mongoose 의 스키마와 모델간의 관계

 

model() 메소드의 첫 번째 파라미터로는 모델 이름이 전달되고, 두 번째 파라미터에는 스키마 객체가 전달된다.

이렇게 모델 객체를 만들면 모델 객체에 지정한 users 라는 이름은 데이터베이스 컬렉션과 매칭되고 UserSchema 객체는 mongoose.Schema() 메소드로 만든 스키마 객체를 가리키게 된다. 

스키마 객체와 모델 객체 모두 다른 함수에서도 사용할 수 있도록 변수로 정의해둔다.

 

몽구스로 사용자 인증하기

스키마를 만들었으므로 이제 데이터베이스에 들어 있는 users 컬렉션의 데이터를 조회하여 사용자 인증 과정에 적용해볼 차례이다.

그러려면 데이터 조회 방법을 알아야한다.

모델 객체 또는 모델을 사용해서 만들어 낼 인스턴스 객체에는 데이터베이스의 컬렉션에 들어 있는 문서 객체를 조회,수정,삭제 할 수 있는 메소드가 들어있다.

 

다음은 데이터 처리에 사용하는 대표적인 메소드들이다.

 

메소드 이름 설명
find([criteria], [callback]) 조회 조건을 사용해 컬렉션의 데이터를 조회한다.
save([options], [callback]) 모델 인스턴스 객체의 데이터를 저장한다.
저장 결과는 콜백 함수로 전달된다.
update([criteria], [docs], [options], [callback]) 컬렉션의 데이터를 조회한 후 업데이트한다.
where() 메소드와 함께 사용한다.
remove([criteria], [callback]) 컬렉션의 데이터를 삭제한다.

find() 메소드는 컬렉션의 데이터를 조회하는데 사용한다.

모델 객체가 있다면 find() 메소드를 호출해서 조회 조건을 넣어준다.

save() 메소드는 모델 객체가 아닌 모델 인스턴스 객체를 만들어야 사용할 수 있다.

save() 메소드를 호출하면 대상이 되는 모델 인스턴스 객체에 들어있는 속성 값이 저장된다.

update() 메소드는 컬렉션의 데이터를 업데이트하는데 사용된다.

 

예를 들어, 다음 코드를 사용하면 id 속성 값이 test01인 문서 객체를 컬렉션에서 찾아낸 후 name 속성의 값을 애프터스쿨로 바꿀 수 있다.

 

UserModel.where({id : 'test01'}).update({name:'애프터스쿨'}, function(err...) {...})

 

사용자를 인증하는 authUser() 함수의 코드는 다음과 같이 수정한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
...
// 사용자를 인증하는 함수
var authUser = function(database, id, password, callback){
    console.log('authUser 호출됨 : ' + id + ', ' + password + );
                
    // 아이디와 비밀번호를 사용해 검색
    UserModel.find({"id":id, "password":password}, function(err, results){
        if(err){
            callback(err,null);
            return;
        }
        
        console.log('아이디 [%s], 비밀번호 [%s]로 사용자 검색 결과',id,password);
        console.dir(results);
        
        if(results.length > 0) {
            console.log('일치하는 사용자 찾음.', id , password)
            callback(null,results);
        }else{
            console.log('일치하는 사용자 찾지 못함.');
            callback(null,null);
        }
    });
}
...
cs

모델 객체에 UserModel에는 find() 메소드가 들어있으므로 find() 메소드를 호출하면서 id 와 password 속성이 들어있는 자바스크립트 객체를 파라미터로 전달한다.

이렇게 하면 지정된 아이디와 비밀번호로 데이터를 조회할 수 있다. 조회 결과는 콜백 함수로 전달되는데 콜백함수의 첫 번째 파라미터는 오류 객체, 두 번째 파라미터는 결과 객체가 된다.

 

사용자를 등록하는 addUser() 함수는 다음과 같이 수정한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
...
// 사용자를 추가하는 함수
var addUser = function(database, id, password, name, callback){
    console.log('addUser 호출됨 : ' + id + ', ' + password);
    
    //UserModel 의 인스턴스 객체 생성
    var user = new UserModel({"id":id, "password":password, "name":name});
    
    // save()로 저장
    user.save(function(err){
        if(err){
            callback(err,null);
            return;
        }
        console.log('사용자 데이터 추가함.');
        callback(null,user);
    });
    
}
...
cs

데이터를 저장하기 위해 new 연산자와 모델 객체인 UserModel로 새로운 모델 인스턴스를 만든다.

new 연산자로 모델 인스턴스를 만들면 그 객체의 save() 메소드를 호출하여 저장한다. 사용자가 브라우저에서 전달한 id 와 password 값으로 모델 인스턴스를 만들었기 때문에 그 값이 그대로 데이터베이스의 users 컬렉션에 추가된다.

 

이제 app3.js 를 실행하고 localhost:3000/public/login.html 을 연다.

아이디와 비밀번호를 입력하고 전송을 누르면 성공 페이지가 나타난다. 그리고 localhost:3000/public/adduser.html 을 열고 사용자 등록을 요청하면 데이터베이스의 users 컬렉션에 사용자가 추가된다.

 

그리고 /process/adduser 부분도 바뀌는 부분이 있다.

addedUser로 결과를 받고 if 구문에서 조건으로 변경한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
...
router.route('/process/adduser').post(function(req,res){
    console.log('/process/adduser 호출됨.');
    
    var paramId = req.body.id || req.query.id;
    var paramPassword = req.body.password || req.query.password;
    var paramName = req.body.name || req.query.name;
    
    console.log('요청 파라미터 : ' + paramId + ', ' + paramPassword + ', ' + paramName);
    
    // 데이터베이스 객체가 초기화된 경우, addUser 함수 호출하여 사용자 추가
    if (database){
        addUser(database, paramId, paramPassword, paramName, function(err, addedUser){
            if(err) {throw err;}
            
            //결과 객체 확인하여 추가된 데이터 있으면 성공 응답 전송
            if(addedUser){
                console.dir(addedUser);
                
                res.writeHead('200', {'Content-Type':'text/html;charset=utf8'});
                res.write('<h2>사용자 추가 성공</h2>');
                res.end();
            }else{
                res.writeHead('200', {'Content-Type':'text/html;charset=utf8'});
                res.write('<h2>사용자 추가 실패</h2>');
                res.end();
            }
        });
    }else{
        // 데이터베이스 객체가 초기화되지 않는 경우 실패응답 전송
        res.writeHead('200', {'Content-Type':'text/html;charset=utf8'});
        res.write('<h2>데이터베이스 연결 실패</h2>');
        res.end();
    }
    
});
...
cs

 

 

성공적으로 데이터를 추가하고 결과를 확인할 수 있다.

 

 

Comments