오늘은 몰랐으면 내일은 알면 된다
[MongoDB 입문] 6. Schema&Model, 간단한 User API 만들어보기 본문
[Schema, Model]
폴더구조를 다음과 같이 만들고, User API를 만들기 위해서 User.js파일을 생성한다.

const mongoose = require('mongoose')
const UserSchema = new mongoose.Schema({
})
Schema에는 User라는 객체는 어떠한 key value를 가지는지, 각 key value의 필수 여부등의 정보를 명시해준다.
그러면 mongoose에서 Schema에 맞는 데이터인지 한번 확인을 거치고 맞는 경우에 DB에 저장을 해준다.
const UserSchema = new mongoose.Schema({
username: {type: String, required: true},
name: {
first: {type:String, required: true},
last: {type:String, required: true}
},
age: Number,
email: String
}, { timestamps: true })
Schema는 두가지 인자를 가질 수 있는데, 첫번째 인자에는 위와 같이 조건들을 명시해준다. 두번째에 들어가 있는 timestamp라는 조건은 데이터가 생성되거나 수정되었을 때 시간을 기록하는 용도라고 한다.
const User = mongoose.model('user',UserSchema)
module.exports = {User}
Schema 작성이 완료되면, 위와 같이 mongoose에게 알려준다.
user라는 collection을 만들텐데, 거기에 들어가는 데이터는 UserSchema를 따르겠다는 표시라고 한다.
이제 완성된 내용을 server.js에 추가해준다.
const express = require('express')
const mongoose = require('mongoose')
const {User} = require('./models/User')
const app = express()
const users = []
//..생략
[POST]
이제 DB와 연결하는 작업 및 데이터를 넣을 준비가 완료되었으니, 직접 넣어보도록 하자.
먼저 server.js의 내용을 아래와 같이 바꿔준다.
app.post('/user', async (req, res) => {
const user = new User(req.body)
await user.save()
return res.send({ user })
})
그 다음 Postman에서 Schema에 맞게 데이터를 세팅하고 POST요청을 보내보면,

아래와 같이 데이터가 잘 들어가는 것을 확인할 수 있다. BlogService는 MONGO_URI에 세팅한 DB이름이다.

그러나 이때, Schema에 맞지 않는 데이터를 송신하게 되면 오류가 난 상태로 먹통이 된다.
그래서 다음과 같이 두가지 오류 처리를 해준다.
3~4라인은 클라이언트의 입력이 올바르지 않을 경우 400과 함께 메세지를 보내는 방법이고,
catch문은 서버사이드에 오류가 발생했을 때 500을 보내는 방법이다.
app.post('/user', async (req, res) => {
try {
let {username, name} = req.body
if(!username) return res.status(400).send({err: "username required"})
if(!name || !name.first || !name.last) return res.status(400).send({err: "name required"})
const user = new User(req.body)
await user.save()
return res.send({ user })
} catch(err) {
console.log(err)
return res.status(500).send({ err: err.message })
}
})
참고) 다음의 1라인은 2,3라인을 한줄로 선언한 것이다. (Destructuring)
let {username, name} = req.body
let username = req.body.username
let name = req.body.name
[GET]
이제 user를 GET을 통해 받아와보자.
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 })
}
})
Postman으로 GET 요청을 보내보면 아래와 같이 users라는 key에 객체 배열이 담겨서 반환된 것을 확인할 수 있다.

[Unique Index]
모든 값을 동일하게 세팅한다음 POST요청을 보내면, 별탈없이 들어가는 것을 확인할 수 있다.

