Node.js) MongoDB 접속 후 DB 다루기

2023. 3. 4. 15:51개발/Node.js

728x90

Chapter

2023.03.03 - [개발/MongoDB] - MongoDB) 계정 만들고 db 생성하고 CRUD 해보기

2023.03.03 - [개발/Node.js] - Node.js) 설치부터 REST API 만들어보기

2023.03.04 - [개발/Node.js] - Node.js) 비동기 프로그래밍 (Asynchronous Programming) 공부해보기

2023.03.04 - [개발/Node.js] - Node.js) MongoDB 접속 후 DB 다루기

참고

 

해당 내용은 강의를 기반으로 작성하였습니다. 
강의를 들으면 더욱 상세한 내용을 얻으실 수 있으니, 강의를 듣기를 권장합니다.
https://www.inflearn.com/course/%EB%AA%BD%EA%B3%A0%EB%94%94%EB%B9%84-%EA%B8%B0%EC%B4%88-%EC%8B%A4%EB%AC%B4/dashboard

 

MongoDB Drive Node.js 

 

https://www.mongodb.com/docs/drivers/node/current/

 

MongoDB Node Driver — Node.js

Developer Data Platform Innovate fast at scale with a unified developer experience

www.mongodb.com

https://www.npmjs.com/package/mongodb

 

mongodb

The official MongoDB driver for Node.js. Latest version: 5.1.0, last published: 8 days ago. Start using mongodb in your project by running `npm i mongodb`. There are 11546 other projects in the npm registry using mongodb.

www.npmjs.com

 

mongodb보다 편의기능이 많아서 mongoose 사용

https://www.npmjs.com/package/mongoose

 

mongoose

Mongoose MongoDB ODM. Latest version: 7.0.0, last published: 4 days ago. Start using mongoose in your project by running `npm i mongoose`. There are 13529 other projects in the npm registry using mongoose.

www.npmjs.com

 

Node.js에서 Mongodb connect 하기

connect your application 선택

server node.js 선택 (상황에 맞게 선택 파이썬이면 Python)

아래 url 복사해서 사용하기

반응형

Node.js에서 접속해 보기

const MONGO_URL = 'mongodb+srv://admin:<password>@cluster0.rcubev9.mongodb.net/<service_name>?retryWrites=true&w=majority'

async를 쓰기 위해서는 let으로 지정해야 함. -> 그걸 하기 위해선 함수로 감싸야함.

const mongoose = require('mongoose')
mongoose.connect(MONGO_URL).then(result => console.log({ result }))

이런 식으로 asycn를 해서 사용할 수 있다고 함.

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

    app.use(express.json())
    app.get("/user", function (req, res) {
        return res.send({ users: users })
    })

}

server();

server.js

const server = async () => {
    try {
        await mongoose.connect(MONGO_URL);
        console.log("MongoDB connected")
        app.use(express.json())

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

server();

 

Collection 만들기

일단 기존에 server 만든 server.js를 src 폴더로 이동

그래서 아래와 같은 형식으로 구조를 짜주기

|-- src

      |-- server.js

      |-- models

          |-- User.js

 

 

Schema를 만들기

User.js

const mongoose = require('mongoose');
// 스키마 배열 정보 표시 (mongoose가 체크해주고 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 })

// mongoose에 schema 전달 (collection을 만든다는 뜻)
const User = mongoose.model("user", UserSchema)
// 외부에서 가져다가 쓴다는 뜻
module.exports = { User }

Server.js에 반영하기

server.js

- asycn 할 때는 항상 return을 해주기

const express = require("express");
const app = express();
const mongoose = require('mongoose')
// shell에서 user.update find와 비슷한 개념이라고 보면 된다고 함.
const { User } = require("./models/User")

const MONGO_URL = 'mongodb+srv://admin:<password>@cluster0.rcubev9.mongodb.net/BlogService?retryWrites=true&w=majority'

const server = async () => {
    try {
        await mongoose.connect(MONGO_URL);
        console.log("MongoDB connected")
        app.use(express.json())
        app.get("/user", async (req, res) => {
            try {
                const users = await User.find({});
                return res.send({ users: users })
            }
            catch (err) {
                console.log({ err })
                return res.status(500).send({ err: err.message })
            }
        })
        app.post("/user", async (req, res) => {
            try {
                // descturected(한 버전)
                let { username, name } = req.body;
                // descturected(안한 버전)
                // let username = req.body.username;
                // let name = req.body.name;
                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" })
                // document 생성
                const user = new User(req.body);
                // save를 호출하면 promise에서 document를 돌려줌 
                await user.save();
                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();

status는 다음과 같음.

 

status를 어느 정도는 잘 알아둬야 올바른 status를 return 할 수 있어 보임.

POST /user/ (CREATE)

Postman에서 Post 날려서 DB에 Document 저장하기

잘 작동하는 것 확인함.

올바른 파라미터를 준 경우

__id : 고유 ID

__v : save 할 때마다 숫자가 올라감 (데이터의 버전)

MongoDB에서 잘 저장되어 있는지 확인

BlogService에 user라는 것이 생김

즉 user라는 collection의 하나의 Document가 들어간 것을 확인?

 

GET /user/ (READ)

Postman에서 Get 날려서 Collection에 있는 Document들 불러오기

array 행태로 나오는 것을 확인.

Username을 Unique 하게 관리하기

const mongoose = require('mongoose');
// 스키마 배열 정보 표시 (mongoose가 체크해주고 db에 저장해주는 역할)
const UserSchema = new mongoose.Schema({
	// unique : true를 추가
    username: { type: String, required: true, unique: true },
    name: {
        first: { type: String, required: true },
        last: { type: String, required: true },
    },
    age: Number,
    email: String
}, { timestamps: true })

// mongoose에 schema 전달 (collection을 만든다는 뜻)
const User = mongoose.model("user", UserSchema)
// 외부에서 가져다가 쓴다는 뜻
module.exports = { User }

server.js를 새로 키면 username이 추가되는 것을 확인할 수 있다.

Post를 통해 기존 유저를 동일하게 추가하려고 하면 중복키 에러 뜨는 것 확인

 

GET /user/:userId 

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 })
    }
})

올바르게 불러오지 못한 경우

올바르게 불러온 경우

DELETE /user/:userId (DELETE)

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 })
        // const user = await User.deleteOne({ _id: userId })
        return res.send({ user })
    } catch (err) {
        console.log({ err })
        return res.status(500).send({ err: err.message })
    }
})

