5 NodeJS로 MongoDB 다루기

source: categories/study/database-mongodb/database5.md

5.1 Mongoose Connection

이제 MongoDB를 NodeJS로 연결해서 데이터를 실제로 생성하고 수정하는 API를 만들겠습니다.

5.1.1 User 관련 API 만들기

몽고DB를 노드JS에 어떻게 연결하느냐.

위 드라이버들을 이용해서 각 언어에서 몽고DB를 제어한다.

노드JS 부분으로 들어가면 설명이 있다.
CRUD는 어떻게하는지, 이전 강의에서 다뤘던 프로미스 콜백 관련된 내용도 있고 여러가지 설명들이 있다.

저희가 초반에 몽고DB 콤파스를 통해 몽고DB를 수정했던 적이 있잖아?
몽고DB 콤파스의 터미널창을 활용해 데이터를 넣고 빼고 수정하고를 해봤었다.
그것도 사실 몽고DB 드라이버이다.
이를 그대로 사용할 수 있다.

위 퀵 스타트에 나와있는 설명대로 하면된다.

npm i monogodb

그런데 우리는 위 monogodb 모듈을 사용하지않고 mongoose라는 모듈을 사용할 것이다.
mongoose를 쓰는 이유는 mongoose는 내부적으로 mongodb 모듈을 사용하고 있다.
그런데 mongoose는 좀 더 저희가 사용하기 편이한 편이 기능들을 가지고 있다.
문법은 동일하지만 편이 기능들을 가지고 있기 때문에 mongoose를 설치하도록 하겠다.

npm i mongoose

첫번째 인자로 연결할 uri을 넣어야한다.

const express = require('express');
const app = express();
const mongoose = require('mongoose');

const users = [];

const MONGO_URI = 'mongodb+srv://hyungju-lee:<password>@mongodbtutorial.2ulmc.mongodb.net/<dbname>?retryWrites=true&w=majority'

let result = mongoose.connect(MONGO_URI);
console.log(result);

app.use(express.json());

app.get('/user', function (req, res) {
    return res.send({users: users});
})

app.post('/user', function (req, res) {
    users.push({ name: req.body.name, age: req.body.age });
    return res.send({success: true})
})

app.listen(3000, function () {
    console.log('server listening on port 3000');
})

// { result: Promise { <pending> } }
// 위와 같이 result가 Promise 팬딩상태를 리턴한다. 
// 바로 연결 성공된 걸 리턴하는 것이아니라 지금은 몽고DB와 커넥션하고있고
// 그 다음에 커넥션을해 무언가를 하면 그때 값을 리턴하는 것이다. resolve든 reject든
const express = require('express');
const app = express();
const mongoose = require('mongoose');

const users = [];

const MONGO_URI = 'mongodb+srv://hyungju-lee:<password>@mongodbtutorial.2ulmc.mongodb.net/<dbname>?retryWrites=true&w=majority'

mongoose.connect(MONGO_URI).then(result => console.log({result}));

app.use(express.json());

app.get('/user', function (req, res) {
    return res.send({users: users});
})

app.post('/user', function (req, res) {
    users.push({ name: req.body.name, age: req.body.age });
    return res.send({success: true})
})

app.listen(3000, function () {
    console.log('server listening on port 3000');
})

// server listening on port 3000

// {
//     result: Mongoose {
//     connections: [ [NativeConnection] ],
//         models: {},
//     modelSchemas: {},
//     events: EventEmitter {
//         _events: [Object: null prototype] {},
//         _eventsCount: 0,
//             _maxListeners: undefined,
//             [Symbol(kCapture)]: false
//     },
//     options: {
//         pluralization: true,
//             autoIndex: true,
//             useCreateIndex: false,
//             [Symbol(mongoose:default)]: true
//     },
//     _pluralize: [Function: pluralize],
//     Schema: [Function: Schema] {
//         reserved: [Object: null prototype],
//         Types: [Object],
//             ObjectId: [Function]
//     },
//     model: [Function (anonymous)],
//         plugins: [ [Array], [Array], [Array], [Array], [Array] ]
// }
// }
const express = require('express');
const app = express();
const mongoose = require('mongoose');

const users = [];

const MONGO_URI = 'mongodb+srv://hyungju-lee:<password>@mongodbtutorial.2ulmc.mongodb.net/<dbname>?retryWrites=true&w=majority'

const server = async () => {
    let mongodbConnection = await mongoose.connect(MONGO_URI);
    console.log(mongodbConnection);

    app.use(express.json());

    app.get('/user', function (req, res) {
        return res.send({users: users});
    })

    app.post('/user', function (req, res) {
        users.push({ name: req.body.name, age: req.body.age });
        return res.send({success: true})
    })

    app.listen(3000, function () {
        console.log('server listening on port 3000');
    })
}

server();

// {
//     result: Mongoose {
//     connections: [ [NativeConnection] ],
//         models: {},
//     modelSchemas: {},
//     events: EventEmitter {
//         _events: [Object: null prototype] {},
//         _eventsCount: 0,
//             _maxListeners: undefined,
//             [Symbol(kCapture)]: false
//     },
//     options: {
//         pluralization: true,
//             autoIndex: true,
//             useCreateIndex: false,
//             [Symbol(mongoose:default)]: true
//     },
//     _pluralize: [Function: pluralize],
//     Schema: [Function: Schema] {
//         reserved: [Object: null prototype],
//         Types: [Object],
//             ObjectId: [Function]
//     },
//     model: [Function (anonymous)],
//         plugins: [ [Array], [Array], [Array], [Array], [Array] ]
// }
// }

// server listening on port 3000

위와 같이 코드를 수정했을 때 중요한 차이가있다.
위와 같이 수정하면 server listening on port 3000 메세지가 나중에 나온다.
이 말은 몽고DB를 먼저 연결하고난 후 서버가 나중에 실행되었다는 뜻이다.
이는 매우 중요하다.

DB가 연결이 안된 상태에서 request를 받기 시작하면 오류가날 것이다.
GET 요청으로 user를 찾는 요청이왔는데 DB가 아직 연결이 안되었다?
그럼 user를 못 찾을거잖아요?

물론 위와 같이 async/await를 사용 안하고 then 메소드에 코드를 다 넣어서 만들 수도 있다.
하지만 async/await가 정리하기 더 편하다는점.

// try catch 처리
const express = require('express');
const app = express();
const mongoose = require('mongoose');

const users = [];

const MONGO_URI = 'mongodb+srv://hyungju-lee:<password>@mongodbtutorial.2ulmc.mongodb.net/<dbname>?retryWrites=true&w=majority'

const server = async () => {
    try {
        await mongoose.connect(MONGO_URI);

        app.use(express.json());

        app.get('/user', function (req, res) {
            return res.send({users: users});
        })

        app.post('/user', function (req, res) {
            users.push({ name: req.body.name, age: req.body.age });
            return res.send({success: true})
        })

        app.listen(3000, function () {
            console.log('server listening on port 3000');
        })
    } catch (err) {
        console.log(err);
    }
}

server();

위와 같이 deprecationWarning 메시지가 싫으시다면, 에러메시지에 나와있는대로 수정하시면 된다.
mongoose.connect(MONGO_URI);의 두번째 인자로 옵션을 넣을 수 있다고 했잖아?
아래와 같이 위 메시지에서 명시한 옵션값을 넣으면 워닝 메시지가 안나오게된다.

const express = require('express');
const app = express();
const mongoose = require('mongoose');

const users = [];

const MONGO_URI = 'mongodb+srv://hyungju-lee:<password>@mongodbtutorial.2ulmc.mongodb.net/<dbname>?retryWrites=true&w=majority'

const server = async () => {
    try {
        await mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true });

        app.use(express.json());

        app.get('/user', function (req, res) {
            return res.send({users: users});
        })

        app.post('/user', function (req, res) {
            users.push({ name: req.body.name, age: req.body.age });
            return res.send({success: true})
        })

        app.listen(3000, function () {
            console.log('server listening on port 3000');
        })
    } catch (err) {
        console.log(err);
    }
}

server();

5.2 Schema & Model

// model/User.js
const {Schema, model} = require('mongoose');

// Schema 첫번째 인자 : 해당 데이터 값의 정의
// Schema 두번째 인자 : 옵션
const UserSchema = new Schema({
    username: {type: String, required: true},
    name: {
        first: {type: String, required: true},
        last: {type: String, required: true}
    },
    age: Number, // required: true 가 아니라면 이렇게 type만 넣을 수도 있다.
    email: String,
}, {
    timestamps: true // createdAt(생상한 시간)을 자동으로 넣어준다.
                     // 그리고 update할 때마다 updateAt이라는 키를 수정해준다.
                     // 언제 생성되고 언제 수정되었는지를 전부 자동으로 넣어준다.
})

// 위와 같이 만들었으면 mongoose한테 알려줘야된다.
const User = model('user', UserSchema); // 몽구스에 알려주는 것이다. user라는 콜렉션을 만들거야. 그리고 그 콜렉션은 위와 같은 데이터형태를 가지고 있어.
// 실제 콜렉션은 users 라는 이름으로 뒤에 s가 붙어서 생성이 된다.

module.exports = { User };
// src/server.js
const express = require('express');
const app = express();
const mongoose = require('mongoose');
const {User} = require('./models/User'); // 이 User로 이제 데이터베이스 작업을 할 수 있다.

const users = [];

const MONGO_URI = 'mongodb+srv://hyungju-lee:fEMZ9UEE9iSEdb6m@mongodbtutorial.2ulmc.mongodb.net/BlogService?retryWrites=true&w=majority'

const server = async () => {
    try {
        await mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true });

        app.use(express.json());

        app.get('/user', function (req, res) {
            return res.send({users: users});
        })

        app.post('/user', function (req, res) {
            users.push({ name: req.body.name, age: req.body.age });
            return res.send({success: true})
        })

        app.listen(3000, function () {
            console.log('server listening on port 3000');
        })
    } catch (err) {
        console.log(err);
    }
}

server();

package.json

{
  "name": "mongodb-tutorial",
  "version": "1.0.0",
  "description": "",
  "main": "src/server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node src/server.js",
    "dev": "nodemon src/server.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.17.1",
    "mongoose": "^5.13.5"
  },
  "devDependencies": {
    "nodemon": "^2.0.12"
  }
}

5.3 User Document 생성하기

// src/server.js
const express = require('express');
const app = express();
const mongoose = require('mongoose');
const {User} = require('./models/User'); // 이 User로 이제 데이터베이스 작업을 할 수 있다.

const MONGO_URI = 'mongodb+srv://hyungju-lee:fEMZ9UEE9iSEdb6m@mongodbtutorial.2ulmc.mongodb.net/BlogService?retryWrites=true&w=majority'

const server = async () => {
    try {
        await mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true });

        app.use(express.json());

        app.get('/user', (req, res) => {

        })

        app.post('/user', async (req, res) => {
            const user = new User(req.body); // 원래는 { username: req.body.username, name: { first: ... } 이런식으로 넣으면되는데,
                                             // req.body가 user의 형태와 똑같다고 가정을하고 req.body 객체를 이렇게 통채로 넣어준다.
            // 위 const user = new User(req.body) 코드의 역할은 document(=row) 생성이다. 즉, 이 코드를 통해 document 인스턴스가 생성된다.
            // 그리고나서 document(=row)를 DB에 저장해야되니까
            // 아래와 같이 몽구스에서 추가된 save라는 메소드를 user.save() 이런식으로 호출하는데,
            // 이 save() 메소드는 Promise 인스턴스를 return하고 document를 돌려준다.
            // 그래서 user.save() 앞엔 await 키워드를 붙여야된다.
            await user.save(); // 그리고 이렇게 user를 저장해준다.
            return res.send({user})
        })

        app.listen(3000, () => console.log('server listening on port 3000'))
    } catch (err) {
        console.log(err);
    }
}

server();

name.last, name.first는 필수인데, 이 값들이 없다 라는 에러이다.

5.4 Express에서 오류 처리하기

위와 같이 에러가나면서 응답은 무한로딩이 되고있다.
그러면 클라이언트(브라우저)에선 어떤 오류인지 알 수가 없잖아?
이렇게되면 안된다.
이 에러를 해결해보자.

// src/server.js
const express = require('express');
const app = express();
const mongoose = require('mongoose');
const {User} = require('./models/User'); // 이 User로 이제 데이터베이스 작업을 할 수 있다.

const MONGO_URI = 'mongodb+srv://hyungju-lee:fEMZ9UEE9iSEdb6m@mongodbtutorial.2ulmc.mongodb.net/BlogService?retryWrites=true&w=majority'