그러나 보통 id같은 경우에는 중복을 허용하지 않기 때문에 Unique 조건을 걸어야 될 때가 있다.
Unique 조건 또한 Schema에서 세팅을 해준다.(User.js)
const UserSchema = new mongoose.Schema({
username: {type: String, required: true, unique: true},
강의에서는 별도의 option을 줘야했는데, 버전업이 되면서 그럴 필요가 없어졌는지 그냥 위와 같이 작성하고 저장만 하면 알아서 읽어들이는 것 같았다.

참고로 이미 중복된 document들이 존재하는 경우에는 unique index를 생성할 수 없기 때문에 중복 데이터를 지우고 진행해야 한다.
앞에서 중복으로 생성한 데이터를 수동으로 지우고 다시 중복 데이터 넣기를 시도해보자.

그러면 이제 duplicate key가 있어 error가 날아오는 것을 확인할 수 있다.
[GET]
이제 특정 username을 가진 사용자를 GET으로 가져와보자.
app.get('/user/:userId', async (req, res) => {
try {
const {userId} = req.params
const user = await User.findOne({ username: userId })
return res.send({ user })
} catch(err) {
console.log(err)
return res.status(500).send({err: err.message})
}
})


[DELETE]
delete도 GET user와 거의 비슷하다.
app.delete('/user/:userId', async (req, res) => {
try {
const {userId} = req.params
const user = await User.deleteOne({ username: userId })
return res.send({ user })
} catch(err) {
console.log(err)
return res.status(500).send({ err: err.message })
}
})

[PUT]
메소드 사용법만 약간 다르고, 그 외의 큰 골격은 동일하다.
app.put('/user/:userId', async (req, res) => {
try {
const {userId} = req.params
const {age} = req.body
if(!age) return res.status(400).send({ err: "age required" })
const user = await User.findOneAndUpdate({ username: userId }, {$set: { age }})
return res.send({user})
} catch(err) {
console.log(err)
return res.status(500).send({ err: err.message })
}
})
findOneAndUpdate는 new option을 주지 않으면 업데이트 이전의 document를 반환한다.
const user = await User.findOneAndUpdate({ username: userId }, {$set: { age }}, {new: true})
$set을 사용하지 않고 key value만 입력해도 mongoose가 알아서 넣어준다.
name도 수정을 해보도록 하자. 그 전에 debug 모드를 켜준다.
mongoose.set('debug', true)
클라이언트가 준 값의 유효성 검사를 다음과 같이 추가할 수도 있다.
app.put('/user/:userId', async (req, res) => {
try {
const {userId} = req.params
const {age, name} = req.body
if(!age && !name) return res.status(400).send({ err: "age or name required" })
if(age && typeof age !== 'number') return res.status(400).send({ err: "age must be a number" })
if(name && typeof name.first !== 'string' && typeof name.last !== 'string') return res.status(400).send({ err: "name shoud be string" })
let updateBody = {}
if(age) updateBody.age = age
if(name) updateBody.name = name
const user = await User.findOneAndUpdate({ username: userId }, {$set: updateBody}, {new: true})
return res.send({user})
} catch(err) {
console.log(err)
return res.status(500).send({ err: err.message })
}
})
server.js에는 최소한의 설정 코드만 있도록 리팩토링한다.
먼저 route 폴더를 만들고 server.js에서 user 관련 코드들을 다 가져온다.

const {Router} = require('express')
const userRouter = Router()
const {User} = require('../models/User')
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
const user = await User.findOne({ username: userId })
return res.send({ user })
} catch(err) {
console.log(err)
return res.status(500).send({err: err.message})
}
})
userRouter.post('/', async (req, res) => {
try {
let {username, name} = req.body
if(!username) return res.status(400).send({err: "username required"})
if(!name || !name.first || !name.last) return res.status(400).send({err: "name required"})
const user = new User(req.body)
await user.save()
return res.send({ user })
} catch(err) {
console.log(err)
return res.status(500).send({ err: err.message })
}
})
userRouter.delete('/:userId', async (req, res) => {
try {
const {userId} = req.params
const user = await User.deleteOne({ username: userId })
return res.send({ user })
} catch(err) {
console.log(err)
return res.status(500).send({ err: err.message })
}
})
userRouter.put('/:userId', async (req, res) => {
try {
const {userId} = req.params
const {age, name} = req.body
if(!age && !name) return res.status(400).send({ err: "age or name required" })
if(age && typeof age !== 'number') return res.status(400).send({ err: "age must be a number" })
if(name && typeof name.first !== 'string' && typeof name.last !== 'string') return res.status(400).send({ err: "name shoud be string" })
let updateBody = {}
if(age) updateBody.age = age
if(name) updateBody.name = name
console.log(updateBody)
const user = await User.findOneAndUpdate({ username: userId }, {$set: updateBody}, {new: true})
return res.send({user})
} catch(err) {
console.log(err)
return res.status(500).send({ err: err.message })
}
})
module.exports = {
userRouter
}
server.js에서는 해당 라우터를 사용할 것이라고 명시해준다.
const {userRouter} = require('./routes/userRoute')
//..생략
app.use('/user', userRouter)
/user로 들어오는 요청은 userRouter가 처리하겠다는 뜻이다.
위의 방식대로 PUT을 하게 되면 User.js에서 설정해둔 Schema가 적용되지 않는 문제점이 있었다.

Update과정이 온전히 DB에서 일어나기 때문에 mongoose설정을 사용하지 못한다.
와중에 이 Schema가 적용이 되었던 Post의 메소드를 보면, User 객체를 만들고, save()를 통해서 데이터를 저장하는 것을 확인할 수 있다.
const user = new User(req.body)
await user.save()
Post 메소드에서 했던 것 처럼 해당 User 정보를 불러와서 Node 상에서 mongoose 검증을 거친 뒤, DB에 수정 요청을 보내는 방법으로 변경할 수 있다.

그러나 이 방법은 또 한계가 있는데, DB를 두번 왔다갔다 하는 문제점이 있다. 그래서 앞의 방법이 약간 더 빠르다고 할 수 있겠다.
(네트워크 비용을 생각해보면 한번에 처리가능할 때는 한번에 하는 편이 낫겠다)
객체가 복잡해지는 경우에는 위와같이 mongoose 검증을 거쳐서 데이터를 조작하는 경우가 발생할 수도 있다고 한다.
save를 통해 데이터를 업데이트 하려면 코드를 아래와 같이 수정할 수 있다.
userRouter.put('/:userId', async (req, res) => {
try {
const {userId} = req.params
const {age, name} = req.body
if(!age && !name) return res.status(400).send({ err: "age or name required" })
if(age && typeof age !== 'number') return res.status(400).send({ err: "age must be a number" })
if(name && typeof name.first !== 'string' && typeof name.last !== 'string') return res.status(400).send({ err: "name shoud be string" })
// let updateBody = {}
// if(age) updateBody.age = age
// if(name) updateBody.name = name
// const user = await User.findOneAndUpdate({ username: userId }, updateBody, {new: true})
let user = await User.findOne({ username: userId })
if(age) user.age = age
if(name) user.name = name
await user.save()
return res.send({user})
} catch(err) {
console.log(err)
return res.status(500).send({ err: err.message })
}
})
'DB' 카테고리의 다른 글
| [MongoDB 입문] 5. 비동기 프로그래밍 예시로 이해해보기 (0) | 2023.01.19 |
|---|---|
| [MongoDB 입문] 4. Express 사용해보기 (0) | 2023.01.19 |
| [MongoDB 입문] 3. NodeJS설치 및 기초세팅 (0) | 2023.01.19 |
| [MongoDB 입문] 2. 간단한 조작(CRUD), database구조 차이 (0) | 2023.01.19 |
| [MongoDB 입문] 1. MongoDB Atlas 설정하기 (0) | 2023.01.19 |