일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- HTML
- 프론트엔드
- react
- 파이썬
- es6
- TypeScript
- 솔리디티
- 웹
- VUE
- 이슈
- CSS
- docker
- JavaScript
- 블록체인
- 쿠버네티스
- 자바스크립트
- 타입스크립트
- BFS
- 컴퓨터공학
- 백준
- k8s
- 백엔드
- 가상화
- 클라우드
- kubernetes
- next.js
- 이더리움
- AWS
- 알고리즘
- 리액트
- Today
- Total
즐겁게, 코드
Express.js & MongoDB 기반 REST api 구현하기 - 3편 본문
[지난 글 목록]
Express.js & MongoDB 기반 REST api 구현하기 - 1편 (서버 만들기)
Express.js & MongoDB 기반 REST api 구현하기 - 2편 (몽고디비 연결하기)
이전 단계에서 몽고디비(MongoDB)를 사용할 준비를 마쳤으니, 이제 몽고디비를 사용해 보자.
몽고디비는 행렬 테이블로 이루어진 기존의 관계형 데이터베이스와는 다르게 어떤 값이든 DB에 어떤 형태로든 추가될 수 있다.
이는 NoSQL 방식의 큰 장점이기도 하지만 자칫하면 무질서한 데이터들로 인해 DB의 무결성을 해칠 수도 있다.
그래서 몽구스에서 제공하는 것이 스키마(Schema)로, 스키마는 데이터 포맷을 일종의 템플릿으로 정해줌으로써 몽고디비에서도 MySQL처럼 구조화된 데이터를 관리할 수 있게 된다.
Ex. [MySQL 레코드 형식]
name | phone | email
찬민 7477 chanmin@gmail.com
수민 2544 soovely@gmail.com
Ex. [Mongoose 스키마 형식]
const mongoose = require("mongoose");
const personSchema = new mongoose.Schema({
name: {
type: String,
required: true,
},
phone: {
type: String,
required: true,
},
email: {
type: String,
required: true,
default: "",
},
});
// 몽고디비에 실제로 저장되는 개별 데이터는 '다큐먼트' 라고 부른다.
[
{
"_id": "5fef0e6819c5103420c38ef3",
"name": "찬민",
"phone": "7477",
"email": "chanmin@gmail.com"
},
{
"_id": "9371f8c91280c91ca38ef259",
"name": "수민",
"phone": "2544",
"email": "soovely@gmail.com"
}
]
이제 서비스의 계정 정보를 제어한다고 생각하고 스키마를 별도의 파일로 분리한다.
스키마는 자바스크립트 객체와 동일한 문법을 사용하므로 매우 친숙하게 느껴질 것이다.
[스키마 항목 속성]
- type: 데이터의 형식(타입)을 지정한다. Number, Boolean 등의 원시 타입은 자바스크립트의 원시형과 완전히 동일하다.
다만 몽구스에서만 지원하는 몇 가지 타입이 더 존재하는데 이는 Mongoose 공식 문서 - 스키마 타입 에서 찾아볼 수 있다.
- required: 해당 속성의 필수여부를 지정한다.
- default: 값이 주어지지 않을 때 사용할 초기값을 지정한다.
이를 토대로 스키마를 완성하면 외부 파일에서 스키마를 사용할 수 있도록 mongoose.model() 을 통해 내보낸다.
이 때 mongoose.model() 의 첫 번째 인자는 컬렉션 이름, 두 번째 인자는 내보낼 스키마의 이름이다.
// accountModel.js
const mongoose = require("mongoose");
const accountSchema = new mongoose.Schema({
nickname: {
type: String,
required: true,
},
major: {
type: String,
required: true,
},
created_at: {
type: Date,
required: true,
default: Date.now,
},
});
module.exports = mongoose.model("Account", accountSchema);
위처럼 닉네임과 전공으로 구성된 기본적인 스키마를 완성했다.
본격적인 코드를 설명하기 전에 사용할 용어를 통일하자면 다음과 같다.
- 스키마 : 데이터의 틀 (클래스)
- 모델 : 스키마를 통해 생성된 객체 (객체)
- 다큐먼트 : 몽고디비 상에서 존재하는 각 데이터
이제 REST api로 몽고디비를 조작하기 위해 라우터 파일에서 스키마를 불러온 후 api별로 수행할 동작을 채워보자.
// routes.js
const express = require("express");
const router = express.Router();
const Account = require("./accountModel");
router.get("/", (req, res) => {
res.send("GET 요청을 수신했습니다.");
});
router.post("/", (req, res) => {
res.send("POST 요청을 수신했습니다.");
})
router.patch("/:id", (req, res) => {
res.send("PATCH 요청을 수신했습니다.");
})
router.delete("/:id", (req, res) => {
res.send("DELETE 요청을 수신했습니다.");
})
module.exports = router;
[1. 모든 계정 데이터 불러오기 (GET 요청 시)]
const express = require("express");
const router = express.Router();
const Account = require("./accountModel");
router.get("/", async (req, res) => {
try {
const accounts = await Account.find();
res.json(accounts);
} catch (err) {
res.status(500).json({ message: err.message });
}
});
(스키마).find() 는 해당 스키마로 작성된 모든 다큐먼트의 배열을 리턴한다.
데이터를 불러오는데 성공하면 이를 클라이언트에 응답으로 전송하고, 오류가 발생하면 에러 메시지를 전송한다.
[2. 계정 데이터 추가하기 (POST 요청 시)]
const express = require("express");
const router = express.Router();
const Account = require("./accountModel");
router.post("/", async (req, res) => {
const account = new Account({
nickname: req.body.nickname,
major: req.body.major,
});
try {
const newAccount = await account.save();
res.status(201).json(newAccount);
} catch (err) {
res.status(400).json({ message: err.message });
}
});
요청 본문에 포함된 별명과 전공 정보를 기반으로 모델(account)을 생성한다.
이후 (모델).save() 를 통해 이를 저장하고 몽고디비의 다큐먼트로 추가한다.
[3. 특정 id를 갖는 다큐먼트 찾기]
갱신과 삭제는 주어진 id를 갖는 다큐먼트를 지목해야 하므로 이를 위한 미들웨어도 작성해준다.
async function getAccount(req, res, next) {
let account;
try {
account = await Account.findById(req.params.id);
if (account == null) {
return res.status(404).json({ message: "계정을 찾을 수 없습니다." });
}
} catch (err) {
return res.status(500).json({ message: err.message });
}
res.account = account;
next();
}
(스키마).findById()는 인자와 일치하는 id를 갖는 다큐먼트를 반환한다.
[4. 계정 데이터 갱신하기 (PATCH 요청 시)]
const express = require("express");
const router = express.Router();
const Account = require("./accountModel");
router.patch("/:id", getAccount, async (req, res) => {
if (req.body.nickname != null) {
res.account.nickname = req.body.nickname;
}
if (req.body.major != null) {
res.account.major = req.body.major;
}
try {
const updatedAccount = await res.account.save();
res.json(updatedAccount);
} catch (err) {
res.status(400).json({ message: err.message });
}
});
위 코드는 요청 본문으로 받은 닉네임과 전공을 특정 다큐먼트에 덮어쓴다.
[5. 계정 데이터 삭제하기 (DELETE 요청 시)]
const express = require("express");
const router = express.Router();
const Account = require("./accountModel");
router.delete("/:id", getAccount, async (req, res) => {
try {
await res.account.remove();
res.json({ message: "계정 정보를 삭제했습니다." });
} catch (err) {
res.status(500).json({ message: err.message });
}
});
마지막으로 (모델).remove() 를 통해 다큐먼트를 디비에서 삭제할 수도 있다.
나중을 위해 요약하자면 다음과 같다.
스키마.find() -> (읽기) 모든 다큐먼트 목록 리턴
모델.save() -> (쓰기) 디비에 다큐먼트 추가
모델.remove() -> (쓰기) 디비에서 다큐먼트 제거
하나 눈여겨봐야 할 점은 지금까지 모두 동일한 URI에 대해 요청을 수행했다는 것이다.
이제 개발자는 "삭제는 /deleteAccount, 추가는 /createAccount, 업데이트는 어디였지..?" 하고 수많은 URL로 둘러싸여 좌절할 필요 없이 HTTP 메서드만으로 요청에 따른 작업을 수행할 수 있게 되었다.
'💻 백엔드 > Node.js' 카테고리의 다른 글
리액트 & socket.io 기반 채팅 어플리케이션 만들기 - 클라이언트 편 (2) | 2021.02.03 |
---|---|
리액트 & socket.io 기반 채팅 어플리케이션 만들기 - 서버 편 (3) | 2021.02.03 |
Express.js & MongoDB 기반 REST api 구현하기 - 2편 (0) | 2020.12.30 |
Express.js & MongoDB 기반 REST api 구현하기 - 1편 (0) | 2020.12.30 |