const server = async () => {
    try {
        await mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true });

        app.use(express.json());

        app.get('/user', (req, res) => {

        })

        app.post('/user', async (req, res) => {
            // try catch 처리를 꼭 해주자.
            try {
                const user = new User(req.body); // 원래는 { username: req.body.username, name: { first: ... } 이런식으로 넣으면되는데,
                                                 // req.body가 user의 형태와 똑같다고 가정을하고 req.body 객체를 이렇게 통채로 넣어준다.
                // 위 const user = new User(req.body) 코드의 역할은 document(=row) 생성이다. 즉, 이 코드를 통해 document 인스턴스가 생성된다.
                // 그리고나서 document(=row)를 DB에 저장해야되니까
                // 아래와 같이 몽구스에서 추가된 save라는 메소드를 user.save() 이런식으로 호출하는데,
                // 이 save() 메소드는 Promise 인스턴스를 return하고 document를 돌려준다.
                // 그래서 user.save() 앞엔 await 키워드를 붙여야된다.
                await user.save(); // 그리고 이렇게 user를 저장해준다.
                return res.send({user})
            } catch (err) {
                // catch에서 잡히는 에러는 서버에서 난 에러이다.
                // user가 값을 잘못 입력해서나는 에러는 이미 try 구문에서 다 잡힌다.
                // 그래서 500번대 status를 return한다.
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.listen(3000, () => console.log('server listening on port 3000'))
    } catch (err) {
        console.log(err);
    }
}

server();


지금 위 경우는 서버 에러가 아닌, 클라이언트가 잘못 입력해서 발생하는 에러잖아?
그래서 이러한 에러를 위한 처리 또한 해준다.

// src/server.js
const express = require('express');
const app = express();
const mongoose = require('mongoose');
const {User} = require('./models/User'); // 이 User로 이제 데이터베이스 작업을 할 수 있다.

const MONGO_URI = 'mongodb+srv://hyungju-lee:fEMZ9UEE9iSEdb6m@mongodbtutorial.2ulmc.mongodb.net/BlogService?retryWrites=true&w=majority'

const server = async () => {
    try {
        await mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true });

        app.use(express.json());

        app.get('/user', (req, res) => {

        })

        app.post('/user', async (req, res) => {
            // try catch 처리를 꼭 해주자.
            try {
                let {username, name} = req.body;
                if (!username) return res.status(400).send({ err: "username is required" });
                if (!name || !name.first || !name.last) return res.status(400).send({err: "Both first and last names are required"});

                const user = new User(req.body); // 원래는 { username: req.body.username, name: { first: ... } 이런식으로 넣으면되는데,
                // req.body가 user의 형태와 똑같다고 가정을하고 req.body 객체를 이렇게 통채로 넣어준다.
                // 위 const user = new User(req.body) 코드의 역할은 document(=row) 생성이다. 즉, 이 코드를 통해 document 인스턴스가 생성된다.
                // 그리고나서 document(=row)를 DB에 저장해야되니까
                // 아래와 같이 몽구스에서 추가된 save라는 메소드를 user.save() 이런식으로 호출하는데,
                // 이 save() 메소드는 Promise 인스턴스를 return하고 document를 돌려준다.
                // 그래서 user.save() 앞엔 await 키워드를 붙여야된다.
                await user.save(); // 그리고 이렇게 user를 저장해준다.
                return res.send({user})
            } catch (err) {
                // catch에서 잡히는 에러는 서버에서 난 에러이다.
                // user가 값을 잘못 입력해서나는 에러는 이미 try 구문에서 다 잡힌다.
                // 그래서 500번대 status를 return한다.
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.listen(3000, () => console.log('server listening on port 3000'))
    } catch (err) {
        console.log(err);
    }
}

server();

위와 같이 정확한 데이터를 입력해 요청보내면 에러가 안나는 것을 알 수 있다.
위 코드에서 user.save() 코드까지 완료되면, return res.send({user}) 이렇게 user를 반환하도록 했다.
그래서 위에 user가 반환된 것이다.

5.5 GET/user

이번엔 생성된 user들을 전부 불러와보도록 하겠다.

// src/server.js
const express = require('express');
const app = express();
const mongoose = require('mongoose');
const {User} = require('./models/User'); // 이 User로 이제 데이터베이스 작업을 할 수 있다.

const MONGO_URI = 'mongodb+srv://hyungju-lee:fEMZ9UEE9iSEdb6m@mongodbtutorial.2ulmc.mongodb.net/BlogService?retryWrites=true&w=majority'

const server = async () => {
    try {
        await mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true });

        app.use(express.json());

        app.get('/user', async (req, res) => {
            try {
                const users = await User.find({}); // .find({}) : 안에 인자값으로 {} 빈객체를 넣는다. 특별한 조건이 없다는 뜻.
                                                   // 즉, 전체 user를 불러오는 메소드이다.
                                                   // .find() 메소드는 반환값이 배열이다.
                return res.send({users});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.post('/user', async (req, res) => {
            // try catch 처리를 꼭 해주자.
            try {
                let {username, name} = req.body;
                if (!username) return res.status(400).send({ err: "username is required" });
                if (!name || !name.first || !name.last) return res.status(400).send({err: "Both first and last names are required"});

                const user = new User(req.body); // 원래는 { username: req.body.username, name: { first: ... } 이런식으로 넣으면되는데,
                // req.body가 user의 형태와 똑같다고 가정을하고 req.body 객체를 이렇게 통채로 넣어준다.
                // 위 const user = new User(req.body) 코드의 역할은 document(=row) 생성이다. 즉, 이 코드를 통해 document 인스턴스가 생성된다.
                // 그리고나서 document(=row)를 DB에 저장해야되니까
                // 아래와 같이 몽구스에서 추가된 save라는 메소드를 user.save() 이런식으로 호출하는데,
                // 이 save() 메소드는 Promise 인스턴스를 return하고 document를 돌려준다.
                // 그래서 user.save() 앞엔 await 키워드를 붙여야된다.
                await user.save(); // 그리고 이렇게 user를 저장해준다.
                return res.send({user})
            } catch (err) {
                // catch에서 잡히는 에러는 서버에서 난 에러이다.
                // user가 값을 잘못 입력해서나는 에러는 이미 try 구문에서 다 잡힌다.
                // 그래서 500번대 status를 return한다.
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.listen(3000, () => console.log('server listening on port 3000'))
    } catch (err) {
        console.log(err);
    }
}

server();


// src/server.js
const express = require('express');
const app = express();
const mongoose = require('mongoose');
const {User} = require('./models/User'); // 이 User로 이제 데이터베이스 작업을 할 수 있다.

const MONGO_URI = 'mongodb+srv://hyungju-lee:fEMZ9UEE9iSEdb6m@mongodbtutorial.2ulmc.mongodb.net/BlogService?retryWrites=true&w=majority'

const server = async () => {
    try {
        await mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true });

        app.use(express.json());

        app.get('/user', async (req, res) => {
            try {
                const users = await User.findOne({}); // findOne()을 하면 1개만 리턴을한다.
                return res.send({users});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.post('/user', async (req, res) => {
            // try catch 처리를 꼭 해주자.
            try {
                let {username, name} = req.body;
                if (!username) return res.status(400).send({ err: "username is required" });
                if (!name || !name.first || !name.last) return res.status(400).send({err: "Both first and last names are required"});

                const user = new User(req.body); // 원래는 { username: req.body.username, name: { first: ... } 이런식으로 넣으면되는데,
                // req.body가 user의 형태와 똑같다고 가정을하고 req.body 객체를 이렇게 통채로 넣어준다.
                // 위 const user = new User(req.body) 코드의 역할은 document(=row) 생성이다. 즉, 이 코드를 통해 document 인스턴스가 생성된다.
                // 그리고나서 document(=row)를 DB에 저장해야되니까
                // 아래와 같이 몽구스에서 추가된 save라는 메소드를 user.save() 이런식으로 호출하는데,
                // 이 save() 메소드는 Promise 인스턴스를 return하고 document를 돌려준다.
                // 그래서 user.save() 앞엔 await 키워드를 붙여야된다.
                await user.save(); // 그리고 이렇게 user를 저장해준다.
                return res.send({user})
            } catch (err) {
                // catch에서 잡히는 에러는 서버에서 난 에러이다.
                // user가 값을 잘못 입력해서나는 에러는 이미 try 구문에서 다 잡힌다.
                // 그래서 500번대 status를 return한다.
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.listen(3000, () => console.log('server listening on port 3000'))
    } catch (err) {
        console.log(err);
    }
}

server();


다시 find 메소드로 수정한다.
그리고 데이터를 하나 더 입력해보자.

// src/server.js
const express = require('express');
const app = express();
const mongoose = require('mongoose');
const {User} = require('./models/User'); // 이 User로 이제 데이터베이스 작업을 할 수 있다.

const MONGO_URI = 'mongodb+srv://hyungju-lee:fEMZ9UEE9iSEdb6m@mongodbtutorial.2ulmc.mongodb.net/BlogService?retryWrites=true&w=majority'

const server = async () => {
    try {
        await mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true });

        app.use(express.json());

        app.get('/user', async (req, res) => {
            try {
                const users = await User.find({});
                return res.send({users});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.post('/user', async (req, res) => {
            // try catch 처리를 꼭 해주자.
            try {
                let {username, name} = req.body;
                if (!username) return res.status(400).send({ err: "username is required" });
                if (!name || !name.first || !name.last) return res.status(400).send({err: "Both first and last names are required"});

                const user = new User(req.body); // 원래는 { username: req.body.username, name: { first: ... } 이런식으로 넣으면되는데,
                // req.body가 user의 형태와 똑같다고 가정을하고 req.body 객체를 이렇게 통채로 넣어준다.
                // 위 const user = new User(req.body) 코드의 역할은 document(=row) 생성이다. 즉, 이 코드를 통해 document 인스턴스가 생성된다.
                // 그리고나서 document(=row)를 DB에 저장해야되니까
                // 아래와 같이 몽구스에서 추가된 save라는 메소드를 user.save() 이런식으로 호출하는데,
                // 이 save() 메소드는 Promise 인스턴스를 return하고 document를 돌려준다.
                // 그래서 user.save() 앞엔 await 키워드를 붙여야된다.
                await user.save(); // 그리고 이렇게 user를 저장해준다.
                return res.send({user})
            } catch (err) {
                // catch에서 잡히는 에러는 서버에서 난 에러이다.
                // user가 값을 잘못 입력해서나는 에러는 이미 try 구문에서 다 잡힌다.
                // 그래서 500번대 status를 return한다.
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.listen(3000, () => console.log('server listening on port 3000'))
    } catch (err) {
        console.log(err);
    }
}

server();

5.6 Unique Index

똑같은 user를 하나 더 생성해보도록 하겠다.

위와 같이 생성이 되는 것을 확인할 수 있다.
_id는 바뀌었다.

즉, 중복으로 데이터가 생성이 안되도록하려면 어떻게 해야되느냐.

위와 같이 unique: true 옵션값을 넣어주면된다.
그런데 터미널창에 deprecate 메시지가 나온다.
메시지를 보면 Use createIndexes라고 되어있다.

Index라는 개념을 나중에 설명드리긴할건데, unique라는 옵션값을 정의한 것도 이것 또한 일종의 index이다.
그래서 이거대신 createIndexes를 사용하라는 문구인 것이다.
기존에는 ensureIndex라는 것을 썼는데 이건 옛날 방식이고 createIndexes를 써라.

위와 같이 설정을 해준다.
그럼 더이상 경고메시지가 생기지않는다.


다시 같은 데이터를 생성해보도록 하겠다.

그런데 여전히 같은 데이터가 생성이된다.
이 이유는 usernameunique로 만들기 전에 이미 중복된 데이터가 있었잖아?
그래서 uniqueIndex를 생성을 못한 것이다.
즉, 중복된 데이터들을 먼저 제거를 해줘야된다.
일단 데이터베이스에서 수동으로 제거하겠다.

index로 설정된 것이 _id밖에 없다. 현재는.

서버를 재실행하면 이때 몽구스가 무슨일을 하냐면, 각 모델별로 index 정보들을 확인해 index를 생성하라고한다.
몽구스를 실행할 때마다 이런 index가 계속 재생성되는 것은 아니고, 몽고DB에서 이 index를 생성했었는지 아닌지 확인해보고
생성 안했었으면 그때 생성하고 이미 생성된거면 넘어가고 그렇게 한다.

이제 다시 새로고침을하면 위와 같이 usernameindex에 추가된 것을 확인할 수 있다.
만약 index를 안쓴다면 휴지통모양을 눌러서 삭제를 해주시면된다.
이 상태에서, 이제는 중복된 데이터가 없잖아?
중복된 데이터를 다시 요청보내보도록 하겠다.

위와 같은 에러가 발생한다.
username key가 중복되었다는 에러이다.

// src/server.js
const express = require('express');
const app = express();
const mongoose = require('mongoose');
const {User} = require('./models/User'); // 이 User로 이제 데이터베이스 작업을 할 수 있다.

const MONGO_URI = 'mongodb+srv://hyungju-lee:fEMZ9UEE9iSEdb6m@mongodbtutorial.2ulmc.mongodb.net/BlogService?retryWrites=true&w=majority'

const server = async () => {
    try {
        await mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true });

        app.use(express.json());

        app.get('/user', async (req, res) => {
            try {
                const users = await User.find({});
                return res.send({users});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.post('/user', async (req, res) => {
            // try catch 처리를 꼭 해주자.
            try {
                let {username, name} = req.body;
                if (!username) return res.status(400).send({ err: "username is required" });
                if (!name || !name.first || !name.last) return res.status(400).send({err: "Both first and last names are required"});

                const user = new User(req.body); // 원래는 { username: req.body.username, name: { first: ... } 이런식으로 넣으면되는데,
                // req.body가 user의 형태와 똑같다고 가정을하고 req.body 객체를 이렇게 통채로 넣어준다.
                // 위 const user = new User(req.body) 코드의 역할은 document(=row) 생성이다. 즉, 이 코드를 통해 document 인스턴스가 생성된다.
                // 그리고나서 document(=row)를 DB에 저장해야되니까
                // 아래와 같이 몽구스에서 추가된 save라는 메소드를 user.save() 이런식으로 호출하는데,
                // 이 save() 메소드는 Promise 인스턴스를 return하고 document를 돌려준다.
                // 그래서 user.save() 앞엔 await 키워드를 붙여야된다.
                await user.save(); // 그리고 이렇게 user를 저장해준다.
                return res.send({user})
            } catch (err) {
                // catch에서 잡히는 에러는 서버에서 난 에러이다.
                // user가 값을 잘못 입력해서나는 에러는 이미 try 구문에서 다 잡힌다.
                // 그래서 500번대 status를 return한다.
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.listen(3000, () => console.log('server listening on port 3000'))
    } catch (err) {
        console.log(err);
    }
}

server();
// models/User.js
const {Schema, model} = require('mongoose');

// Schema 첫번째 인자 : 해당 데이터 값의 정의
// Schema 두번째 인자 : 옵션
const UserSchema = new Schema({
    username: {type: String, required: true, unique: true},
    name: {
        first: {type: String, required: true},
        last: {type: String, required: true}
    },
    age: Number, // required: true 가 아니라면 이렇게 type만 넣을 수도 있다.
    email: String,
}, {
    timestamps: true // createdAt(생상한 시간)을 자동으로 넣어준다.
                     // 그리고 update할 때마다 updateAt이라는 키를 수정해준다.
                     // 언제 생성되고 언제 수정되었는지를 전부 자동으로 넣어준다.
})

// 위와 같이 만들었으면 mongoose한테 알려줘야된다.
const User = model('user', UserSchema); // 몽구스에 알려주는 것이다. user라는 콜렉션을 만들거야. 그리고 그 콜렉션은 위와 같은 데이터형태를 가지고 있어.
// 실제 콜렉션은 users 라는 이름으로 뒤에 s가 붙어서 생성이 된다.

module.exports = { User };

5.7 GET/user/:userId

  • GET/user/:userId 특정 id를 가진 데이터를 불러오는 API를 만들어보도록 하겠다.
// src/server.js
const express = require('express');
const app = express();
const mongoose = require('mongoose');
const {User} = require('./models/User'); // 이 User로 이제 데이터베이스 작업을 할 수 있다.

const MONGO_URI = 'mongodb+srv://hyungju-lee:fEMZ9UEE9iSEdb6m@mongodbtutorial.2ulmc.mongodb.net/BlogService?retryWrites=true&w=majority'

const server = async () => {
    try {
        await mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true });

        app.use(express.json());

        app.get('/user', async (req, res) => {
            try {
                const users = await User.find({});
                return res.send({users});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        // userId를 요청하는 API를 만들어준다.
        app.get('/user/:userId', async (req, res) => {
            console.log(req.params);
        })

        app.post('/user', async (req, res) => {
            // try catch 처리를 꼭 해주자.
            try {
                let {username, name} = req.body;
                if (!username) return res.status(400).send({ err: "username is required" });
                if (!name || !name.first || !name.last) return res.status(400).send({err: "Both first and last names are required"});

                const user = new User(req.body); // 원래는 { username: req.body.username, name: { first: ... } 이런식으로 넣으면되는데,
                // req.body가 user의 형태와 똑같다고 가정을하고 req.body 객체를 이렇게 통채로 넣어준다.
                // 위 const user = new User(req.body) 코드의 역할은 document(=row) 생성이다. 즉, 이 코드를 통해 document 인스턴스가 생성된다.
                // 그리고나서 document(=row)를 DB에 저장해야되니까
                // 아래와 같이 몽구스에서 추가된 save라는 메소드를 user.save() 이런식으로 호출하는데,
                // 이 save() 메소드는 Promise 인스턴스를 return하고 document를 돌려준다.
                // 그래서 user.save() 앞엔 await 키워드를 붙여야된다.
                await user.save(); // 그리고 이렇게 user를 저장해준다.
                return res.send({user})
            } catch (err) {
                // catch에서 잡히는 에러는 서버에서 난 에러이다.
                // user가 값을 잘못 입력해서나는 에러는 이미 try 구문에서 다 잡힌다.
                // 그래서 500번대 status를 return한다.
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.listen(3000, () => console.log('server listening on port 3000'))
    } catch (err) {
        console.log(err);
    }
}

server();

// src/server.js
const express = require('express');
const app = express();
const mongoose = require('mongoose');
const {User} = require('./models/User'); // 이 User로 이제 데이터베이스 작업을 할 수 있다.

const MONGO_URI = 'mongodb+srv://hyungju-lee:fEMZ9UEE9iSEdb6m@mongodbtutorial.2ulmc.mongodb.net/BlogService?retryWrites=true&w=majority'

const server = async () => {
    try {
        await mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true });

        app.use(express.json());

        app.get('/user', async (req, res) => {
            try {
                const users = await User.find({});
                return res.send({users});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.get('/user/:userId', async (req, res) => {
            try {
                const {userId} = req.params;
                const user = await User.findOne({_id: userId});
                return res.send({user});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.post('/user', async (req, res) => {
            // try catch 처리를 꼭 해주자.
            try {
                let {username, name} = req.body;
                if (!username) return res.status(400).send({ err: "username is required" });
                if (!name || !name.first || !name.last) return res.status(400).send({err: "Both first and last names are required"});

                const user = new User(req.body); // 원래는 { username: req.body.username, name: { first: ... } 이런식으로 넣으면되는데,
                // req.body가 user의 형태와 똑같다고 가정을하고 req.body 객체를 이렇게 통채로 넣어준다.
                // 위 const user = new User(req.body) 코드의 역할은 document(=row) 생성이다. 즉, 이 코드를 통해 document 인스턴스가 생성된다.
                // 그리고나서 document(=row)를 DB에 저장해야되니까
                // 아래와 같이 몽구스에서 추가된 save라는 메소드를 user.save() 이런식으로 호출하는데,
                // 이 save() 메소드는 Promise 인스턴스를 return하고 document를 돌려준다.
                // 그래서 user.save() 앞엔 await 키워드를 붙여야된다.
                await user.save(); // 그리고 이렇게 user를 저장해준다.
                return res.send({user})
            } catch (err) {
                // catch에서 잡히는 에러는 서버에서 난 에러이다.
                // user가 값을 잘못 입력해서나는 에러는 이미 try 구문에서 다 잡힌다.
                // 그래서 500번대 status를 return한다.
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.listen(3000, () => console.log('server listening on port 3000'))
    } catch (err) {
        console.log(err);
    }
}

server();

위와 같이 에러가 난다.
에러메시지를 보면 ObjectId로 변환이 실패했다는 뜻이다.
urlEnd Point로 넘긴게 123 숫자긴한데, 넘어갈 땐 모두 String으로 넘어간다.
그러나 String으로 넘어간다고해서 되는게아니고 ObjectId에 맞는 길이, 형식이 있다.
그런 형식에 안맞기 때문에 위와 같은 에러가 난 것이다.

그러면 위 에러도 클라이언트에서 값을 잘못해서 발생한 에러잖아?
그럼 500 에러가아닌 400 에러가 나야될 것이다.

위 코드에서 validator 체크를 하는 코드를 추가하도록 하겠다.

// src/server.js
const express = require('express');
const app = express();
const mongoose = require('mongoose');
const {User} = require('./models/User'); // 이 User로 이제 데이터베이스 작업을 할 수 있다.

const MONGO_URI = 'mongodb+srv://hyungju-lee:fEMZ9UEE9iSEdb6m@mongodbtutorial.2ulmc.mongodb.net/BlogService?retryWrites=true&w=majority'

const server = async () => {
    try {
        await mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true });

        app.use(express.json());

        app.get('/user', async (req, res) => {
            try {
                const users = await User.find({});
                return res.send({users});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.get('/user/:userId', async (req, res) => {
            try {
                const {userId} = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
                const user = await User.findOne({_id: userId});
                return res.send({user});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.post('/user', async (req, res) => {
            // try catch 처리를 꼭 해주자.
            try {
                let {username, name} = req.body;
                if (!username) return res.status(400).send({ err: "username is required" });
                if (!name || !name.first || !name.last) return res.status(400).send({err: "Both first and last names are required"});

                const user = new User(req.body); // 원래는 { username: req.body.username, name: { first: ... } 이런식으로 넣으면되는데,
                // req.body가 user의 형태와 똑같다고 가정을하고 req.body 객체를 이렇게 통채로 넣어준다.
                // 위 const user = new User(req.body) 코드의 역할은 document(=row) 생성이다. 즉, 이 코드를 통해 document 인스턴스가 생성된다.
                // 그리고나서 document(=row)를 DB에 저장해야되니까
                // 아래와 같이 몽구스에서 추가된 save라는 메소드를 user.save() 이런식으로 호출하는데,
                // 이 save() 메소드는 Promise 인스턴스를 return하고 document를 돌려준다.
                // 그래서 user.save() 앞엔 await 키워드를 붙여야된다.
                await user.save(); // 그리고 이렇게 user를 저장해준다.
                return res.send({user})
            } catch (err) {
                // catch에서 잡히는 에러는 서버에서 난 에러이다.
                // user가 값을 잘못 입력해서나는 에러는 이미 try 구문에서 다 잡힌다.
                // 그래서 500번대 status를 return한다.
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.listen(3000, () => console.log('server listening on port 3000'))
    } catch (err) {
        console.log(err);
    }
}

server();

5.8 DELETE/user/:userId

// src/server.js
const express = require('express');
const app = express();
const mongoose = require('mongoose');
const {User} = require('./models/User'); // 이 User로 이제 데이터베이스 작업을 할 수 있다.

const MONGO_URI = 'mongodb+srv://hyungju-lee:fEMZ9UEE9iSEdb6m@mongodbtutorial.2ulmc.mongodb.net/BlogService?retryWrites=true&w=majority'

const server = async () => {
    try {
        await mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true });

        app.use(express.json());

        app.get('/user', async (req, res) => {
            try {
                const users = await User.find({});
                return res.send({users});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.get('/user/:userId', async (req, res) => {
            try {
                const {userId} = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userID" });
                const user = await User.findOne({_id: userId});
                return res.send({user});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.post('/user', async (req, res) => {
            // try catch 처리를 꼭 해주자.
            try {
                let {username, name} = req.body;
                if (!username) return res.status(400).send({ err: "username is required" });
                if (!name || !name.first || !name.last) return res.status(400).send({err: "Both first and last names are required"});

                const user = new User(req.body); // 원래는 { username: req.body.username, name: { first: ... } 이런식으로 넣으면되는데,
                // req.body가 user의 형태와 똑같다고 가정을하고 req.body 객체를 이렇게 통채로 넣어준다.
                // 위 const user = new User(req.body) 코드의 역할은 document(=row) 생성이다. 즉, 이 코드를 통해 document 인스턴스가 생성된다.
                // 그리고나서 document(=row)를 DB에 저장해야되니까
                // 아래와 같이 몽구스에서 추가된 save라는 메소드를 user.save() 이런식으로 호출하는데,
                // 이 save() 메소드는 Promise 인스턴스를 return하고 document를 돌려준다.
                // 그래서 user.save() 앞엔 await 키워드를 붙여야된다.
                await user.save(); // 그리고 이렇게 user를 저장해준다.
                return res.send({user})
            } catch (err) {
                // catch에서 잡히는 에러는 서버에서 난 에러이다.
                // user가 값을 잘못 입력해서나는 에러는 이미 try 구문에서 다 잡힌다.
                // 그래서 500번대 status를 return한다.
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.delete('/user/:userId', async (req, res) => {
            try {
                const {userId} = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
                const user = await User.findOneAndDelete({_id: userId}); // user 객체가 리턴되면 그 객체가 잘 삭제된거고 null을 리턴하면 애초에 그 객체는 존재하지 않았다는 것

                // const user = await User.deleteOne({_id: userId}); // 그냥 deleteOne 메소드를 사용해도 상관이 없다.
                // findOneAndDelete과 deleteOne의 차이는 user를 반환받을 수 있냐 없냐 차이이다.
                // 만약 받을 필요 없으면 deleteOne이 조금 더 효율적일 것이다.

                return res.send({user});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.listen(3000, () => console.log('server listening on port 3000'))
    } catch (err) {
        console.log(err);
    }
}

server();

5.9 PUT/user/:userId

한가지 API를 더 만들어보겠습니다.

크게 4가지 있다고 했잖아요?

CRUD에서 1대1로 해당하는 GET(read)했고, POST(Create)했고, DELETE(Delete)했고.. 아직 안한 PUT(Update)를 해야됩니다.


// src/server.js
const express = require('express');
const app = express();
const mongoose = require('mongoose');
const {User} = require('./models/User'); // 이 User로 이제 데이터베이스 작업을 할 수 있다.

const MONGO_URI = 'mongodb+srv://hyungju-lee:fEMZ9UEE9iSEdb6m@mongodbtutorial.2ulmc.mongodb.net/BlogService?retryWrites=true&w=majority'

const server = async () => {
    try {
        await mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true });

        app.use(express.json());

        app.get('/user', async (req, res) => {
            try {
                const users = await User.find({});
                return res.send({users});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.get('/user/:userId', async (req, res) => {
            try {
                const {userId} = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userID" });
                const user = await User.findOne({_id: userId});
                return res.send({user});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.post('/user', async (req, res) => {
            // try catch 처리를 꼭 해주자.
            try {
                let {username, name} = req.body;
                if (!username) return res.status(400).send({ err: "username is required" });
                if (!name || !name.first || !name.last) return res.status(400).send({err: "Both first and last names are required"});

                const user = new User(req.body); // 원래는 { username: req.body.username, name: { first: ... } 이런식으로 넣으면되는데,
                // req.body가 user의 형태와 똑같다고 가정을하고 req.body 객체를 이렇게 통채로 넣어준다.
                // 위 const user = new User(req.body) 코드의 역할은 document(=row) 생성이다. 즉, 이 코드를 통해 document 인스턴스가 생성된다.
                // 그리고나서 document(=row)를 DB에 저장해야되니까
                // 아래와 같이 몽구스에서 추가된 save라는 메소드를 user.save() 이런식으로 호출하는데,
                // 이 save() 메소드는 Promise 인스턴스를 return하고 document를 돌려준다.
                // 그래서 user.save() 앞엔 await 키워드를 붙여야된다.
                await user.save(); // 그리고 이렇게 user를 저장해준다.
                return res.send({user})
            } catch (err) {
                // catch에서 잡히는 에러는 서버에서 난 에러이다.
                // user가 값을 잘못 입력해서나는 에러는 이미 try 구문에서 다 잡힌다.
                // 그래서 500번대 status를 return한다.
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.delete('/user/:userId', async (req, res) => {
            try {
                const {userId} = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
                const user = await User.findOneAndDelete({_id: userId}); // user 객체가 리턴되면 그 객체가 잘 삭제된거고 null을 리턴하면 애초에 그 객체는 존재하지 않았다는 것

                // const user = await User.deleteOne({_id: userId}); // 그냥 deleteOne 메소드를 사용해도 상관이 없다.
                // findOneAndDelete과 deleteOne의 차이는 user를 반환받을 수 있냐 없냐 차이이다.
                // 만약 받을 필요 없으면 deleteOne이 조금 더 효율적일 것이다.

                return res.send({user});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        // 이제 PUT(update)을 구현해봅시다.
        app.put('user/:userId', async (req, res) => {
            // ...
        })

        app.listen(3000, () => console.log('server listening on port 3000'))
    } catch (err) {
        console.log(err);
    }
}

server();

데이터베이스 I/O 작업이 들어가므로 마찬가지로 async 키워드를 이용해 비동기 처리를 해주고


// src/server.js
const express = require('express');
const app = express();
const mongoose = require('mongoose');
const {User} = require('./models/User'); // 이 User로 이제 데이터베이스 작업을 할 수 있다.

const MONGO_URI = 'mongodb+srv://hyungju-lee:fEMZ9UEE9iSEdb6m@mongodbtutorial.2ulmc.mongodb.net/BlogService?retryWrites=true&w=majority'

const server = async () => {
    try {
        await mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true });

        app.use(express.json());

        app.get('/user', async (req, res) => {
            try {
                const users = await User.find({});
                return res.send({users});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.get('/user/:userId', async (req, res) => {
            try {
                const {userId} = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userID" });
                const user = await User.findOne({_id: userId});
                return res.send({user});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.post('/user', async (req, res) => {
            // try catch 처리를 꼭 해주자.
            try {
                let {username, name} = req.body;
                if (!username) return res.status(400).send({ err: "username is required" });
                if (!name || !name.first || !name.last) return res.status(400).send({err: "Both first and last names are required"});

                const user = new User(req.body); // 원래는 { username: req.body.username, name: { first: ... } 이런식으로 넣으면되는데,
                // req.body가 user의 형태와 똑같다고 가정을하고 req.body 객체를 이렇게 통채로 넣어준다.
                // 위 const user = new User(req.body) 코드의 역할은 document(=row) 생성이다. 즉, 이 코드를 통해 document 인스턴스가 생성된다.
                // 그리고나서 document(=row)를 DB에 저장해야되니까
                // 아래와 같이 몽구스에서 추가된 save라는 메소드를 user.save() 이런식으로 호출하는데,
                // 이 save() 메소드는 Promise 인스턴스를 return하고 document를 돌려준다.
                // 그래서 user.save() 앞엔 await 키워드를 붙여야된다.
                await user.save(); // 그리고 이렇게 user를 저장해준다.
                return res.send({user})
            } catch (err) {
                // catch에서 잡히는 에러는 서버에서 난 에러이다.
                // user가 값을 잘못 입력해서나는 에러는 이미 try 구문에서 다 잡힌다.
                // 그래서 500번대 status를 return한다.
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.delete('/user/:userId', async (req, res) => {
            try {
                const {userId} = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
                const user = await User.findOneAndDelete({_id: userId}); // user 객체가 리턴되면 그 객체가 잘 삭제된거고 null을 리턴하면 애초에 그 객체는 존재하지 않았다는 것

                // const user = await User.deleteOne({_id: userId}); // 그냥 deleteOne 메소드를 사용해도 상관이 없다.
                // findOneAndDelete과 deleteOne의 차이는 user를 반환받을 수 있냐 없냐 차이이다.
                // 만약 받을 필요 없으면 deleteOne이 조금 더 효율적일 것이다.

                return res.send({user});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        // 이제 PUT(update)을 구현해봅시다.
        app.put('user/:userId', async (req, res) => {
            try {
                const { userId } = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
            } catch (err) {
                console.log(err);
                return res.status(500).send({ err: err.message });
            }
        })

        app.listen(3000, () => console.log('server listening on port 3000'))
    } catch (err) {
        console.log(err);
    }
}

server();

작성하다보면 위와 같이 반복적인 코드들이 생깁니다.

try catch문도 그렇고 검증하는 validation 코드들도 있고..

이를 미들웨어로도 할 수 있는데, 일단 이 강의는 mongoDB가 메인이니까 이대로 진행하도록 하겠습니다.


// src/models/User.js
const {Schema, model} = require('mongoose');

// Schema 첫번째 인자 : 해당 데이터 값의 정의
// Schema 두번째 인자 : 옵션
const UserSchema = new Schema({
    username: {type: String, required: true, unique: true},
    name: {
        first: {type: String, required: true},
        last: {type: String, required: true}
    },
    age: Number, // required: true 가 아니라면 이렇게 type만 넣을 수도 있다.
    email: String,
}, {
    timestamps: true // createdAt(생상한 시간)을 자동으로 넣어준다.
                     // 그리고 update할 때마다 updateAt이라는 키를 수정해준다.
                     // 언제 생성되고 언제 수정되었는지를 전부 자동으로 넣어준다.
})

// 위와 같이 만들었으면 mongoose한테 알려줘야된다.
const User = model('user', UserSchema); // 몽구스에 알려주는 것이다. user라는 콜렉션을 만들거야. 그리고 그 콜렉션은 위와 같은 데이터형태를 가지고 있어.
// 실제 콜렉션은 users 라는 이름으로 뒤에 s가 붙어서 생성이 된다.

module.exports = { User };

Update 기능으로 username은 못 바꾼다고하고, 바꾸려면 name, age, email 정보를 바꿀건데, 일단 age만 바꾸는 것으로 만들어보겠습니다.


const express = require('express');
const app = express();
const mongoose = require('mongoose');
const {User} = require('./models/User'); // 이 User로 이제 데이터베이스 작업을 할 수 있다.

const MONGO_URI = 'mongodb+srv://hyungju-lee:fEMZ9UEE9iSEdb6m@mongodbtutorial.2ulmc.mongodb.net/BlogService?retryWrites=true&w=majority'

const server = async () => {
    try {
        await mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true });

        app.use(express.json());

        app.get('/user', async (req, res) => {
            try {
                const users = await User.find({});
                return res.send({users});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.get('/user/:userId', async (req, res) => {
            try {
                const {userId} = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userID" });
                const user = await User.findOne({_id: userId});
                return res.send({user});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.post('/user', async (req, res) => {
            // try catch 처리를 꼭 해주자.
            try {
                let {username, name} = req.body;
                if (!username) return res.status(400).send({ err: "username is required" });
                if (!name || !name.first || !name.last) return res.status(400).send({err: "Both first and last names are required"});

                const user = new User(req.body); // 원래는 { username: req.body.username, name: { first: ... } 이런식으로 넣으면되는데,
                // req.body가 user의 형태와 똑같다고 가정을하고 req.body 객체를 이렇게 통채로 넣어준다.
                // 위 const user = new User(req.body) 코드의 역할은 document(=row) 생성이다. 즉, 이 코드를 통해 document 인스턴스가 생성된다.
                // 그리고나서 document(=row)를 DB에 저장해야되니까
                // 아래와 같이 몽구스에서 추가된 save라는 메소드를 user.save() 이런식으로 호출하는데,
                // 이 save() 메소드는 Promise 인스턴스를 return하고 document를 돌려준다.
                // 그래서 user.save() 앞엔 await 키워드를 붙여야된다.
                await user.save(); // 그리고 이렇게 user를 저장해준다.
                return res.send({user})
            } catch (err) {
                // catch에서 잡히는 에러는 서버에서 난 에러이다.
                // user가 값을 잘못 입력해서나는 에러는 이미 try 구문에서 다 잡힌다.
                // 그래서 500번대 status를 return한다.
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.delete('/user/:userId', async (req, res) => {
            try {
                const {userId} = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
                const user = await User.findOneAndDelete({_id: userId}); // user 객체가 리턴되면 그 객체가 잘 삭제된거고 null을 리턴하면 애초에 그 객체는 존재하지 않았다는 것

                // const user = await User.deleteOne({_id: userId}); // 그냥 deleteOne 메소드를 사용해도 상관이 없다.
                // findOneAndDelete과 deleteOne의 차이는 user를 반환받을 수 있냐 없냐 차이이다.
                // 만약 받을 필요 없으면 deleteOne이 조금 더 효율적일 것이다.

                return res.send({user});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        // 이제 PUT(update)을 구현해봅시다.
        app.put('user/:userId', async (req, res) => {
            try {
                const { userId } = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
                // age 정보를 디스트럭쳐링을 통해 req.body에서 빼주고..
                const { age } = req.body;
                const user = await User.findOneAndUpdate(); // 이것도 delete와 마찬가지로 updateOne 메소드를 사용해도됩니다.
                                                            // 그런데 결과도 받아보고싶으니 findOneAndUpdate 메소드를 쓰도록 하겠습니다.
            } catch (err) {
                console.log(err);
                return res.status(500).send({ err: err.message });
            }
        })

        app.listen(3000, () => console.log('server listening on port 3000'))
    } catch (err) {
        console.log(err);
    }
}

server();

age 정보를 디스트럭쳐링을 통해 req.body에서 빼주시고..

이것도 delete와 마찬가지로 updateOne 메소드를 사용해도됩니다.

그런데 결과도 받아보고싶으니 findOneAndUpdate 메소드를 쓰도록하겠습니다.


const express = require('express');
const app = express();
const mongoose = require('mongoose');
const {User} = require('./models/User'); // 이 User로 이제 데이터베이스 작업을 할 수 있다.

const MONGO_URI = 'mongodb+srv://hyungju-lee:fEMZ9UEE9iSEdb6m@mongodbtutorial.2ulmc.mongodb.net/BlogService?retryWrites=true&w=majority'

const server = async () => {
    try {
        await mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true });

        app.use(express.json());

        app.get('/user', async (req, res) => {
            try {
                const users = await User.find({});
                return res.send({users});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.get('/user/:userId', async (req, res) => {
            try {
                const {userId} = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userID" });
                const user = await User.findOne({_id: userId});
                return res.send({user});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.post('/user', async (req, res) => {
            // try catch 처리를 꼭 해주자.
            try {
                let {username, name} = req.body;
                if (!username) return res.status(400).send({ err: "username is required" });
                if (!name || !name.first || !name.last) return res.status(400).send({err: "Both first and last names are required"});

                const user = new User(req.body); // 원래는 { username: req.body.username, name: { first: ... } 이런식으로 넣으면되는데,
                // req.body가 user의 형태와 똑같다고 가정을하고 req.body 객체를 이렇게 통채로 넣어준다.
                // 위 const user = new User(req.body) 코드의 역할은 document(=row) 생성이다. 즉, 이 코드를 통해 document 인스턴스가 생성된다.
                // 그리고나서 document(=row)를 DB에 저장해야되니까
                // 아래와 같이 몽구스에서 추가된 save라는 메소드를 user.save() 이런식으로 호출하는데,
                // 이 save() 메소드는 Promise 인스턴스를 return하고 document를 돌려준다.
                // 그래서 user.save() 앞엔 await 키워드를 붙여야된다.
                await user.save(); // 그리고 이렇게 user를 저장해준다.
                return res.send({user})
            } catch (err) {
                // catch에서 잡히는 에러는 서버에서 난 에러이다.
                // user가 값을 잘못 입력해서나는 에러는 이미 try 구문에서 다 잡힌다.
                // 그래서 500번대 status를 return한다.
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.delete('/user/:userId', async (req, res) => {
            try {
                const {userId} = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
                const user = await User.findOneAndDelete({_id: userId}); // user 객체가 리턴되면 그 객체가 잘 삭제된거고 null을 리턴하면 애초에 그 객체는 존재하지 않았다는 것

                // const user = await User.deleteOne({_id: userId}); // 그냥 deleteOne 메소드를 사용해도 상관이 없다.
                // findOneAndDelete과 deleteOne의 차이는 user를 반환받을 수 있냐 없냐 차이이다.
                // 만약 받을 필요 없으면 deleteOne이 조금 더 효율적일 것이다.

                return res.send({user});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        // 이제 PUT(update)을 구현해봅시다.
        app.put('user/:userId', async (req, res) => {
            try {
                const { userId } = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
                // age 정보를 디스트럭쳐링을 통해 req.body에서 빼주고..
                const { age } = req.body;
                const user = await User.findOneAndUpdate(); // 이것도 delete와 마찬가지로 updateOne 메소드를 사용해도됩니다.
                                                            // 그런데 결과도 받아보고싶으니 findOneAndUpdate 메소드를 쓰도록 하겠습니다.
                // const user = await User.findById();
                // const user = await User.findByIdAndDelete();
                // 그리고 findOneAndUpdate와 비슷한 메소드로 위와 같이 findById, findByIdAndDelete 등이 있습니다.
                // 이것은 차이가 뭐냐면,
            } catch (err) {
                console.log(err);
                return res.status(500).send({ err: err.message });
            }
        })

        app.listen(3000, () => console.log('server listening on port 3000'))
    } catch (err) {
        console.log(err);
    }
}

server();

그리고 findOneAndUpdate와 비슷한 메소드로 위와 같이 findById, findByIdAndDelete 등등이 있습니다.

이것은 차이가 뭐냐면,

예를 들어서 위와 같이하면, {} 객체를 넣어서 필터 조건을 넣는데,

위와 같이 _id: userId 라던지 이렇게했는데, 그럴필요 없이

위와 같이 바로 userId를 넣어주면됩니다.

코드상으론 위가 좀 더 간결하죠?

몽고DB상으로는 위 코드나 이전 코드나 똑같은데, mongoose가 좀 더 깔끔하게 코드를 해주는 메소드를 제공해주는 겁니다.

그래서 위 메소드를 쓰고싶다면 쓰셔도됩니다.


const express = require('express');
const app = express();
const mongoose = require('mongoose');
const {User} = require('./models/User'); // 이 User로 이제 데이터베이스 작업을 할 수 있다.

const MONGO_URI = 'mongodb+srv://hyungju-lee:fEMZ9UEE9iSEdb6m@mongodbtutorial.2ulmc.mongodb.net/BlogService?retryWrites=true&w=majority'

const server = async () => {
    try {
        await mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true });

        app.use(express.json());

        app.get('/user', async (req, res) => {
            try {
                const users = await User.find({});
                return res.send({users});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.get('/user/:userId', async (req, res) => {
            try {
                const {userId} = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userID" });
                const user = await User.findOne({_id: userId});
                return res.send({user});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.post('/user', async (req, res) => {
            // try catch 처리를 꼭 해주자.
            try {
                let {username, name} = req.body;
                if (!username) return res.status(400).send({ err: "username is required" });
                if (!name || !name.first || !name.last) return res.status(400).send({err: "Both first and last names are required"});

                const user = new User(req.body); // 원래는 { username: req.body.username, name: { first: ... } 이런식으로 넣으면되는데,
                // req.body가 user의 형태와 똑같다고 가정을하고 req.body 객체를 이렇게 통채로 넣어준다.
                // 위 const user = new User(req.body) 코드의 역할은 document(=row) 생성이다. 즉, 이 코드를 통해 document 인스턴스가 생성된다.
                // 그리고나서 document(=row)를 DB에 저장해야되니까
                // 아래와 같이 몽구스에서 추가된 save라는 메소드를 user.save() 이런식으로 호출하는데,
                // 이 save() 메소드는 Promise 인스턴스를 return하고 document를 돌려준다.
                // 그래서 user.save() 앞엔 await 키워드를 붙여야된다.
                await user.save(); // 그리고 이렇게 user를 저장해준다.
                return res.send({user})
            } catch (err) {
                // catch에서 잡히는 에러는 서버에서 난 에러이다.
                // user가 값을 잘못 입력해서나는 에러는 이미 try 구문에서 다 잡힌다.
                // 그래서 500번대 status를 return한다.
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.delete('/user/:userId', async (req, res) => {
            try {
                const {userId} = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
                const user = await User.findOneAndDelete({_id: userId}); // user 객체가 리턴되면 그 객체가 잘 삭제된거고 null을 리턴하면 애초에 그 객체는 존재하지 않았다는 것

                // const user = await User.deleteOne({_id: userId}); // 그냥 deleteOne 메소드를 사용해도 상관이 없다.
                // findOneAndDelete과 deleteOne의 차이는 user를 반환받을 수 있냐 없냐 차이이다.
                // 만약 받을 필요 없으면 deleteOne이 조금 더 효율적일 것이다.

                return res.send({user});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        // 이제 PUT(update)을 구현해봅시다.
        app.put('user/:userId', async (req, res) => {
            try {
                const { userId } = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
                // age 정보를 디스트럭쳐링을 통해 req.body에서 빼주고..
                const { age } = req.body;
                const user = await User.findByIdAndUpdate(userId, {  }); // 이것도 delete와 마찬가지로 updateOne 메소드를 사용해도됩니다.
                                                                         // 그런데 결과도 받아보고싶으니 findOneAndUpdate 메소드를 쓰도록 하겠습니다.
                // const user = await User.findById();
                // const user = await User.findByIdAndDelete();
                // 그리고 findOneAndUpdate와 비슷한 메소드로 위와 같이 findById, findByIdAndDelete 등이 있습니다.
            } catch (err) {
                console.log(err);
                return res.status(500).send({ err: err.message });
            }
        })

        app.listen(3000, () => console.log('server listening on port 3000'))
    } catch (err) {
        console.log(err);
    }
}

server();

그리고 두번째 인자로 위와 같이 어떤 프로퍼티를 어떤 값으로 수정하고 싶은지를 적어주면 됩니다.

터미널 창에서 $set을 썼던거를 기억하실거에요.


const express = require('express');
const app = express();
const mongoose = require('mongoose');
const {User} = require('./models/User'); // 이 User로 이제 데이터베이스 작업을 할 수 있다.

const MONGO_URI = 'mongodb+srv://hyungju-lee:fEMZ9UEE9iSEdb6m@mongodbtutorial.2ulmc.mongodb.net/BlogService?retryWrites=true&w=majority'

const server = async () => {
    try {
        await mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true });

        app.use(express.json());

        app.get('/user', async (req, res) => {
            try {
                const users = await User.find({});
                return res.send({users});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.get('/user/:userId', async (req, res) => {
            try {
                const {userId} = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userID" });
                const user = await User.findOne({_id: userId});
                return res.send({user});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.post('/user', async (req, res) => {
            // try catch 처리를 꼭 해주자.
            try {
                let {username, name} = req.body;
                if (!username) return res.status(400).send({ err: "username is required" });
                if (!name || !name.first || !name.last) return res.status(400).send({err: "Both first and last names are required"});

                const user = new User(req.body); // 원래는 { username: req.body.username, name: { first: ... } 이런식으로 넣으면되는데,
                // req.body가 user의 형태와 똑같다고 가정을하고 req.body 객체를 이렇게 통채로 넣어준다.
                // 위 const user = new User(req.body) 코드의 역할은 document(=row) 생성이다. 즉, 이 코드를 통해 document 인스턴스가 생성된다.
                // 그리고나서 document(=row)를 DB에 저장해야되니까
                // 아래와 같이 몽구스에서 추가된 save라는 메소드를 user.save() 이런식으로 호출하는데,
                // 이 save() 메소드는 Promise 인스턴스를 return하고 document를 돌려준다.
                // 그래서 user.save() 앞엔 await 키워드를 붙여야된다.
                await user.save(); // 그리고 이렇게 user를 저장해준다.
                return res.send({user})
            } catch (err) {
                // catch에서 잡히는 에러는 서버에서 난 에러이다.
                // user가 값을 잘못 입력해서나는 에러는 이미 try 구문에서 다 잡힌다.
                // 그래서 500번대 status를 return한다.
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.delete('/user/:userId', async (req, res) => {
            try {
                const {userId} = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
                const user = await User.findOneAndDelete({_id: userId}); // user 객체가 리턴되면 그 객체가 잘 삭제된거고 null을 리턴하면 애초에 그 객체는 존재하지 않았다는 것

                // const user = await User.deleteOne({_id: userId}); // 그냥 deleteOne 메소드를 사용해도 상관이 없다.
                // findOneAndDelete과 deleteOne의 차이는 user를 반환받을 수 있냐 없냐 차이이다.
                // 만약 받을 필요 없으면 deleteOne이 조금 더 효율적일 것이다.

                return res.send({user});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        // 이제 PUT(update)을 구현해봅시다.
        app.put('user/:userId', async (req, res) => {
            try {
                const { userId } = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
                // age 정보를 디스트럭쳐링을 통해 req.body에서 빼주고..
                const { age } = req.body;
                const user = await User.findByIdAndUpdate(userId, { $set: { age } }); // 이것도 delete와 마찬가지로 updateOne 메소드를 사용해도됩니다.
                                                                                      // 그런데 결과도 받아보고싶으니 findOneAndUpdate 메소드를 쓰도록 하겠습니다.
                // const user = await User.findById();
                // const user = await User.findByIdAndDelete();
                // 그리고 findOneAndUpdate와 비슷한 메소드로 위와 같이 findById, findByIdAndDelete 등이 있습니다.
            } catch (err) {
                console.log(err);
                return res.status(500).send({ err: err.message });
            }
        })

        app.listen(3000, () => console.log('server listening on port 3000'))
    } catch (err) {
        console.log(err);
    }
}

server();

위와 같이 age 프로퍼티를 수정하고 싶으므로 위와 같이 작성한다.

keyvalue의 이름이 같으면 위와같이 작성할 수 있다.

이렇게 마지막으로 return res.send({ user }); 까지 작성해주고 테스트를 한번 해보겠습니다.


const express = require('express');
const app = express();
const mongoose = require('mongoose');
const {User} = require('./models/User'); // 이 User로 이제 데이터베이스 작업을 할 수 있다.

const MONGO_URI = 'mongodb+srv://hyungju-lee:fEMZ9UEE9iSEdb6m@mongodbtutorial.2ulmc.mongodb.net/BlogService?retryWrites=true&w=majority'

const server = async () => {
    try {
        await mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true });

        app.use(express.json());

        app.get('/user', async (req, res) => {
            try {
                const users = await User.find({});
                return res.send({users});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.get('/user/:userId', async (req, res) => {
            try {
                const {userId} = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userID" });
                const user = await User.findOne({_id: userId});
                return res.send({user});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.post('/user', async (req, res) => {
            // try catch 처리를 꼭 해주자.
            try {
                let {username, name} = req.body;
                if (!username) return res.status(400).send({ err: "username is required" });
                if (!name || !name.first || !name.last) return res.status(400).send({err: "Both first and last names are required"});

                const user = new User(req.body); // 원래는 { username: req.body.username, name: { first: ... } 이런식으로 넣으면되는데,
                // req.body가 user의 형태와 똑같다고 가정을하고 req.body 객체를 이렇게 통채로 넣어준다.
                // 위 const user = new User(req.body) 코드의 역할은 document(=row) 생성이다. 즉, 이 코드를 통해 document 인스턴스가 생성된다.
                // 그리고나서 document(=row)를 DB에 저장해야되니까
                // 아래와 같이 몽구스에서 추가된 save라는 메소드를 user.save() 이런식으로 호출하는데,
                // 이 save() 메소드는 Promise 인스턴스를 return하고 document를 돌려준다.
                // 그래서 user.save() 앞엔 await 키워드를 붙여야된다.
                await user.save(); // 그리고 이렇게 user를 저장해준다.
                return res.send({user})
            } catch (err) {
                // catch에서 잡히는 에러는 서버에서 난 에러이다.
                // user가 값을 잘못 입력해서나는 에러는 이미 try 구문에서 다 잡힌다.
                // 그래서 500번대 status를 return한다.
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.delete('/user/:userId', async (req, res) => {
            try {
                const {userId} = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
                const user = await User.findOneAndDelete({_id: userId}); // user 객체가 리턴되면 그 객체가 잘 삭제된거고 null을 리턴하면 애초에 그 객체는 존재하지 않았다는 것

                // const user = await User.deleteOne({_id: userId}); // 그냥 deleteOne 메소드를 사용해도 상관이 없다.
                // findOneAndDelete과 deleteOne의 차이는 user를 반환받을 수 있냐 없냐 차이이다.
                // 만약 받을 필요 없으면 deleteOne이 조금 더 효율적일 것이다.

                return res.send({user});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        // 이제 PUT(update)을 구현해봅시다.
        app.put('user/:userId', async (req, res) => {
            try {
                const { userId } = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
                // age 정보를 디스트럭쳐링을 통해 req.body에서 빼주고..
                const { age } = req.body;
                const user = await User.findByIdAndUpdate(userId, { $set: { age } }); // 이것도 delete와 마찬가지로 updateOne 메소드를 사용해도됩니다.
                                                                                             // 그런데 결과도 받아보고싶으니 findOneAndUpdate 메소드를 쓰도록 하겠습니다.
                return res.send({ user });
                // const user = await User.findById();
                // const user = await User.findByIdAndDelete();
                // 그리고 findOneAndUpdate와 비슷한 메소드로 위와 같이 findById, findByIdAndDelete 등이 있습니다.
            } catch (err) {
                console.log(err);
                return res.status(500).send({ err: err.message });
            }
        })

        app.listen(3000, () => console.log('server listening on port 3000'))
    } catch (err) {
        console.log(err);
    }
}

server();

위와 같이 PUT으로 설정해주고 현재 존재하는 데이터의 id값을 위와 같이 End Point에 넣어주고 Body를 설정해준다.

JSON으로 설정한 후 위와 같이 age: 31 값을 보낸다.

아, 그런데 여기서 숫자에 대한 validation을 안했죠?

일단 age 값이 있는지 없는지 확인하려면 위와 같은 코드를 추가해준다.

만약 여기서 타입까지 확인하고 싶다면?

위와 같이 typeof 키워드를 활용한 코드도 작성해주면된다.


const express = require('express');
const app = express();
const mongoose = require('mongoose');
const {User} = require('./models/User'); // 이 User로 이제 데이터베이스 작업을 할 수 있다.

const MONGO_URI = 'mongodb+srv://hyungju-lee:fEMZ9UEE9iSEdb6m@mongodbtutorial.2ulmc.mongodb.net/BlogService?retryWrites=true&w=majority'

const server = async () => {
    try {
        await mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true });

        app.use(express.json());

        app.get('/user', async (req, res) => {
            try {
                const users = await User.find({});
                return res.send({users});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.get('/user/:userId', async (req, res) => {
            try {
                const {userId} = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userID" });
                const user = await User.findOne({_id: userId});
                return res.send({user});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.post('/user', async (req, res) => {
            // try catch 처리를 꼭 해주자.
            try {
                let {username, name} = req.body;
                if (!username) return res.status(400).send({ err: "username is required" });
                if (!name || !name.first || !name.last) return res.status(400).send({err: "Both first and last names are required"});

                const user = new User(req.body); // 원래는 { username: req.body.username, name: { first: ... } 이런식으로 넣으면되는데,
                // req.body가 user의 형태와 똑같다고 가정을하고 req.body 객체를 이렇게 통채로 넣어준다.
                // 위 const user = new User(req.body) 코드의 역할은 document(=row) 생성이다. 즉, 이 코드를 통해 document 인스턴스가 생성된다.
                // 그리고나서 document(=row)를 DB에 저장해야되니까
                // 아래와 같이 몽구스에서 추가된 save라는 메소드를 user.save() 이런식으로 호출하는데,
                // 이 save() 메소드는 Promise 인스턴스를 return하고 document를 돌려준다.
                // 그래서 user.save() 앞엔 await 키워드를 붙여야된다.
                await user.save(); // 그리고 이렇게 user를 저장해준다.
                return res.send({user})
            } catch (err) {
                // catch에서 잡히는 에러는 서버에서 난 에러이다.
                // user가 값을 잘못 입력해서나는 에러는 이미 try 구문에서 다 잡힌다.
                // 그래서 500번대 status를 return한다.
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.delete('/user/:userId', async (req, res) => {
            try {
                const {userId} = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
                const user = await User.findOneAndDelete({_id: userId}); // user 객체가 리턴되면 그 객체가 잘 삭제된거고 null을 리턴하면 애초에 그 객체는 존재하지 않았다는 것

                // const user = await User.deleteOne({_id: userId}); // 그냥 deleteOne 메소드를 사용해도 상관이 없다.
                // findOneAndDelete과 deleteOne의 차이는 user를 반환받을 수 있냐 없냐 차이이다.
                // 만약 받을 필요 없으면 deleteOne이 조금 더 효율적일 것이다.

                return res.send({user});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        // 이제 PUT(update)을 구현해봅시다.
        app.put('user/:userId', async (req, res) => {
            try {
                const { userId } = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
                // age 정보를 디스트럭쳐링을 통해 req.body에서 빼주고..
                const { age } = req.body;
                if (!age) return res.status(400).send({ err: "age is required" }); // age 값이 있는지 없는지 체크하는 코드
                if (typeof age !== 'number') return res.status(400).send({ err: "age must be a number" }); // age 값 타입 체크하는 코드
                const user = await User.findByIdAndUpdate(userId, { $set: { age } }); // 이것도 delete와 마찬가지로 updateOne 메소드를 사용해도됩니다.
                                                                                             // 그런데 결과도 받아보고싶으니 findOneAndUpdate 메소드를 쓰도록 하겠습니다.
                return res.send({ user });
                // const user = await User.findById();
                // const user = await User.findByIdAndDelete();
                // 그리고 findOneAndUpdate와 비슷한 메소드로 위와 같이 findById, findByIdAndDelete 등이 있습니다.
            } catch (err) {
                console.log(err);
                return res.status(500).send({ err: err.message });
            }
        })

        app.listen(3000, () => console.log('server listening on port 3000'))
    } catch (err) {
        console.log(err);
    }
}

server();

음? 잘 안보네지는거같은데..

아 슬래시/를 빼먹었습니다. 위와 같이 수정해주고,


const express = require('express');
const app = express();
const mongoose = require('mongoose');
const {User} = require('./models/User'); // 이 User로 이제 데이터베이스 작업을 할 수 있다.

const MONGO_URI = 'mongodb+srv://hyungju-lee:fEMZ9UEE9iSEdb6m@mongodbtutorial.2ulmc.mongodb.net/BlogService?retryWrites=true&w=majority'

const server = async () => {
    try {
        await mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true });

        app.use(express.json());

        app.get('/user', async (req, res) => {
            try {
                const users = await User.find({});
                return res.send({users});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.get('/user/:userId', async (req, res) => {
            try {
                const {userId} = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userID" });
                const user = await User.findOne({_id: userId});
                return res.send({user});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.post('/user', async (req, res) => {
            // try catch 처리를 꼭 해주자.
            try {
                let {username, name} = req.body;
                if (!username) return res.status(400).send({ err: "username is required" });
                if (!name || !name.first || !name.last) return res.status(400).send({err: "Both first and last names are required"});

                const user = new User(req.body); // 원래는 { username: req.body.username, name: { first: ... } 이런식으로 넣으면되는데,
                // req.body가 user의 형태와 똑같다고 가정을하고 req.body 객체를 이렇게 통채로 넣어준다.
                // 위 const user = new User(req.body) 코드의 역할은 document(=row) 생성이다. 즉, 이 코드를 통해 document 인스턴스가 생성된다.
                // 그리고나서 document(=row)를 DB에 저장해야되니까
                // 아래와 같이 몽구스에서 추가된 save라는 메소드를 user.save() 이런식으로 호출하는데,
                // 이 save() 메소드는 Promise 인스턴스를 return하고 document를 돌려준다.
                // 그래서 user.save() 앞엔 await 키워드를 붙여야된다.
                await user.save(); // 그리고 이렇게 user를 저장해준다.
                return res.send({user})
            } catch (err) {
                // catch에서 잡히는 에러는 서버에서 난 에러이다.
                // user가 값을 잘못 입력해서나는 에러는 이미 try 구문에서 다 잡힌다.
                // 그래서 500번대 status를 return한다.
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.delete('/user/:userId', async (req, res) => {
            try {
                const {userId} = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
                const user = await User.findOneAndDelete({_id: userId}); // user 객체가 리턴되면 그 객체가 잘 삭제된거고 null을 리턴하면 애초에 그 객체는 존재하지 않았다는 것

                // const user = await User.deleteOne({_id: userId}); // 그냥 deleteOne 메소드를 사용해도 상관이 없다.
                // findOneAndDelete과 deleteOne의 차이는 user를 반환받을 수 있냐 없냐 차이이다.
                // 만약 받을 필요 없으면 deleteOne이 조금 더 효율적일 것이다.

                return res.send({user});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        // 이제 PUT(update)을 구현해봅시다.
        app.put('/user/:userId', async (req, res) => {
            try {
                const { userId } = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
                // age 정보를 디스트럭쳐링을 통해 req.body에서 빼주고..
                const { age } = req.body;
                if (!age) return res.status(400).send({ err: "age is required" }); // age 값이 있는지 없는지 체크하는 코드
                if (typeof age !== 'number') return res.status(400).send({ err: "age must be a number" }); // age 값 타입 체크하는 코드
                const user = await User.findByIdAndUpdate(userId, { $set: { age } }); // 이것도 delete와 마찬가지로 updateOne 메소드를 사용해도됩니다.
                                                                                             // 그런데 결과도 받아보고싶으니 findOneAndUpdate 메소드를 쓰도록 하겠습니다.
                return res.send({ user });
                // const user = await User.findById();
                // const user = await User.findByIdAndDelete();
                // 그리고 findOneAndUpdate와 비슷한 메소드로 위와 같이 findById, findByIdAndDelete 등이 있습니다.
            } catch (err) {
                console.log(err);
                return res.status(500).send({ err: err.message });
            }
        })

        app.listen(3000, () => console.log('server listening on port 3000'))
    } catch (err) {
        console.log(err);
    }
}

server();

다시 요청을보내면 age must be a number라고 나옵니다.

이번엔 number 타입으로 입력해 요청을 보내면…

그런데 위 요청을 보내기전에 다시한번

현재 요청을 보내서 age를 바꾸려는 userage30입니다.

30에서 31로 바꾸려는 요청을 보내려고하는겁니다.

요청을 보내서 return되는 값을 보면 분명 잘 처리는 된거같은데 return된 값의 age를 보면 30입니다.

그런데 또 웃긴게 GET요청으로 조회를해보면 31로 나옵니다.

그게 이유가뭐냐면 위 코드를 보시면 findByIdAndUpdate의 첫번째 인자가 필터 조건이고, 두번째 인자가 필터링된 데이터의 어떤 키, 벨류를 바꿀건지, 세번째 인자는 옵션이라고 말씀을 드렸었던거 같은데,

세번째 인자에 { new: true }를 해주셔야됩니다.

이 세번째 인자에 옵션값을 위와같이 해주지않으면 업데이트 되기 전의 값을 return해줍니다.


const express = require('express');
const app = express();
const mongoose = require('mongoose');
const {User} = require('./models/User'); // 이 User로 이제 데이터베이스 작업을 할 수 있다.

const MONGO_URI = 'mongodb+srv://hyungju-lee:fEMZ9UEE9iSEdb6m@mongodbtutorial.2ulmc.mongodb.net/BlogService?retryWrites=true&w=majority'

const server = async () => {
    try {
        await mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true });

        app.use(express.json());

        app.get('/user', async (req, res) => {
            try {
                const users = await User.find({});
                return res.send({users});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.get('/user/:userId', async (req, res) => {
            try {
                const {userId} = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userID" });
                const user = await User.findOne({_id: userId});
                return res.send({user});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.post('/user', async (req, res) => {
            // try catch 처리를 꼭 해주자.
            try {
                let {username, name} = req.body;
                if (!username) return res.status(400).send({ err: "username is required" });
                if (!name || !name.first || !name.last) return res.status(400).send({err: "Both first and last names are required"});

                const user = new User(req.body); // 원래는 { username: req.body.username, name: { first: ... } 이런식으로 넣으면되는데,
                // req.body가 user의 형태와 똑같다고 가정을하고 req.body 객체를 이렇게 통채로 넣어준다.
                // 위 const user = new User(req.body) 코드의 역할은 document(=row) 생성이다. 즉, 이 코드를 통해 document 인스턴스가 생성된다.
                // 그리고나서 document(=row)를 DB에 저장해야되니까
                // 아래와 같이 몽구스에서 추가된 save라는 메소드를 user.save() 이런식으로 호출하는데,
                // 이 save() 메소드는 Promise 인스턴스를 return하고 document를 돌려준다.
                // 그래서 user.save() 앞엔 await 키워드를 붙여야된다.
                await user.save(); // 그리고 이렇게 user를 저장해준다.
                return res.send({user})
            } catch (err) {
                // catch에서 잡히는 에러는 서버에서 난 에러이다.
                // user가 값을 잘못 입력해서나는 에러는 이미 try 구문에서 다 잡힌다.
                // 그래서 500번대 status를 return한다.
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.delete('/user/:userId', async (req, res) => {
            try {
                const {userId} = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
                const user = await User.findOneAndDelete({_id: userId}); // user 객체가 리턴되면 그 객체가 잘 삭제된거고 null을 리턴하면 애초에 그 객체는 존재하지 않았다는 것

                // const user = await User.deleteOne({_id: userId}); // 그냥 deleteOne 메소드를 사용해도 상관이 없다.
                // findOneAndDelete과 deleteOne의 차이는 user를 반환받을 수 있냐 없냐 차이이다.
                // 만약 받을 필요 없으면 deleteOne이 조금 더 효율적일 것이다.

                return res.send({user});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        // 이제 PUT(update)을 구현해봅시다.
        app.put('/user/:userId', async (req, res) => {
            try {
                const { userId } = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
                // age 정보를 디스트럭쳐링을 통해 req.body에서 빼주고..
                const { age } = req.body;
                if (!age) return res.status(400).send({ err: "age is required" }); // age 값이 있는지 없는지 체크하는 코드
                if (typeof age !== 'number') return res.status(400).send({ err: "age must be a number" }); // age 값 타입 체크하는 코드
                const user = await User.findByIdAndUpdate(userId, { $set: { age } }, { new: true }); // 이것도 delete와 마찬가지로 updateOne 메소드를 사용해도됩니다.
                                                                                                     // 그런데 결과도 받아보고싶으니 findOneAndUpdate 메소드를 쓰도록 하겠습니다.
                return res.send({ user });
                // const user = await User.findById();
                // const user = await User.findByIdAndDelete();
                // 그리고 findOneAndUpdate와 비슷한 메소드로 위와 같이 findById, findByIdAndDelete 등이 있습니다.
            } catch (err) {
                console.log(err);
                return res.status(500).send({ err: err.message });
            }
        })

        app.listen(3000, () => console.log('server listening on port 3000'))
    } catch (err) {
        console.log(err);
    }
}

server();

그래서 GET 요청을해서 데이터를 다시 받아오면 제대로 반영이 되어있는데,

PUT 요청을 보내 업데이트된 데이터를 받아오면 업데이트되기 전의 값으로 받아오기 때문에 업데이트 전 값이 보이는 것이다.

{ new: true }를 저장하고 업데이트 요청을 보내면 업데이트 되고나서의 값이 return된다.

그리고 터미널창보면 또 deprecation 메시지가 나오는데, 지금 mongoose는 내부적으로 findOneAndUpdate 메소드를 호출할 때, useFindAndModify 옵션을 false로 설정하지 않았으면 안된다, 이는 deprecated된 것이다 라는 메시지이다.

그래서 위와 같이 해당 옵션값을 false로 설정하면된다.


const express = require('express');
const app = express();
const mongoose = require('mongoose');
const {User} = require('./models/User'); // 이 User로 이제 데이터베이스 작업을 할 수 있다.

const MONGO_URI = 'mongodb+srv://hyungju-lee:fEMZ9UEE9iSEdb6m@mongodbtutorial.2ulmc.mongodb.net/BlogService?retryWrites=true&w=majority'

const server = async () => {
    try {
        await mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true, useFindAndModify: false });

        app.use(express.json());

        app.get('/user', async (req, res) => {
            try {
                const users = await User.find({});
                return res.send({users});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.get('/user/:userId', async (req, res) => {
            try {
                const {userId} = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userID" });
                const user = await User.findOne({_id: userId});
                return res.send({user});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.post('/user', async (req, res) => {
            // try catch 처리를 꼭 해주자.
            try {
                let {username, name} = req.body;
                if (!username) return res.status(400).send({ err: "username is required" });
                if (!name || !name.first || !name.last) return res.status(400).send({err: "Both first and last names are required"});

                const user = new User(req.body); // 원래는 { username: req.body.username, name: { first: ... } 이런식으로 넣으면되는데,
                // req.body가 user의 형태와 똑같다고 가정을하고 req.body 객체를 이렇게 통채로 넣어준다.
                // 위 const user = new User(req.body) 코드의 역할은 document(=row) 생성이다. 즉, 이 코드를 통해 document 인스턴스가 생성된다.
                // 그리고나서 document(=row)를 DB에 저장해야되니까
                // 아래와 같이 몽구스에서 추가된 save라는 메소드를 user.save() 이런식으로 호출하는데,
                // 이 save() 메소드는 Promise 인스턴스를 return하고 document를 돌려준다.
                // 그래서 user.save() 앞엔 await 키워드를 붙여야된다.
                await user.save(); // 그리고 이렇게 user를 저장해준다.
                return res.send({user})
            } catch (err) {
                // catch에서 잡히는 에러는 서버에서 난 에러이다.
                // user가 값을 잘못 입력해서나는 에러는 이미 try 구문에서 다 잡힌다.
                // 그래서 500번대 status를 return한다.
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.delete('/user/:userId', async (req, res) => {
            try {
                const {userId} = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
                const user = await User.findOneAndDelete({_id: userId}); // user 객체가 리턴되면 그 객체가 잘 삭제된거고 null을 리턴하면 애초에 그 객체는 존재하지 않았다는 것

                // const user = await User.deleteOne({_id: userId}); // 그냥 deleteOne 메소드를 사용해도 상관이 없다.
                // findOneAndDelete과 deleteOne의 차이는 user를 반환받을 수 있냐 없냐 차이이다.
                // 만약 받을 필요 없으면 deleteOne이 조금 더 효율적일 것이다.

                return res.send({user});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        // 이제 PUT(update)을 구현해봅시다.
        app.put('/user/:userId', async (req, res) => {
            try {
                const { userId } = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
                // age 정보를 디스트럭쳐링을 통해 req.body에서 빼주고..
                const { age } = req.body;
                if (!age) return res.status(400).send({ err: "age is required" }); // age 값이 있는지 없는지 체크하는 코드
                if (typeof age !== 'number') return res.status(400).send({ err: "age must be a number" }); // age 값 타입 체크하는 코드
                const user = await User.findByIdAndUpdate(userId, { $set: { age } }, { new: true }); // 이것도 delete와 마찬가지로 updateOne 메소드를 사용해도됩니다.
                                                                                                                   // 그런데 결과도 받아보고싶으니 findOneAndUpdate 메소드를 쓰도록 하겠습니다.
                return res.send({ user });
                // const user = await User.findById();
                // const user = await User.findByIdAndDelete();
                // 그리고 findOneAndUpdate와 비슷한 메소드로 위와 같이 findById, findByIdAndDelete 등이 있습니다.
            } catch (err) {
                console.log(err);
                return res.status(500).send({ err: err.message });
            }
        })

        app.listen(3000, () => console.log('server listening on port 3000'))
    } catch (err) {
        console.log(err);
    }
}

server();

그래서 다시 업데이트 요청을 보내도

deprecated 메시지가 안나온다.

5.10 mongoose가 내부적으로 어떤 작업을 하는가

PUT API를 만들 때 $set을 썼는데, 사실 이거 안써도됩니다.


const express = require('express');
const app = express();
const mongoose = require('mongoose');
const {User} = require('./models/User'); // 이 User로 이제 데이터베이스 작업을 할 수 있다.

const MONGO_URI = 'mongodb+srv://hyungju-lee:fEMZ9UEE9iSEdb6m@mongodbtutorial.2ulmc.mongodb.net/BlogService?retryWrites=true&w=majority'

const server = async () => {
    try {
        await mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true, useFindAndModify: false });

        app.use(express.json());

        app.get('/user', async (req, res) => {
            try {
                const users = await User.find({});
                return res.send({users});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.get('/user/:userId', async (req, res) => {
            try {
                const {userId} = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userID" });
                const user = await User.findOne({_id: userId});
                return res.send({user});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.post('/user', async (req, res) => {
            // try catch 처리를 꼭 해주자.
            try {
                let {username, name} = req.body;
                if (!username) return res.status(400).send({ err: "username is required" });
                if (!name || !name.first || !name.last) return res.status(400).send({err: "Both first and last names are required"});

                const user = new User(req.body); // 원래는 { username: req.body.username, name: { first: ... } 이런식으로 넣으면되는데,
                // req.body가 user의 형태와 똑같다고 가정을하고 req.body 객체를 이렇게 통채로 넣어준다.
                // 위 const user = new User(req.body) 코드의 역할은 document(=row) 생성이다. 즉, 이 코드를 통해 document 인스턴스가 생성된다.
                // 그리고나서 document(=row)를 DB에 저장해야되니까
                // 아래와 같이 몽구스에서 추가된 save라는 메소드를 user.save() 이런식으로 호출하는데,
                // 이 save() 메소드는 Promise 인스턴스를 return하고 document를 돌려준다.
                // 그래서 user.save() 앞엔 await 키워드를 붙여야된다.
                await user.save(); // 그리고 이렇게 user를 저장해준다.
                return res.send({user})
            } catch (err) {
                // catch에서 잡히는 에러는 서버에서 난 에러이다.
                // user가 값을 잘못 입력해서나는 에러는 이미 try 구문에서 다 잡힌다.
                // 그래서 500번대 status를 return한다.
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.delete('/user/:userId', async (req, res) => {
            try {
                const {userId} = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
                const user = await User.findOneAndDelete({_id: userId}); // user 객체가 리턴되면 그 객체가 잘 삭제된거고 null을 리턴하면 애초에 그 객체는 존재하지 않았다는 것

                // const user = await User.deleteOne({_id: userId}); // 그냥 deleteOne 메소드를 사용해도 상관이 없다.
                // findOneAndDelete과 deleteOne의 차이는 user를 반환받을 수 있냐 없냐 차이이다.
                // 만약 받을 필요 없으면 deleteOne이 조금 더 효율적일 것이다.

                return res.send({user});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        // 이제 PUT(update)을 구현해봅시다.
        app.put('/user/:userId', async (req, res) => {
            try {
                const { userId } = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
                // age 정보를 디스트럭쳐링을 통해 req.body에서 빼주고..
                const { age } = req.body;
                if (!age) return res.status(400).send({ err: "age is required" }); // age 값이 있는지 없는지 체크하는 코드
                if (typeof age !== 'number') return res.status(400).send({ err: "age must be a number" }); // age 값 타입 체크하는 코드
                const user = await User.findByIdAndUpdate(userId, { age }, { new: true }); // 이것도 delete와 마찬가지로 updateOne 메소드를 사용해도됩니다.
                                                                                           // 그런데 결과도 받아보고싶으니 findOneAndUpdate 메소드를 쓰도록 하겠습니다.
                return res.send({ user });
                // const user = await User.findById();
                // const user = await User.findByIdAndDelete();
                // 그리고 findOneAndUpdate와 비슷한 메소드로 위와 같이 findById, findByIdAndDelete 등이 있습니다.
            } catch (err) {
                console.log(err);
                return res.status(500).send({ err: err.message });
            }
        })

        app.listen(3000, () => console.log('server listening on port 3000'))
    } catch (err) {
        console.log(err);
    }
}

server();

그냥 위와 같이만해도 됩니다. 위와 같이만 하면 mongoose가 알아서 $set을 추가해줍니다.

그리고 위의 첫번째 인자 userIdstring 타입입니다. '/user/:userId'로 받은 string 타입입니다.

express에서 얘를 string으로 넘겨주지 중간에 object로 바꿔준다거나 그런 작업이 없습니다.

그런데 그냥 string 타입으로 findByIdAndUpdate 메소드의 첫번째 인자로 넘겼는데 문제가 없죠?

이전 강의에서 기억하실지 모르겠지만 위에서 했을 때는 저렇게 string으로 넘기면 오류가 났었습니다.

objectId로 감싸줬어야 했습니다.

여기서 {username: 'hoffrung'}을 치고 find 버튼을 누르면 필터링이 이거는 됩니다.

그런데 id를 그대로 넣어서하면 안돼고 위와 같이 Objectid('여기에아이디') 이렇게해야 필터링이 제대로됩니다. Objectid 없이는 안됩니다.

그래서 이를 몽고DB에서 ObjectId 라이브러리를 가져와가지고 _id로 감싸가지고 호출했어야했는데 몽구스에선 그걸 할 필요가 없다.

(아, 이거 실습하면서 했을 때.. 아 기억난다. 다시한번 확인해봐야겠다)

그럼 어떻게 mongoose가 내부적으로 몽고DB 쿼리를 바꾸는지 확인해보도록 하겠습니다.


const express = require('express');
const app = express();
const mongoose = require('mongoose');
const {User} = require('./models/User'); // 이 User로 이제 데이터베이스 작업을 할 수 있다.

const MONGO_URI = 'mongodb+srv://hyungju-lee:fEMZ9UEE9iSEdb6m@mongodbtutorial.2ulmc.mongodb.net/BlogService?retryWrites=true&w=majority'

const server = async () => {
    try {
        await mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true, useFindAndModify: false });
        mongoose.set('debug', true);
        app.use(express.json());

        app.get('/user', async (req, res) => {
            try {
                const users = await User.find({});
                return res.send({users});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.get('/user/:userId', async (req, res) => {
            try {
                const {userId} = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userID" });
                const user = await User.findOne({_id: userId});
                return res.send({user});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.post('/user', async (req, res) => {
            // try catch 처리를 꼭 해주자.
            try {
                let {username, name} = req.body;
                if (!username) return res.status(400).send({ err: "username is required" });
                if (!name || !name.first || !name.last) return res.status(400).send({err: "Both first and last names are required"});

                const user = new User(req.body); // 원래는 { username: req.body.username, name: { first: ... } 이런식으로 넣으면되는데,
                // req.body가 user의 형태와 똑같다고 가정을하고 req.body 객체를 이렇게 통채로 넣어준다.
                // 위 const user = new User(req.body) 코드의 역할은 document(=row) 생성이다. 즉, 이 코드를 통해 document 인스턴스가 생성된다.
                // 그리고나서 document(=row)를 DB에 저장해야되니까
                // 아래와 같이 몽구스에서 추가된 save라는 메소드를 user.save() 이런식으로 호출하는데,
                // 이 save() 메소드는 Promise 인스턴스를 return하고 document를 돌려준다.
                // 그래서 user.save() 앞엔 await 키워드를 붙여야된다.
                await user.save(); // 그리고 이렇게 user를 저장해준다.
                return res.send({user})
            } catch (err) {
                // catch에서 잡히는 에러는 서버에서 난 에러이다.
                // user가 값을 잘못 입력해서나는 에러는 이미 try 구문에서 다 잡힌다.
                // 그래서 500번대 status를 return한다.
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.delete('/user/:userId', async (req, res) => {
            try {
                const {userId} = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
                const user = await User.findOneAndDelete({_id: userId}); // user 객체가 리턴되면 그 객체가 잘 삭제된거고 null을 리턴하면 애초에 그 객체는 존재하지 않았다는 것

                // const user = await User.deleteOne({_id: userId}); // 그냥 deleteOne 메소드를 사용해도 상관이 없다.
                // findOneAndDelete과 deleteOne의 차이는 user를 반환받을 수 있냐 없냐 차이이다.
                // 만약 받을 필요 없으면 deleteOne이 조금 더 효율적일 것이다.

                return res.send({user});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        // 이제 PUT(update)을 구현해봅시다.
        app.put('/user/:userId', async (req, res) => {
            try {
                const { userId } = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
                // age 정보를 디스트럭쳐링을 통해 req.body에서 빼주고..
                const { age } = req.body;
                if (!age) return res.status(400).send({ err: "age is required" }); // age 값이 있는지 없는지 체크하는 코드
                if (typeof age !== 'number') return res.status(400).send({ err: "age must be a number" }); // age 값 타입 체크하는 코드
                const user = await User.findByIdAndUpdate(userId, { age }, { new: true }); // 이것도 delete와 마찬가지로 updateOne 메소드를 사용해도됩니다.
                                                                                                                   // 그런데 결과도 받아보고싶으니 findOneAndUpdate 메소드를 쓰도록 하겠습니다.
                return res.send({ user });
                // const user = await User.findById();
                // const user = await User.findByIdAndDelete();
                // 그리고 findOneAndUpdate와 비슷한 메소드로 위와 같이 findById, findByIdAndDelete 등이 있습니다.
            } catch (err) {
                console.log(err);
                return res.status(500).send({ err: err.message });
            }
        })

        app.listen(3000, () => console.log('server listening on port 3000'))
    } catch (err) {
        console.log(err);
    }
}

server();

위와 같이 server 코드에 mongoose.set('debug', true); 코드를 넣어주면 쿼리들을 다 볼 수 있습니다.

위 상태에서 다시 PUT을 호출해보면

터미널 창에 나오죠?

위에 보시면 저희는 그냥 첫번째 인자로 userId만 넣어줬는데, 이를 객체 안에 필터형식으로 바꿔서 {_id: ObjectId("")} 이런 형태로 자동으로 바꿔줬습니다.

$setOnInsert: { createdAt: new Data(...) } 이거는 처음에 생성되는 글이면 생성된 시간을 등록하는 건데, 지금은 이미 있는 글이므로 이건 실행되지 않는다.

그 다음에 $set이 업데이트하는 것이다. 우리가 두번째 인자로 넣어준 { age }를 업데이트 해주는 것.

그리고 updatedAt 이것도 mongoose에서 해주는 것이다. 몽고DB에서 해주는 것이 아니라.

업데이트할 때마다 updatedAt$set 해주는 것이다.

그리고 보시면 returnOriginal: false라고 되어있잖아요?

이게 뭐냐면, update되기 전의 문서를 return할 것이냐, <- 원래는 이거 true로 되어있다. 지금 저희가 세번째 인자로 { new: true } 옵션값을 넘겨서그렇지 이 옵션값을 빼고 다시 요청을 보내면 returnOriginal: true로 보인다. 업데이트 되기 전꺼를 보내라 이겁니다.

이번엔 useFindAndModify: false 옵션을 빼고 호출을 해봅시다.


const express = require('express');
const app = express();
const mongoose = require('mongoose');
const {User} = require('./models/User'); // 이 User로 이제 데이터베이스 작업을 할 수 있다.

const MONGO_URI = 'mongodb+srv://hyungju-lee:fEMZ9UEE9iSEdb6m@mongodbtutorial.2ulmc.mongodb.net/BlogService?retryWrites=true&w=majority'

const server = async () => {
    try {
        await mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true });
        mongoose.set('debug', true);
        app.use(express.json());

        app.get('/user', async (req, res) => {
            try {
                const users = await User.find({});
                return res.send({users});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.get('/user/:userId', async (req, res) => {
            try {
                const {userId} = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userID" });
                const user = await User.findOne({_id: userId});
                return res.send({user});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.post('/user', async (req, res) => {
            // try catch 처리를 꼭 해주자.
            try {
                let {username, name} = req.body;
                if (!username) return res.status(400).send({ err: "username is required" });
                if (!name || !name.first || !name.last) return res.status(400).send({err: "Both first and last names are required"});

                const user = new User(req.body); // 원래는 { username: req.body.username, name: { first: ... } 이런식으로 넣으면되는데,
                // req.body가 user의 형태와 똑같다고 가정을하고 req.body 객체를 이렇게 통채로 넣어준다.
                // 위 const user = new User(req.body) 코드의 역할은 document(=row) 생성이다. 즉, 이 코드를 통해 document 인스턴스가 생성된다.
                // 그리고나서 document(=row)를 DB에 저장해야되니까
                // 아래와 같이 몽구스에서 추가된 save라는 메소드를 user.save() 이런식으로 호출하는데,
                // 이 save() 메소드는 Promise 인스턴스를 return하고 document를 돌려준다.
                // 그래서 user.save() 앞엔 await 키워드를 붙여야된다.
                await user.save(); // 그리고 이렇게 user를 저장해준다.
                return res.send({user})
            } catch (err) {
                // catch에서 잡히는 에러는 서버에서 난 에러이다.
                // user가 값을 잘못 입력해서나는 에러는 이미 try 구문에서 다 잡힌다.
                // 그래서 500번대 status를 return한다.
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.delete('/user/:userId', async (req, res) => {
            try {
                const {userId} = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
                const user = await User.findOneAndDelete({_id: userId}); // user 객체가 리턴되면 그 객체가 잘 삭제된거고 null을 리턴하면 애초에 그 객체는 존재하지 않았다는 것

                // const user = await User.deleteOne({_id: userId}); // 그냥 deleteOne 메소드를 사용해도 상관이 없다.
                // findOneAndDelete과 deleteOne의 차이는 user를 반환받을 수 있냐 없냐 차이이다.
                // 만약 받을 필요 없으면 deleteOne이 조금 더 효율적일 것이다.

                return res.send({user});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        // 이제 PUT(update)을 구현해봅시다.
        app.put('/user/:userId', async (req, res) => {
            try {
                const { userId } = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
                // age 정보를 디스트럭쳐링을 통해 req.body에서 빼주고..
                const { age } = req.body;
                if (!age) return res.status(400).send({ err: "age is required" }); // age 값이 있는지 없는지 체크하는 코드
                if (typeof age !== 'number') return res.status(400).send({ err: "age must be a number" }); // age 값 타입 체크하는 코드
                const user = await User.findByIdAndUpdate(userId, { age }, { new: true }); // 이것도 delete와 마찬가지로 updateOne 메소드를 사용해도됩니다.
                                                                                                                   // 그런데 결과도 받아보고싶으니 findOneAndUpdate 메소드를 쓰도록 하겠습니다.
                return res.send({ user });
                // const user = await User.findById();
                // const user = await User.findByIdAndDelete();
                // 그리고 findOneAndUpdate와 비슷한 메소드로 위와 같이 findById, findByIdAndDelete 등이 있습니다.
            } catch (err) {
                console.log(err);
                return res.status(500).send({ err: err.message });
            }
        })

        app.listen(3000, () => console.log('server listening on port 3000'))
    } catch (err) {
        console.log(err);
    }
}

server();

그러면 터미널창에 뜨는게 다르죠?

이전엔 user.findOneAndUpdate(...) 이런식으로 떴는데, 위에 뜬건 다르죠? 이건 옛날 방식으로 호출한겁니다. findAndModify.

이렇게해도 되긴 되는데 deprecation warning이 뜬다는 것. findeOneAndUpdate를 사용하는 것을 권장하는겁니다.

그래서 똑같은 findByIdAndUpdate 메소드를 호출하였음에도 불구하고 옵션값에따라 최신 API를 사용하느냐, 아니면 옛날 API를 사용하느냐가 달라진다.

5.11 debug 모드 설정하기

PUT API를 만들면서 age 값만 수정을 했는데, 여러가지를 수정할 수 있다.

name 프로퍼티도 수정해보도록 하겠다.

수정요청이 여러개가 들어오면 그 수정요청들을 전부 다 보낼 수 있잖아요?

age, name관련 수정요청이 들어온다면, 이 둘 중에 하나만 들어와도 수정요청을 보내야겠죠?

그런데 만약 둘 다 아무것도 수정요청이 안들어올수도있잖아?

그럴 경우를 대비해서 아래와같이

if문을 활용해 agename이 둘 다 없으면 400 에러를 return한다.


const express = require('express');
const app = express();
const mongoose = require('mongoose');
const {User} = require('./models/User'); // 이 User로 이제 데이터베이스 작업을 할 수 있다.

const MONGO_URI = 'mongodb+srv://hyungju-lee:fEMZ9UEE9iSEdb6m@mongodbtutorial.2ulmc.mongodb.net/BlogService?retryWrites=true&w=majority'

const server = async () => {
    try {
        await mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true, useFindAndModify: false });
        mongoose.set('debug', true);
        app.use(express.json());

        app.get('/user', async (req, res) => {
            try {
                const users = await User.find({});
                return res.send({users});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.get('/user/:userId', async (req, res) => {
            try {
                const {userId} = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userID" });
                const user = await User.findOne({_id: userId});
                return res.send({user});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.post('/user', async (req, res) => {
            // try catch 처리를 꼭 해주자.
            try {
                let {username, name} = req.body;
                if (!username) return res.status(400).send({ err: "username is required" });
                if (!name || !name.first || !name.last) return res.status(400).send({err: "Both first and last names are required"});

                const user = new User(req.body); // 원래는 { username: req.body.username, name: { first: ... } 이런식으로 넣으면되는데,
                // req.body가 user의 형태와 똑같다고 가정을하고 req.body 객체를 이렇게 통채로 넣어준다.
                // 위 const user = new User(req.body) 코드의 역할은 document(=row) 생성이다. 즉, 이 코드를 통해 document 인스턴스가 생성된다.
                // 그리고나서 document(=row)를 DB에 저장해야되니까
                // 아래와 같이 몽구스에서 추가된 save라는 메소드를 user.save() 이런식으로 호출하는데,
                // 이 save() 메소드는 Promise 인스턴스를 return하고 document를 돌려준다.
                // 그래서 user.save() 앞엔 await 키워드를 붙여야된다.
                await user.save(); // 그리고 이렇게 user를 저장해준다.
                return res.send({user})
            } catch (err) {
                // catch에서 잡히는 에러는 서버에서 난 에러이다.
                // user가 값을 잘못 입력해서나는 에러는 이미 try 구문에서 다 잡힌다.
                // 그래서 500번대 status를 return한다.
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.delete('/user/:userId', async (req, res) => {
            try {
                const {userId} = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
                const user = await User.findOneAndDelete({_id: userId}); // user 객체가 리턴되면 그 객체가 잘 삭제된거고 null을 리턴하면 애초에 그 객체는 존재하지 않았다는 것

                // const user = await User.deleteOne({_id: userId}); // 그냥 deleteOne 메소드를 사용해도 상관이 없다.
                // findOneAndDelete과 deleteOne의 차이는 user를 반환받을 수 있냐 없냐 차이이다.
                // 만약 받을 필요 없으면 deleteOne이 조금 더 효율적일 것이다.

                return res.send({user});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        // 이제 PUT(update)을 구현해봅시다.
        app.put('/user/:userId', async (req, res) => {
            try {
                const { userId } = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
                // age 정보를 디스트럭쳐링을 통해 req.body에서 빼주고..
                const { age, name } = req.body;
                if (!age && !name) return res.status(400).send({ err: "age or name is required" }); // age, name 둘 다 없는지 체크하는 코드
                if (!age) return res.status(400).send({ err: "age is required" }); // age 값이 있는지 없는지 체크하는 코드
                if (typeof age !== 'number') return res.status(400).send({ err: "age must be a number" }); // age 값 타입 체크하는 코드
                const user = await User.findByIdAndUpdate(userId, { age }, { new: true }); // 이것도 delete와 마찬가지로 updateOne 메소드를 사용해도됩니다.
                                                                                                                   // 그런데 결과도 받아보고싶으니 findOneAndUpdate 메소드를 쓰도록 하겠습니다.
                return res.send({ user });
                // const user = await User.findById();
                // const user = await User.findByIdAndDelete();
                // 그리고 findOneAndUpdate와 비슷한 메소드로 위와 같이 findById, findByIdAndDelete 등이 있습니다.
            } catch (err) {
                console.log(err);
                return res.status(500).send({ err: err.message });
            }
        })

        app.listen(3000, () => console.log('server listening on port 3000'))
    } catch (err) {
        console.log(err);
    }
}

server();

둘 중에 하나라도 수정요청이와야 수정요청을 보낼테니깐.

아래 age 관련 코드는 이제 필요없으므로 지워주고


const express = require('express');
const app = express();
const mongoose = require('mongoose');
const {User} = require('./models/User'); // 이 User로 이제 데이터베이스 작업을 할 수 있다.

const MONGO_URI = 'mongodb+srv://hyungju-lee:fEMZ9UEE9iSEdb6m@mongodbtutorial.2ulmc.mongodb.net/BlogService?retryWrites=true&w=majority'

const server = async () => {
    try {
        await mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true, useFindAndModify: false });
        mongoose.set('debug', true);
        app.use(express.json());

        app.get('/user', async (req, res) => {
            try {
                const users = await User.find({});
                return res.send({users});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.get('/user/:userId', async (req, res) => {
            try {
                const {userId} = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userID" });
                const user = await User.findOne({_id: userId});
                return res.send({user});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.post('/user', async (req, res) => {
            // try catch 처리를 꼭 해주자.
            try {
                let {username, name} = req.body;
                if (!username) return res.status(400).send({ err: "username is required" });
                if (!name || !name.first || !name.last) return res.status(400).send({err: "Both first and last names are required"});

                const user = new User(req.body); // 원래는 { username: req.body.username, name: { first: ... } 이런식으로 넣으면되는데,
                // req.body가 user의 형태와 똑같다고 가정을하고 req.body 객체를 이렇게 통채로 넣어준다.
                // 위 const user = new User(req.body) 코드의 역할은 document(=row) 생성이다. 즉, 이 코드를 통해 document 인스턴스가 생성된다.
                // 그리고나서 document(=row)를 DB에 저장해야되니까
                // 아래와 같이 몽구스에서 추가된 save라는 메소드를 user.save() 이런식으로 호출하는데,
                // 이 save() 메소드는 Promise 인스턴스를 return하고 document를 돌려준다.
                // 그래서 user.save() 앞엔 await 키워드를 붙여야된다.
                await user.save(); // 그리고 이렇게 user를 저장해준다.
                return res.send({user})
            } catch (err) {
                // catch에서 잡히는 에러는 서버에서 난 에러이다.
                // user가 값을 잘못 입력해서나는 에러는 이미 try 구문에서 다 잡힌다.
                // 그래서 500번대 status를 return한다.
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.delete('/user/:userId', async (req, res) => {
            try {
                const {userId} = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
                const user = await User.findOneAndDelete({_id: userId}); // user 객체가 리턴되면 그 객체가 잘 삭제된거고 null을 리턴하면 애초에 그 객체는 존재하지 않았다는 것

                // const user = await User.deleteOne({_id: userId}); // 그냥 deleteOne 메소드를 사용해도 상관이 없다.
                // findOneAndDelete과 deleteOne의 차이는 user를 반환받을 수 있냐 없냐 차이이다.
                // 만약 받을 필요 없으면 deleteOne이 조금 더 효율적일 것이다.

                return res.send({user});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        // 이제 PUT(update)을 구현해봅시다.
        app.put('/user/:userId', async (req, res) => {
            try {
                const { userId } = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
                // age 정보를 디스트럭쳐링을 통해 req.body에서 빼주고..
                const { age, name } = req.body;
                if (!age && !name) return res.status(400).send({ err: "age or name is required" }); // age, name 둘 다 없는지 체크하는 코드
                if (typeof age !== 'number') return res.status(400).send({ err: "age must be a number" }); // age 값 타입 체크하는 코드
                const user = await User.findByIdAndUpdate(userId, { age }, { new: true }); // 이것도 delete와 마찬가지로 updateOne 메소드를 사용해도됩니다.
                                                                                           // 그런데 결과도 받아보고싶으니 findOneAndUpdate 메소드를 쓰도록 하겠습니다.
                return res.send({ user });
                // const user = await User.findById();
                // const user = await User.findByIdAndDelete();
                // 그리고 findOneAndUpdate와 비슷한 메소드로 위와 같이 findById, findByIdAndDelete 등이 있습니다.
            } catch (err) {
                console.log(err);
                return res.status(500).send({ err: err.message });
            }
        })

        app.listen(3000, () => console.log('server listening on port 3000'))
    } catch (err) {
        console.log(err);
    }
}

server();

이제 위와 같이 수정해준다.

age가 존재는하나 number가 아니라면 에러를 return한다.

name도 마찬가지로 존재는하나 first, laststring이 아닌 경우는 에러를 return한다.


const express = require('express');
const app = express();
const mongoose = require('mongoose');
const {User} = require('./models/User'); // 이 User로 이제 데이터베이스 작업을 할 수 있다.

const MONGO_URI = 'mongodb+srv://hyungju-lee:fEMZ9UEE9iSEdb6m@mongodbtutorial.2ulmc.mongodb.net/BlogService?retryWrites=true&w=majority'

const server = async () => {
    try {
        await mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true, useFindAndModify: false });
        mongoose.set('debug', true);
        app.use(express.json());

        app.get('/user', async (req, res) => {
            try {
                const users = await User.find({});
                return res.send({users});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.get('/user/:userId', async (req, res) => {
            try {
                const {userId} = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userID" });
                const user = await User.findOne({_id: userId});
                return res.send({user});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.post('/user', async (req, res) => {
            // try catch 처리를 꼭 해주자.
            try {
                let {username, name} = req.body;
                if (!username) return res.status(400).send({ err: "username is required" });
                if (!name || !name.first || !name.last) return res.status(400).send({err: "Both first and last names are required"});

                const user = new User(req.body); // 원래는 { username: req.body.username, name: { first: ... } 이런식으로 넣으면되는데,
                // req.body가 user의 형태와 똑같다고 가정을하고 req.body 객체를 이렇게 통채로 넣어준다.
                // 위 const user = new User(req.body) 코드의 역할은 document(=row) 생성이다. 즉, 이 코드를 통해 document 인스턴스가 생성된다.
                // 그리고나서 document(=row)를 DB에 저장해야되니까
                // 아래와 같이 몽구스에서 추가된 save라는 메소드를 user.save() 이런식으로 호출하는데,
                // 이 save() 메소드는 Promise 인스턴스를 return하고 document를 돌려준다.
                // 그래서 user.save() 앞엔 await 키워드를 붙여야된다.
                await user.save(); // 그리고 이렇게 user를 저장해준다.
                return res.send({user})
            } catch (err) {
                // catch에서 잡히는 에러는 서버에서 난 에러이다.
                // user가 값을 잘못 입력해서나는 에러는 이미 try 구문에서 다 잡힌다.
                // 그래서 500번대 status를 return한다.
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.delete('/user/:userId', async (req, res) => {
            try {
                const {userId} = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
                const user = await User.findOneAndDelete({_id: userId}); // user 객체가 리턴되면 그 객체가 잘 삭제된거고 null을 리턴하면 애초에 그 객체는 존재하지 않았다는 것

                // const user = await User.deleteOne({_id: userId}); // 그냥 deleteOne 메소드를 사용해도 상관이 없다.
                // findOneAndDelete과 deleteOne의 차이는 user를 반환받을 수 있냐 없냐 차이이다.
                // 만약 받을 필요 없으면 deleteOne이 조금 더 효율적일 것이다.

                return res.send({user});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        // 이제 PUT(update)을 구현해봅시다.
        app.put('/user/:userId', async (req, res) => {
            try {
                const { userId } = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
                // age 정보를 디스트럭쳐링을 통해 req.body에서 빼주고..
                const { age, name } = req.body;
                if (!age && !name) return res.status(400).send({ err: "age or name is required" }); // age, name 둘 다 없는지 체크하는 코드
                if (age && typeof age !== 'number') return res.status(400).send({ err: "age must be a number" }); // age 값 타입 체크하는 코드
                if (name && typeof name.first !== 'string' && typeof name.last !== 'string') return res.status(400).send({ err: "first and last name" }); // name이 존재는하나 first, last가 string이 아닌 경우는 에러를 return한다.
                const user = await User.findByIdAndUpdate(userId, { age }, { new: true }); // 이것도 delete와 마찬가지로 updateOne 메소드를 사용해도됩니다.
                                                                                                         // 그런데 결과도 받아보고싶으니 findOneAndUpdate 메소드를 쓰도록 하겠습니다.
                return res.send({ user });
                // const user = await User.findById();
                // const user = await User.findByIdAndDelete();
                // 그리고 findOneAndUpdate와 비슷한 메소드로 위와 같이 findById, findByIdAndDelete 등이 있습니다.
            } catch (err) {
                console.log(err);
                return res.status(500).send({ err: err.message });
            }
        })

        app.listen(3000, () => console.log('server listening on port 3000'))
    } catch (err) {
        console.log(err);
    }
}

server();

위와 같이하고 두번째 인자에 name 프로퍼티를 추가한다.


const express = require('express');
const app = express();
const mongoose = require('mongoose');
const {User} = require('./models/User'); // 이 User로 이제 데이터베이스 작업을 할 수 있다.

const MONGO_URI = 'mongodb+srv://hyungju-lee:fEMZ9UEE9iSEdb6m@mongodbtutorial.2ulmc.mongodb.net/BlogService?retryWrites=true&w=majority'

const server = async () => {
    try {
        await mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true, useFindAndModify: false });
        mongoose.set('debug', true);
        app.use(express.json());

        app.get('/user', async (req, res) => {
            try {
                const users = await User.find({});
                return res.send({users});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.get('/user/:userId', async (req, res) => {
            try {
                const {userId} = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userID" });
                const user = await User.findOne({_id: userId});
                return res.send({user});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.post('/user', async (req, res) => {
            // try catch 처리를 꼭 해주자.
            try {
                let {username, name} = req.body;
                if (!username) return res.status(400).send({ err: "username is required" });
                if (!name || !name.first || !name.last) return res.status(400).send({err: "Both first and last names are required"});

                const user = new User(req.body); // 원래는 { username: req.body.username, name: { first: ... } 이런식으로 넣으면되는데,
                // req.body가 user의 형태와 똑같다고 가정을하고 req.body 객체를 이렇게 통채로 넣어준다.
                // 위 const user = new User(req.body) 코드의 역할은 document(=row) 생성이다. 즉, 이 코드를 통해 document 인스턴스가 생성된다.
                // 그리고나서 document(=row)를 DB에 저장해야되니까
                // 아래와 같이 몽구스에서 추가된 save라는 메소드를 user.save() 이런식으로 호출하는데,
                // 이 save() 메소드는 Promise 인스턴스를 return하고 document를 돌려준다.
                // 그래서 user.save() 앞엔 await 키워드를 붙여야된다.
                await user.save(); // 그리고 이렇게 user를 저장해준다.
                return res.send({user})
            } catch (err) {
                // catch에서 잡히는 에러는 서버에서 난 에러이다.
                // user가 값을 잘못 입력해서나는 에러는 이미 try 구문에서 다 잡힌다.
                // 그래서 500번대 status를 return한다.
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.delete('/user/:userId', async (req, res) => {
            try {
                const {userId} = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
                const user = await User.findOneAndDelete({_id: userId}); // user 객체가 리턴되면 그 객체가 잘 삭제된거고 null을 리턴하면 애초에 그 객체는 존재하지 않았다는 것

                // const user = await User.deleteOne({_id: userId}); // 그냥 deleteOne 메소드를 사용해도 상관이 없다.
                // findOneAndDelete과 deleteOne의 차이는 user를 반환받을 수 있냐 없냐 차이이다.
                // 만약 받을 필요 없으면 deleteOne이 조금 더 효율적일 것이다.

                return res.send({user});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        // 이제 PUT(update)을 구현해봅시다.
        app.put('/user/:userId', async (req, res) => {
            try {
                const { userId } = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
                // age 정보를 디스트럭쳐링을 통해 req.body에서 빼주고..
                const { age, name } = req.body;
                if (!age && !name) return res.status(400).send({ err: "age or name is required" }); // age, name 둘 다 없는지 체크하는 코드
                if (age && typeof age !== 'number') return res.status(400).send({ err: "age must be a number" }); // age 값 타입 체크하는 코드
                if (name && typeof name.first !== 'string' && typeof name.last !== 'string') return res.status(400).send({ err: "first and last name" }); // name이 존재는하나 first, last가 string이 아닌 경우는 에러를 return한다.
                const user = await User.findByIdAndUpdate(userId, { age, name }, { new: true }); // 이것도 delete와 마찬가지로 updateOne 메소드를 사용해도됩니다.
                                                                                                         // 그런데 결과도 받아보고싶으니 findOneAndUpdate 메소드를 쓰도록 하겠습니다.
                return res.send({ user });
                // const user = await User.findById();
                // const user = await User.findByIdAndDelete();
                // 그리고 findOneAndUpdate와 비슷한 메소드로 위와 같이 findById, findByIdAndDelete 등이 있습니다.
            } catch (err) {
                console.log(err);
                return res.status(500).send({ err: err.message });
            }
        })

        app.listen(3000, () => console.log('server listening on port 3000'))
    } catch (err) {
        console.log(err);
    }
}

server();

이렇게하면 문제가 하나 있다.

일단 이렇게해서 돌려보도록 하겠다.

만약 age 값을 string으로 보냈다면 위와 같이 에러가 잘 나온다.

그리고 위와 같이 아무것도 안보냈을 경우도 에러가 잘 나온다.

age 값을 50으로하고 요청을 보내보자. 그럼 { new: true }이기 때문에 업데이트된 후의 값을 return해준다.

age50으로 잘 바뀐 것을 확인할 수 있다. 그런데 name 값이 null로 바뀌어있다.

GET으로 조회해보면 namenull로 바뀌어있는 것을 확인할 수 있다.

여기서 기억하셔야될게 2가지가 있는데, 일단 첫번째로 이렇게된 이유가 뭐냐면,

위의 코드에서 age값을 수정요청할 때, name값은 수정요청하지 않는 경우, 즉 name값이 존재하지 않는 경우가 있잖아요?

그래서 age 수정요청을 보내는데 name이 없을 경우, namenull이 들어가니깐 namenull로 수정된 것이다.

console.log(name);을 찍으면 name 수정값이 없다면 null이나 undefined가 나올 것입니다.

그래서 이러한 문제를 고쳐주려면 어떻게해야되느냐,


const express = require('express');
const app = express();
const mongoose = require('mongoose');
const {User} = require('./models/User'); // 이 User로 이제 데이터베이스 작업을 할 수 있다.

const MONGO_URI = 'mongodb+srv://hyungju-lee:fEMZ9UEE9iSEdb6m@mongodbtutorial.2ulmc.mongodb.net/BlogService?retryWrites=true&w=majority'

const server = async () => {
    try {
        await mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true, useFindAndModify: false });
        mongoose.set('debug', true);
        app.use(express.json());

        app.get('/user', async (req, res) => {
            try {
                const users = await User.find({});
                return res.send({users});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.get('/user/:userId', async (req, res) => {
            try {
                const {userId} = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userID" });
                const user = await User.findOne({_id: userId});
                return res.send({user});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.post('/user', async (req, res) => {
            // try catch 처리를 꼭 해주자.
            try {
                let {username, name} = req.body;
                if (!username) return res.status(400).send({ err: "username is required" });
                if (!name || !name.first || !name.last) return res.status(400).send({err: "Both first and last names are required"});

                const user = new User(req.body); // 원래는 { username: req.body.username, name: { first: ... } 이런식으로 넣으면되는데,
                // req.body가 user의 형태와 똑같다고 가정을하고 req.body 객체를 이렇게 통채로 넣어준다.
                // 위 const user = new User(req.body) 코드의 역할은 document(=row) 생성이다. 즉, 이 코드를 통해 document 인스턴스가 생성된다.
                // 그리고나서 document(=row)를 DB에 저장해야되니까
                // 아래와 같이 몽구스에서 추가된 save라는 메소드를 user.save() 이런식으로 호출하는데,
                // 이 save() 메소드는 Promise 인스턴스를 return하고 document를 돌려준다.
                // 그래서 user.save() 앞엔 await 키워드를 붙여야된다.
                await user.save(); // 그리고 이렇게 user를 저장해준다.
                return res.send({user})
            } catch (err) {
                // catch에서 잡히는 에러는 서버에서 난 에러이다.
                // user가 값을 잘못 입력해서나는 에러는 이미 try 구문에서 다 잡힌다.
                // 그래서 500번대 status를 return한다.
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        app.delete('/user/:userId', async (req, res) => {
            try {
                const {userId} = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
                const user = await User.findOneAndDelete({_id: userId}); // user 객체가 리턴되면 그 객체가 잘 삭제된거고 null을 리턴하면 애초에 그 객체는 존재하지 않았다는 것

                // const user = await User.deleteOne({_id: userId}); // 그냥 deleteOne 메소드를 사용해도 상관이 없다.
                // findOneAndDelete과 deleteOne의 차이는 user를 반환받을 수 있냐 없냐 차이이다.
                // 만약 받을 필요 없으면 deleteOne이 조금 더 효율적일 것이다.

                return res.send({user});
            } catch (err) {
                console.log(err);
                return res.status(500).send({err: err.message});
            }
        })

        // 이제 PUT(update)을 구현해봅시다.
        app.put('/user/:userId', async (req, res) => {
            try {
                const { userId } = req.params;
                if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
                // age 정보를 디스트럭쳐링을 통해 req.body에서 빼주고..
                const { age, name } = req.body;
                if (!age && !name) return res.status(400).send({ err: "age or name is required" }); // age, name 둘 다 없는지 체크하는 코드
                if (age && typeof age !== 'number') return res.status(400).send({ err: "age must be a number" }); // age 값 타입 체크하는 코드
                if (name && typeof name.first !== 'string' && typeof name.last !== 'string') return res.status(400).send({ err: "first and last name" }); // name이 존재는하나 first, last가 string이 아닌 경우는 에러를 return한다.
                let updateBody = {};
                if (age) updateBody.age = age;
                if (name) updateBody.name = name;
                const user = await User.findByIdAndUpdate(userId, updateBody, { new: true }); // 이것도 delete와 마찬가지로 updateOne 메소드를 사용해도됩니다.
                                                                                                     // 그런데 결과도 받아보고싶으니 findOneAndUpdate 메소드를 쓰도록 하겠습니다.
                return res.send({ user });
                // const user = await User.findById();
                // const user = await User.findByIdAndDelete();
                // 그리고 findOneAndUpdate와 비슷한 메소드로 위와 같이 findById, findByIdAndDelete 등이 있습니다.
            } catch (err) {
                console.log(err);
                return res.status(500).send({ err: err.message });
            }
        })

        app.listen(3000, () => console.log('server listening on port 3000'))
    } catch (err) {
        console.log(err);
    }
}

server();

위와 같이 수정해야된다. 위와 같이 바꾸면 agename이 각각 진짜로 있을 때만 수정요청이 보내진다.

수정요청이 없는데 아까처럼 괜히 null값이 들어가서 수정되거나하지 않을 것이다.


const {Schema, model} = require('mongoose');

// Schema 첫번째 인자 : 해당 데이터 값의 정의
// Schema 두번째 인자 : 옵션
const UserSchema = new Schema({
    username: {type: String, required: true, unique: true},
    name: {
        first: {type: String, required: true},
        last: {type: String, required: true}
    },
    age: Number, // required: true 가 아니라면 이렇게 type만 넣을 수도 있다.
    email: String,
}, {
    timestamps: true // createdAt(생상한 시간)을 자동으로 넣어준다.
                     // 그리고 update할 때마다 updateAt이라는 키를 수정해준다.
                     // 언제 생성되고 언제 수정되었는지를 전부 자동으로 넣어준다.
})

// 위와 같이 만들었으면 mongoose한테 알려줘야된다.
const User = model('user', UserSchema); // 몽구스에 알려주는 것이다. user라는 콜렉션을 만들거야. 그리고 그 콜렉션은 위와 같은 데이터형태를 가지고 있어.
// 실제 콜렉션은 users 라는 이름으로 뒤에 s가 붙어서 생성이 된다.

module.exports = { User };

그런데 저희가 위에서 namefirst, last가 있고 각각 required: true라고 설정했잖아요?

그런데 이게 무시가됐습니다. 이게 조금 아쉬운점이긴한데, mongoose가 이를 확인할 수 있는 시점은 언제냐면,

저희가 POST 같은걸 할 때 보면, new User(req.body); 코드로 User 인스턴스를 여기서 만들고있죠?

여기서 console.log(user);를 해보면, await user.save(); 코드 전에. 그럼 user document가 나온다.

user document를 백앤드 서버에서 먼저 생성을하고 그 다음에 그걸 user.save();로 저장한겁니다.

이때 mongoose가 확인을 할 수가 있습니다. 아래 코드를 가지고.

업데이트(PUT)도 마찬가지로 저희가 바로 db에서 업데이트를 했는데,

이거처럼 User를 불러온다음에 이 user 객체를 수정을하고 그 다음에 save를해서 업데이트할 수도 있습니다.

그렇게할 경우엔

이게 확인이 됩니다.

이 내용을 잘 아셔야되는데, 여튼 위와 같이 수정하면 이 문제는 해결이되는데, 그런데 이상한점이 하나가 있죠?

분명히 여기서는 namefirst, last가 필수다라고 되어있습니다.

그럼 애초에 에러를 뱉어냈어야하는데 에러가나질 않았다. 이 부분은 좀이따 자세히 설명드리겠다.

여튼 일단 이렇게하고,

name 프로퍼티에 first, last를 다 넣어서 요청을 보내면 다 잘 수정된채로 결과값이 return된다.

다시 이번엔 age30으로해서 수정요청을 보내면, 이번에는 age만 잘 수정된다. namenull로 바뀌지 않는다.

위에 터미널창보면 이전엔 nameundefined가 들어가있는 것을 볼 수 있다. 그래서 name이 다 날라간거였고,

마지막 요청을 보면 name 수정요청을 안보냈었잖아? 그래서 age만 들어가있는 것을 볼 수 있다.

nameage가 둘 다 있을 땐, 둘 다 들어가고.

반대로 name만 있다면, age는 바뀌지않고 name만 바뀌었죠?

터미널창을 보면 name: {first: ..., last: ...} 있는데 age는 없다.

5.12 라우터 리팩토링

다음 강의로 넘어가기전에 코드 리팩토링을 하겠습니다.

지금 저희가 사실 server.js 파일에다가만 코드를 작성했잖아요?

server.js 파일은 사실 서버를 세팅하는 용도 정도로만 사용하는 파일이고 로직은 따로 빼도록 하겠습니다.

routes라는 폴더를 하나 만들도록 하겠습니다.

이 폴더 안에 userRoute.js파일을 생성하겠습니다. 이 안에 user 관련 API를 다 모아두도록 하겠습니다.

이런식으로 CRUD API들을 다 userRoute.js로 옮기도록 하겠습니다.

그런데 여기선 app 변수를 쓰는 것이 아니고 여기서 필요한 것이 Router인데 이는 express 라이브러리에서 제공해준다.

이렇게 Routerrequire해와서 새로운 userRouter 변수에 Router를 호출해 할당해준다.

그리고 위와 같이 app 대신에 userRouter로 바꿔준다.

그리고 위에 /user/:userId 부분 있잖아요? 여기서 user를 다 날려줄겁니다.

즉, /:userId 이런식으로..

user 들어가있는 부분들은 다 날려줍시다.

그리고 마지막으로 module.exports = { userRouter } 코드를 통해 userRouter를 외부로 내보낸다.

그리고 server.js로 돌아가서

위와 같이 require해오고 app.use('/user', userRouter);라는 미들웨어를 등록한다.

그리고 const { User } = require('./models/user'); 이 코드는 더이상 여기에 있을 필요가 없죠?

삭제해줍니다.

삭제한 그 코드는 userRoute.js 파일에 필요하므로 여기에 추가해줍니다.

이렇게 수정하면 똑같이 작동을 할겁니다.

server.js에선 설정만해주면 되므로 훨씬 더 깔끔해졌습니다.

GET 요청을 보내도 제대로 작동하는 것을 확인할 수 있습니다.


root_folder/
|-- src/
|   |-- models/
|       `-- User.js
|   |-- routes/
|       `-- userRoute.js
|   `-- server.js
|-- .gitignore
|-- package.json


// src/server.js
const express = require('express');
const app = express();
const mongoose = require('mongoose');
const {userRouter} = require("./routes/userRoute"); // 이 User로 이제 데이터베이스 작업을 할 수 있다.

const MONGO_URI = 'mongodb+srv://hyungju-lee:fEMZ9UEE9iSEdb6m@mongodbtutorial.2ulmc.mongodb.net/BlogService?retryWrites=true&w=majority'

const server = async () => {
    try {
        await mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true, useFindAndModify: false });
        mongoose.set('debug', true);
        app.use(express.json());

        app.use('/user', userRouter);

        app.listen(3000, () => console.log('server listening on port 3000'))
    } catch (err) {
        console.log(err);
    }
}

server();


// src/routes/userRoute.js
const {Router} = require("express");
const userRouter = Router();
const {User} = require("../models/User");
const mongoose = require("mongoose");

userRouter.get('/', async (req, res) => {
    try {
        const users = await User.find({});
        return res.send({users});
    } catch (err) {
        console.log(err);
        return res.status(500).send({err: err.message});
    }
})

userRouter.get('/:userId', async (req, res) => {
    try {
        const {userId} = req.params;
        if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userID" });
        const user = await User.findOne({_id: userId});
        return res.send({user});
    } catch (err) {
        console.log(err);
        return res.status(500).send({err: err.message});
    }
})

userRouter.post('/', async (req, res) => {
    // try catch 처리를 꼭 해주자.
    try {
        let {username, name} = req.body;
        if (!username) return res.status(400).send({ err: "username is required" });
        if (!name || !name.first || !name.last) return res.status(400).send({err: "Both first and last names are required"});

        const user = new User(req.body); // 원래는 { username: req.body.username, name: { first: ... } 이런식으로 넣으면되는데,
        // req.body가 user의 형태와 똑같다고 가정을하고 req.body 객체를 이렇게 통채로 넣어준다.
        // 위 const user = new User(req.body) 코드의 역할은 document(=row) 생성이다. 즉, 이 코드를 통해 document 인스턴스가 생성된다.
        // 그리고나서 document(=row)를 DB에 저장해야되니까
        // 아래와 같이 몽구스에서 추가된 save라는 메소드를 user.save() 이런식으로 호출하는데,
        // 이 save() 메소드는 Promise 인스턴스를 return하고 document를 돌려준다.
        // 그래서 user.save() 앞엔 await 키워드를 붙여야된다.
        await user.save(); // 그리고 이렇게 user를 저장해준다.
        return res.send({user})
    } catch (err) {
        // catch에서 잡히는 에러는 서버에서 난 에러이다.
        // user가 값을 잘못 입력해서나는 에러는 이미 try 구문에서 다 잡힌다.
        // 그래서 500번대 status를 return한다.
        console.log(err);
        return res.status(500).send({err: err.message});
    }
})

userRouter.delete('/:userId', async (req, res) => {
    try {
        const {userId} = req.params;
        if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
        const user = await User.findOneAndDelete({_id: userId}); // user 객체가 리턴되면 그 객체가 잘 삭제된거고 null을 리턴하면 애초에 그 객체는 존재하지 않았다는 것

        // const user = await User.deleteOne({_id: userId}); // 그냥 deleteOne 메소드를 사용해도 상관이 없다.
        // findOneAndDelete과 deleteOne의 차이는 user를 반환받을 수 있냐 없냐 차이이다.
        // 만약 받을 필요 없으면 deleteOne이 조금 더 효율적일 것이다.

        return res.send({user});
    } catch (err) {
        console.log(err);
        return res.status(500).send({err: err.message});
    }
})

// 이제 PUT(update)을 구현해봅시다.
userRouter.put('/:userId', async (req, res) => {
    try {
        const { userId } = req.params;
        if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
        // age 정보를 디스트럭쳐링을 통해 req.body에서 빼주고..
        const { age, name } = req.body;
        if (!age && !name) return res.status(400).send({ err: "age or name is required" }); // age, name 둘 다 없는지 체크하는 코드
        if (age && typeof age !== 'number') return res.status(400).send({ err: "age must be a number" }); // age 값 타입 체크하는 코드
        if (name && typeof name.first !== 'string' && typeof name.last !== 'string') return res.status(400).send({ err: "first and last name" }); // name이 존재는하나 first, last가 string이 아닌 경우는 에러를 return한다.
        let updateBody = {};
        if (age) updateBody.age = age;
        if (name) updateBody.name = name;
        const user = await User.findByIdAndUpdate(userId, updateBody, { new: true }); // 이것도 delete와 마찬가지로 updateOne 메소드를 사용해도됩니다.
        // 그런데 결과도 받아보고싶으니 findOneAndUpdate 메소드를 쓰도록 하겠습니다.
        return res.send({ user });
        // const user = await User.findById();
        // const user = await User.findByIdAndDelete();
        // 그리고 findOneAndUpdate와 비슷한 메소드로 위와 같이 findById, findByIdAndDelete 등이 있습니다.
    } catch (err) {
        console.log(err);
        return res.status(500).send({ err: err.message });
    }
})

module.exports = {
    userRouter
}

5.13 VS code Plugin 설정하기

그리고 vscode 설치할 때 좋은 플러그인들이 많다고 했잖아요?

유용한 플러그인 2개를 설치하겠습니다.

위 플러그인을 설치합시다. 안하셔도되는데, 이 플러그인을 설치하면 각 파일 확장자별로 아이콘이 달라져 눈에 잘 들어오더라구요.

그리고 또 하나는 code formatter를 설치하도록할게요.

prettier라고 이거를 설치하도록 하겠습니다.

이렇게하고나면 코드를 작성하면서 저장을하면 코드가 저절로 보기좋은 형태로 수정됩니다. (줄맞춤, 들여쓰기 등등)

설치했는데 자동으로 안바뀌신다면, 맥에선 command + ,, 윈도에선 ctrl + ,를 누르시면

이렇게뜨는데, 여기서

editor save를 검색하시고 Format On Save가 체크가되어있는지 확인해보셔야된다.

5.14 findOneAndUpdate VS save

좀 전에 updateBody에 대해 언급을 했었는데, 위와 같이 코드를 updateBody로 수정한 이유가 age 수정요청이 있을 때, name은 수정요청도 없는데도 불구하고 null이 설정되면서 수정되었기 때문에 그 오류를 해결하기 위함이었다.

그런데 name은 애초에 firstlastrequired: true로 설정되어있음에도 불구하고 에러가 발생하지 않았다.

왜 이러한 현상이 발생했는지 설명하도록 하겠다.

지금 저희가 한 방식은 위와 같습니다.

일단 Client가 있고, 저희의 Client는 지금 Postman이죠?

그리고 Backend Node.js 서버가있고

Database MongoDB가 있는데,

Client에서 BackendPUT/user/123123, {age: 30} 요청을 보냈습니다.

123123 id userage30으로 바꿔라.

라고 보냈는데, name이 빠졌었잖아요? name은 빠지고 age만 들어가있죠?

그럼 Backend Node.js 서버에서 age의 값이 숫자로 들어왔는지 validation하는 과정이 있었고, validation을 통과하면 바로 mongoDB로 비동기 호출을 합니다. (User.findOneAndUpdate)

그럼 mongoDB는 이 요청받은 정보를 기반으로해서 해당 정보를 업데이트하고 업데이트된 문서, user document를 return합니다.

그리고 Backend Node.js 서버는 return받은걸 약간의 가공을해서 다시 Client에게 돌려줍니다.


현재 이런 과정인데, 이런 과정에서는 아쉽게도 Update 과정이 온전히 모두 mongoDB에서 일어나는거거든요?

이 경우에는 mongoose에서 설정한 내용들을 검증하는 거를 하질 않습니다.

그 과정이 생략이됩니다.

그래서 이 문제를 해결할 수 있는 방법은

전에 해결했던 이 방법을 쓰셔도되고, (위 방법도 어쨌든 name이 null이 아닐 때, first와 last가 모두 있는지 검사하긴하니까)

그게아니라 mongoose의 설정, 위 코드를 사용하고 싶다면, 위 코드를 사용해 mongoose 오류를 뱉어내게하고싶다면

mongoose 오류를 뱉어내는 경우는 위 코드란 말이죠?

const user = new User(req.body);

이 부분.

위 코드와 put 요청을 보내는 코드의 차이는 뭐냐면, 위 코드는 일단 mongoose를 통해서 User 인스턴스를 만들었습니다. User 객체를 만든거죠.

만든 다음에 user.save();를 통해 저장을 했습니다.

그런데 delete도 그렇고 put을 보면 User를 여기서 불러온다음에 수정한 것이 아니라,

const user = await User.findByIdAndUpdate(userId, updateBody, {new: true});

userId에 해당하는 User가 있으면, 두번째 인자대로 수정을해라! 라고 mongoDB에 말을 해주는 것입니다.

그래서 Update를 Post처럼 불러온다음에 Node에서 수정을하고 Node에서 mongoose로 검증을 한 다음에 mongoDB보고 이거 적용해줘! 라고 하는 방법이 있습니다.

그 방법이 바로 이겁니다.

똑같이 Client에서 Backend로 PUT /user/123123, {age: 30} 호출이 들어옵니다.

똑같이 age에 들어온 값이 number인지 아닌지 validation을 합니다.

그런데 이전엔 바로 User.findOneAndUpdate를 통해 찾는 것과 Update를 동시에 실행했잖아요?

그런데 이번엔 findOne해서 해당된 User를 불러옵니다.

그럼 Backend 서버엔 해당 User 인스턴스(데이터, document)가 있겠죠?

그러면 Backend에서 수동으로 user update를 해줍니다. 그 다음에 user.save()를 호출하게되면 mongoose가 check를 해줍니다.

그럼 만약에 뭔가 잘못된 정보다 그러면 오류를 뱉어낼 것입니다.

그럼 애초부터 이렇게하면되지 않느냐 할 수 있는데, 차이가 있습니다. 위의 로직을 보시면 알겠지만 데이터베이스를 2번 왔다갔다해야됩니다.

user를 찾는데 한번, save하는데 한번.

아까 이전 코드에서는 1번만 왔다갔다하면 됐죠? 그래서 1번만 왔다갔다하면되는 코드가 약간 더 빠릅니다.

그런데 그렇다고 2번 왔다갔다하는 것이 크게 느리다거나 그러진 않습니다.

DB 여러번 왔다갔다 거리는 것은 자주 있는 일이라서 이렇게 하는 것 자체는 큰 문제가 되지 않습니다.

그리고 또 하나 이렇게했을 때 장점이 있는데, 지금 저희가 예시로 다루는 User 객체는 간단하잖아요? 그런데 앞으로 쓸 객체들이 굉장히 복잡해질 수가 있거든요? 그래서 한번 왔다갔다거리는 것만으로 수정이 힘들 수가 있습니다.

그런 경우에는 DB에서 불러와서 Node에서 수정을하고 그 다음에 Update를 하는 것이 좋습니다. 그래서 위와 같은 방법도 앞으로 사용할 일이 생길 수도 있습니다.

5.15 save로 document 수정하기

위 방법으로 Update하는 것도 해볼게요.

위 부분을 다 주석처리하고

let user = await User.findById(userId); 코드를 통해 mongoDB에서 불러오고

if (age) user.age = age;

age가 존재하면 user.ageage로 바꿔줍니다.

if (name) user.name = name;

name도 마찬가지.

그 다음에

await user.save();

위와 같이 코드를 작성해주면된다.

그래서 console.log로 수정되기 전 user와 수정 후 user를 찍어 비교를해보면,

으잉? mongoose is not defined.라는 에러가 떴다.

몽구스를 안불러왔었다.

const mongoose = require('mongoose');

다시 수정요청을하면 수정이 제대로된 것을 볼 수 있다.

터미널창에서 보면

처음에 mongoDB에서 User를 불러온 것.

그래서 수정하기 전 불러온 데이터의 값은 이건데,

수정 후 데이터의 값은 이거라는 것이다.

그리고 user.save(); 코드가 실행되면 실제론 users.updateOne();이라는 메소드가 실행되는 것을 볼 수 있다.

mongoose가 userBeforeEdit에 담겨있는 값과 userAfterEdit에 담겨있는 값을 비교해 바뀐 부분을 찾는 것이다.

name이 바뀌었잖아?

그래서 위에 name$set해주는 것이다. age는 안들어가있죠? 바뀐 부분만 찾아서 처리를 해주는겁니다.

여튼 코드상으론 user.save()이지만 실제론 users.updateOne()이 호출된다는 것!

그리고 이렇게 수정하면 따로 { new: true }같이 해줄필요가 없다. 왜냐면 그 아래 if (age) user.age = age; 이런 코드를 통해 Updateuser를 이미 가지고있잖아?

그래서 return되는건 Update된 user가 return된다.

이전 코드같은 경우는 { new: true }를 설정해줘야

이 과정에서 Update를 하고 Update된 데이터를 찾는 과정까지 하는 것이다. (user document) 그래서 return까지 해주는 것이다.

findOneAndUpdate가 아닌 updateOne만해주면 return안하고 Update만 하고 끝이긴한데, 이게 좀 길이가 차이가 있을 수 있긴한데,

그렇다고해서 위 방식이 더 비효율적인 것은 아니다. 효율적이긴 더 효율적이다.

왜냐하면 mongoDB에서 데이터를 Update하고 찾는 시간보다 Backend에서 Database 왔다갔다하는 시간이 더 걸리기 때문이다.

위 로직은 1번만 왔다갔다하면되지만,

이건 2번을 왔다갔다해야된다.

그래서 한번에 처리할 수 있으면 주석처리한 코드처럼 처리하시고

그게아니라 데이터구조가 복잡해서 직접 수정을 해야된다고한다면, 아니면 여러개의 인스턴스를 불러와서 복합적으로 처리를해야된다 그런 경우들이 생길 수도 있거든요. 그럴 땐 위와 같이 수정해주시면된다.

console.log 지우고 위와 같이 남겨둘게요.


// src/routes/userRoute.js
const {Router} = require("express");
const userRouter = Router();
const {User} = require("../models/User");
const mongoose = require("mongoose");

// REST API 메소드
// 첫번째 인자: End Point
// 두번째 인자: 콜백함수 - 이 함수는 두개의 인자를 받는다.
// '/user'에 get 요청이 오면 아래의 콜백함수가 실행이되는 것이다.
userRouter.get('/', async (req, res) => {
    // 첫번째 인자 req: 클라이언트에서 요청이올 때, ReqBody, ReqHeader, url 등등 그런 정보들이 모두 들어있다.
    // 두번째 인자 res: 클라이언트에 응답할 때 필요한 모든 정보들이 들어있다. 지금부터 저희가 작성할 내용 외에도 기본적으로 들어가야되는 네트워크 정보라던지 그런 것들이 모두 여기 들어있다.
    try {
        const users = await User.find({});
        return res.send({users});
    } catch (err) {
        console.log(err);
        return res.status(500).send({err: err.message});
    }
})

userRouter.get('/:userId', async (req, res) => {
    try {
        const {userId} = req.params;
        if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userID" });
        const user = await User.findOne({_id: userId});
        return res.send({user});
    } catch (err) {
        console.log(err);
        return res.status(500).send({err: err.message});
    }
})

// post 메소드를 사용한다.
userRouter.post('/', async (req, res) => {
    // try catch 처리를 꼭 해주자.
    try {
        let {username, name} = req.body;
        if (!username) return res.status(400).send({ err: "username is required" });
        if (!name || !name.first || !name.last) return res.status(400).send({err: "Both first and last names are required"});

        const user = new User(req.body); // 원래는 { username: req.body.username, name: { first: ... } 이런식으로 넣으면되는데,
        // req.body가 user의 형태와 똑같다고 가정을하고 req.body 객체를 이렇게 통채로 넣어준다.
        // 위 const user = new User(req.body) 코드의 역할은 document(=row) 생성이다. 즉, 이 코드를 통해 document 인스턴스가 생성된다.
        // 그리고나서 document(=row)를 DB에 저장해야되니까
        // 아래와 같이 몽구스에서 추가된 save라는 메소드를 user.save() 이런식으로 호출하는데,
        // 이 save() 메소드는 Promise 인스턴스를 return하고 document를 돌려준다.
        // 그래서 user.save() 앞엔 await 키워드를 붙여야된다.
        await user.save(); // 그리고 이렇게 user를 저장해준다.
        return res.send({user}); // return 키워드를 붙여주는 것이 좋다.
        // 이렇게 코드 작성할리는 없겠지만
        // res.send() 코드를 중복해서 작성해놨을 경우 모든 res.send()가 호출된다.
        // 그렇게되는 것을 방지하기 위해서 return 키워드를 작성한다.
    } catch (err) {
        // catch에서 잡히는 에러는 서버에서 난 에러이다.
        // user가 값을 잘못 입력해서나는 에러는 이미 try 구문에서 다 잡힌다.
        // 그래서 500번대 status를 return한다.
        console.log(err);
        return res.status(500).send({err: err.message});
    }
})

userRouter.delete('/:userId', async (req, res) => {
    try {
        const {userId} = req.params;
        if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
        const user = await User.findOneAndDelete({_id: userId}); // user 객체가 리턴되면 그 객체가 잘 삭제된거고 null을 리턴하면 애초에 그 객체는 존재하지 않았다는 것

        // const user = await User.deleteOne({_id: userId}); // 그냥 deleteOne 메소드를 사용해도 상관이 없다.
        // findOneAndDelete과 deleteOne의 차이는 user를 반환받을 수 있냐 없냐 차이이다.
        // 만약 받을 필요 없으면 deleteOne이 조금 더 효율적일 것이다.

        return res.send({user});
    } catch (err) {
        console.log(err);
        return res.status(500).send({err: err.message});
    }
})

// 이제 PUT(update)을 구현해봅시다.
userRouter.put('/:userId', async (req, res) => {
    try {
        const { userId } = req.params;
        if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" });
        // age 정보를 디스트럭쳐링을 통해 req.body에서 빼주고..
        const { age, name } = req.body;
        if (!age && !name) return res.status(400).send({ err: "age or name is required" }); // age, name 둘 다 없는지 체크하는 코드
        if (age && typeof age !== 'number') return res.status(400).send({ err: "age must be a number" }); // age 값 타입 체크하는 코드
        if (name && typeof name.first !== 'string' && typeof name.last !== 'string') return res.status(400).send({ err: "first and last name" }); // name이 존재는하나 first, last가 string이 아닌 경우는 에러를 return한다.
        // let updateBody = {};
        // if (age) updateBody.age = age;
        // if (name) updateBody.name = name;
        // const user = await User.findByIdAndUpdate(userId, updateBody, { new: true }); // 이것도 delete와 마찬가지로 updateOne 메소드를 사용해도됩니다.
        // 그런데 결과도 받아보고싶으니 findOneAndUpdate 메소드를 쓰도록 하겠습니다.

        let user = await User.findById(userId);
        console.log({userBeforeEdit: user});
        if (age) user.age = age;
        if (name) user.name = name;
        console.log({userAfterEdit: user});
        await user.save();
        return res.send({ user });
    } catch (err) {
        console.log(err);
        return res.status(500).send({ err: err.message });
    }
})

module.exports = {
    userRouter
}