API에 MongoDB를 연결해 데이터를 주고 받기 위해서는 JS 코드에서도 DB Client 역할을 하는 무언가가 있어야 한다.
이를 mongoos라는 도구를 이용해 DB에 연결을 해볼 예정이다.
mongoose는 MongoDB에 데이터를 쉽게 읽고 쓰게 해주는 JavaScript 라이브러리이다.
mongoose를 ODM(Object Document Mapper)이라고도 부른다.
ODM(Object Document Mapper)이란, JavaScript의 객체(Object)와 MongoDB의 문서(Document) 사이에서 ‘매핑’을 수행하는 도구이다. JS 코드에서 작업하는 객체를 MongoDB 데이터베이스의 문서로 좀 더 쉽게 변환하거나, 반대로 문서를 객체로 변환해주는 작업을 수행한다.
설치 방법은 mongoose를 사용할 프로젝트에서 터미널을 열고 다음과 같은 명령어를 입력한다.
yarn add mongoose
mongoose 문서(Document)
좀 더 보기 쉽게 정리하면 다음과 같다.
- MongoDB에서 가지고 있는 각 데이터 하나하나를 문서(Document)라고 정의한다.
- 1개 이상의 Key-Value의 쌍으로 이루어져있다.
- JSON 형식으로 구성되어 있다.
{
// 데이터를 구분하기 위한 고유 ID
"_id": ObjectId("6682192a1c155bd2f27881"),
// 추가한 값
"name": "lyw",
}
mongoose의 컬렉션(Collection)
여러개의 문서(Document)를 보유할 수 있는 MongoDB의 구성요소이다. JSON 형식의 여러가지 문서(Document)를 보유할 수 있으며, 고정된 구성요소가 존재하지 않고, 유연하게 구성할 수 있다.(왜냐하면 비관계형 DB이기 때문에)
관계형 데이터베이스(RDB)의 Table과 동일한 역할을 한다.
mongoose의 스키마(Schema)
컬렉션(Collection)에 들어가는 문서(Document)가 어떤 종류의 값을 가질 것인지 정의하기위해 사용된다.
즉, 스키마는 DB의 구조와 제약조건에 관한 전반적인 명세를 기술한 것이라 보면된다.
스키마 위치는 다음 이미지를 참고 바란다.