삭제 후 다시 조회한 결과

PUT /user/:userId (UPDATE)

mongoose는 아쉬운 점이 /user에서 User 객체를 수정해서 업데이트하면 반영되지만, 그냥 바꿔버리면 required를 만족하지 않고 그냥 넘어갈 수도 있다.

app.put("/user/:userId", async (req, res) => {
    try {
        const { userId } = req.params;
        if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" })
        const { age } = req.body;
        if (!age) return res.status(400).send({ err: "age is required" })
        if (typeof age !== 'number') return res.status(400).send({ err: "age must be a number" })
        // findByIdAndUpdate user id를 이용해서 바로 얻을 수 있음 (간결하게 할 수 있음)
        // const user = await User.findByIdAndUpdate(userId)
        const user = await User.findByIdAndUpdate(userId, { $set: { age } }, { new: true })
        // const user = await User.findByIdAndUpdate(userId, { age }, { new: true })
        return res.send({ user })
    } catch (err) {
        console.log({ err })
        return res.status(500).send({ err: err.message })
    }
}
)

99에서 24로 변경된 것을 확인함.

일부 코드 추가

name, age 확인하는 부분

app.put("/user/:userId", async (req, res) => {
    try {
        const { userId } = req.params;
        if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" })
        const { age, name } = req.body;
        if (!age && !name) return res.status(400).send({ err: 'age or name is required' })
        if (!age) return res.status(400).send({ err: "age is 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: "first and last name are strings" })
        // findByIdAndUpdate user id를 이용해서 바로 얻을 수 있음 (간결하게 할 수 있음)
        // const user = await User.findByIdAndUpdate(userId)
        // const user = await User.findByIdAndUpdate(userId, { $set: { age } }, { new: true })
        let updateBody = {};
        if (age) updateBody.age = age
        if (name) updateBody.name = name
        const user = await User.findByIdAndUpdate(userId, updateBody, { new: true })
        return res.send({ user })
    } catch (err) {
        console.log({ err })
        return res.status(500).send({ err: err.message })
    }
}
)

 

Mongoose가 내부적으로 어떻게 처리하는지 보기

해당 코드 추가

mongoose.set("debug", true)

내부적으로 이런 식으로 작동된다는 것을 확인할 수 있음.

server listening on port 3000
Mongoose: users.createIndex({ username: 1 }, { unique: true, background: true })
Mongoose: users.find({}, { projection: null })
Mongoose: users.findOneAndUpdate({ _id: ObjectId("6402e5f87fc5100dadf7878d") }, { '$setOnInsert': { createdAt: new Date("Sat, 04 Mar 2023 08:16:06 GMT") }, '$set': { age: 56, updatedAt: new Date("Sat, 04 Mar 2023 08:16:06 GMT") }}, { projection: {}, returnDocument: 'after', returnOriginal: false })

 

 

코드 리펙토링

지금까지 코드를 전부 server.js으로 했는데, 해당 코드들은 userRoute.js으로 관리하는 것이 좋다.

실제로 server.js는 거의 바꾸지 않는 것이 좋다고 함.

|-- src

      |-- server.js

      |-- models

          |-- User.js

      |-- routes

          |-- userRoute.js

      |-- html

          |-- index.html

 

server.js

 

const express = require("express");
const app = express();
const mongoose = require('mongoose')
// shell에서 user.update find와 비슷한 개념이라고 보면 된다고 함.
const { userRouter } = require("./routes/userRoute")

const MONGO_URL = 'mongodb+srv://admin:admin1!1@cluster0.rcubev9.mongodb.net/BlogService?retryWrites=true&w=majority'

const server = async () => {
    try {
        await mongoose.connect(MONGO_URL);
        mongoose.set("debug", true)
        console.log("MongoDB connected")
        app.use(express.json())
        // endpoint 가 user로 시작하면 저기로 연결해라
        app.use("/user", userRouter)

        // : 를 사용하면 변수로 받을 수 잇음
        app.get('/index', async (req, res) => {
            try {
                console.log({ __dirname })
                return res.sendFile(__dirname + '/html/index.html')
            } 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();

 

userRoute.js

해당 방법이 작동하는 이유는 express의 Router라는 것 때문에 이렇게 할 수 있다고 한다.

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: 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 {
        // descturected(한 버전)
        let { username, name } = req.body;
        // descturected(안한 버전)
        // let username = req.body.username;
        // let name = req.body.name;
        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" })
        // document 생성
        const user = new User(req.body);
        // save를 호출하면 promise에서 document를 돌려줌 
        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;
        if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" })
        const user = await User.findOneAndDelete({ _id: userId })
        return res.send({ user })
    } catch (err) {
        console.log({ err })
        return res.status(500).send({ err: err.message })
    }
})
// update
userRouter.put("/:userId", async (req, res) => {
    try {
        const { userId } = req.params;
        if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" })
        const { age, name } = req.body;
        if (!age && !name) return res.status(400).send({ err: 'age or name is required' })
        if (!age) return res.status(400).send({ err: "age is 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: "first and last name are strings" })
        // findByIdAndUpdate user id를 이용해서 바로 얻을 수 있음 (간결하게 할 수 있음)
        // const user = await User.findByIdAndUpdate(userId)
        // const user = await User.findByIdAndUpdate(userId, { $set: { age } }, { new: true })
        let updateBody = {};
        if (age) updateBody.age = age
        if (name) updateBody.name = name
        const user = await User.findByIdAndUpdate(userId, updateBody, { new: true })
        return res.send({ user })
    } catch (err) {
        console.log({ err })
        return res.status(500).send({ err: err.message })
    }
}
)
// user router를 외부 노출
module.exports = {
    userRouter
}

 

VSCODE Plugin 추천(icon, prettier)

icon 이쁘게

prettier 줄맞춤 자동

findOneAndUpdate vs save

작업한 것들이 이런 구조라고 함.

update 하는 과정이 mongoDB에서 하기 때문에 mongoose를 거치지 않고 수정이 됨.

 

기존 구조

신규 구조

 

userRouter.put("/:userId", async (req, res) => {
    try {
        const { userId } = req.params;
        if (!mongoose.isValidObjectId(userId)) return res.status(400).send({ err: "invalid userId" })
        const { age, name } = req.body;
        if (!age && !name) return res.status(400).send({ err: 'age or name is required' })
        // if (!age) return res.status(400).send({ err: "age is 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: "first and last name are strings" })
        // findByIdAndUpdate user id를 이용해서 바로 얻을 수 있음 (간결하게 할 수 있음)
        // const user = await User.findByIdAndUpdate(userId)
        // const user = await User.findByIdAndUpdate(userId, { $set: { age } }, { new: true })
        // let updateBody = {};
        // if (age) updateBody.age = age
        // if (name) updateBody.name = name
        // const user = await User.findByIdAndUpdate(userId, updateBody, { new: true })
        let user = await User.findById(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 })
    }
}
)

아래 결과를 보면 아까랑은 다르게 first와 last를 필수로 설정하는 메시지가 뜬다.

이게 더 정확하게 할 수는 있지만, 그만큼 호출이 많이 되기 때문에 아무래도 시간효율성이 떨어진다.

 

github

https://github.com/sungreong/mongodb_and_nodejs_study.git

 

GitHub - sungreong/mongodb_and_nodejs_study

Contribute to sungreong/mongodb_and_nodejs_study development by creating an account on GitHub.

github.com

 

728x90