스키마는 크게 외부 스키마, 개념 스키마, 내부 스키마가 있다.
하지만 이는 관계형 DB(Relational DB)에서 주로 다루는 개념이고, 비관계형 DB(NoSQL)에서는 크게 신경쓰지 않아도 된다.(물론 사용하려면 사용할 수 있다고는 한다.)
JS에서 스키마를 지정하고 난 뒤에 해당 스키마를 바탕으로 모델을 설정하고, 다시 그 모델을 바탕으로 DB를 사용한다. 그래서 스키마는 데이터의 구조와 어떤 제약 사항을 가지는지 정의하기 위해 사용하며, 일반적으로 데이터를 모델링할 때 사용한다고 볼 수 있다.
또한 스키마는 어떤 필드가 있어야 하는지, 필드는 어떤 데이터 타입을 가져야 하는지를 정의한다.
간단한 예시는 다음과 같다.
const UsersSchema = new mongoose.Schema({
name: String, // 문자열 타입입니다.
age: Number, // 숫자 타입입니다.
favorites: [String], // 문자열 배열 타입입니다.
createdAt: { type: Date, default: Date.now }, // 날짜 타입입니다.
someId: mongoose.Schema.Types.ObjectId // ObjectId 타입입니다.
});
- 대표적인 스키마 타입들
- null : null 값과 존재하지 않는 필드
ex: null - String : 문자열
ex: "mongoDB" - Number : 숫자
ex: 3.14 - Date : 날짜
ex: new Date() - Buffer : 파일을 담을 수 있는 버퍼, UTF-8이 아닌 문자열을 저장
ex: 0x65 - Boolean : true or false
ex: true - ObjectId(Schema.Types.ObjectId) : 객체 ID, 주로 다른 객체를 참조할 때 넣음
ex: ObjectId() - Array : 배열 형태의 값
ex: ["a", "b", "c"]
mongoose의 모델(Model)
실제로 JS에서 mongoos를 통해 DB에 데이터를 저장하고 읽어올 때 사용되는 데이터의 구조이다. 즉, MongoDB를 실제 사용하기 위한 개념이다.
스키마를 바탕으로 만들어지고, JavaScript의 객체와 MongoDB 간의 상호작용을 하기 위해 사용하며, MongoDB의 실제 데이터를 다룰 수 있는 여러 메서드를 지원한다. 만약, 사용자의 데이터를 저장하려면, 사용자(Users) 모델을 사용하여 지정할 수 있고, 실제 MongoDB의 데이터를 삽입하거나 삭제하거나 조회하는 기능들을 구현할 수 있다.
웹 서버에 MongoDB 연결
이전 시간에 MongoDB Atlas에서 대여받은 주소를 활용하여 연결을 할 것이다.
해당 주소는 index.js 파일의 connect 내부에 들어간다.
- 파일 구조
spa-shop
├── app.js
├── routes
│ ├── carts.js
│ └── goods.js
└── schemas
├── index.js
├── cart.js
└── goods.js
- index.js
import mongoose from 'mongoose';
const connect = () => {
mongoose
.connect(
'이 안에 주소가 들어간다.',
{
dbName: 'spa_mall', // spa_mall 데이터베이스명을 사용합니다.
},
)
.catch((err) => console.log(err))
.then(() => console.log('몽고디비 연결 성공'));
};
mongoose.connection.on('error', (err) => {
console.error('몽고디비 연결 에러', err);
});
export default connect;
- app.js
import express from 'express';
import goodsRouter from './routes/goods.js';
import newsRouter from './routes/news.js';
import connect from './schemas/index.js';
const app = express();
const PORT = 3000;
connect(); // MongoDB를 연결하기 위한 커넥트 함수를 실행
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use('/api', [goodsRouter, newsRouter]);
app.listen(PORT, () => {
console.log(PORT, '포트로 서버가 열렸어요!');
});
이후 app.js를 동작 시키면 다음과 같은 결과가 나온다.
mongoose를 이용해 REST API 구현
- 스키마 정의
아래 코드는 mongoose를 사용해 스키마를 정의하고 모델을 생성하는 기본 템플릿이다. 해당 템플릿을 바탕으로 상품(goods) 모델을 구현한다.
// 스키마를 정의하는 기본 템플릿
// 1. mongoose 가져오기
import mongoose from 'mongoose'
// 2. 스키마 작성
// defaultSchema를 정의합니다.
const defaultSchema = new mongoose.Schema({ // 스키마 정의
defaultId: { // 이 필드는 실제 사용될 데이터의 이름입니다.
type: Number, // 이 필드의 데이터 타입이 숫자임을 나타냅니다.
required: true, // 이 필드가 >>반드시<< 있어야 함을 나타냅니다.
unique: true // 이 필드의 값이 >>유일<<해야 함을 나타냅니다.
}
}); // 3. 스키마를 통해 모델 구현
// 4. 모델 외부로 보내기
// defaultSchema를 사용하여 'Defaults'라는 이름의 mongoose 모델을 생성합니다.
export default mongoose.model("Defaults", defaultSchema);
상품(goods)을 관리하는 스키마는 다음과 같다.
- 상품(goods) 스키마의 요구사항
- /schema/goods.js 파일을 생성한 후 구현할 것
- 스키마의 이름은 goodsSchema
- 요구사항 구현이 완료되었으면, 해당하는 스키마를 ‘Goods’ 이름을 가진 mongoose 모델로 export.
- goodsId
- 숫자 타입
- 필수 항목.
- 중복된 값을 허용하지 않는다.
- name
- 문자열 타입.
- 필수 항목.
- 중복된 값을 허용하지 않는다.
- thumbnailUrl
- 문자열 타입.
- category
- 문자열 타입.
- price
- 숫자 타입.
import mongoose from 'mongoose';
const goodsSchema = new mongoose.Schema({
goodsId: {
type: Number,
required: true,
unique: true
},
name: {
type: String,
required: true,
unique: true
},
thumbnailUrl: {
type: String,
},
category: {
type: String,
},
price: {
type: Number,
}
});
export default mongoose.model("Goods", goodsSchema);
- 상품 생성 API 작성
이제는 데이터를 DB에 추가할 예정이다.
import express from 'express';
const router = express.Router();
// 1. Goods 모델 가져오기
import Goods from '../schemas/goods.js'
// 2. 가져온 것을 바탕으로 API 구현
router.post('/goods', async(req, res) => {
// 3. 클라이언트로부터 전달받은 데이터를 가져온다.
// goodsId, name, thumbnailUrl, category, price
const {goodsId, name, thumbnailUrl, category, price} = req.body;
// 4. goodsId가 중복되지 않았는지 확인
// => 실제로 MongoDB에 데이터를 조회해서, 해당하는 데이터가 MongoDB에 존재하는지 확인
// exec()가 없는 상태로 await를 사용한다면 해당하는 데이터가 Promis 형태로 동작하지 않는다.
// => 데이터가 조회되지 않을 수 있다.
// find()는 배열이 반환된다.
const goods = await Goods.find({goodsId: goodsId}).exec(); // 데이터를 생성할 때는 사용 안됨. 오직 조회할 때만!
// 4-1. 중복일 시, 에러 메시지 전달
if(goods.length) {
return res.status(400).json({errorMessage: '이미 존재하는 데이터이다.'});
}
// 5. 상품(goods)를 생성
const createdGoods = await Goods.create({
goodsId: goodsId,
name: name,
thumbnailUrl: thumbnailUrl,
category: category,
price: price
});
// 6. 생성된 상품 정보를 클라이언트에게 응답(Response) 반환한다.
return res.status(201).json({goods: createdGoods});
});
// 작성한 라우터를 내보내기 위해 사용
export default router;
Insomnia로 확인을 해보면 다음과 같다.
goodsId나 name이 같은 값일 때, 이를 Send하면 다음과 같다.
- 저장된 데이터 확인
Studio 3T로 저장된 데이터를 확인하면 다음과 같다.
위치와 파일 이름은 index.js 파일과 스키마를 생성했을 때 정해진 것이다.
'Node.js' 카테고리의 다른 글
24/09/08 - Node.js 입문(2 - 4): AWS EC2 Instance 생성 (0) | 2024.09.08 |
---|---|
24/09/07 - Node.js 입문(2 - 3): 미들웨어(Middleware)와 에러 핸들러 (0) | 2024.09.07 |
24/09/04 - Node.js 입문(2 - 1): 데이터베이스와 MongoDB, Studio 3T (0) | 2024.09.04 |
24/09/04 - Node.js 입문(1 - 9): Request와 Response, 경로 변수 (1) | 2024.09.04 |
24/09/04 - Node.js 입문(1 - 8): API Client (1) | 2024.09.04 |