이번시간에는 본격적으로 NodeJS로 REST API구현하고 MongoDB와 연동해서 동작하는 실습을 진행해볼 것입니다.
기존까지 만든 부분을 적절히 분리시켜볼 것입니다. 보통은 스키마와 모델 생성부분을 따로 구분해서 저장합니다.
src폴더에 models라는 폴더를 만든 후 user.js라는 파일을 만들고 아래와 같이 코드를 작성합니다.
// models/user.js
const mongoose = require("mongoose");
const validator = require("validator");
// 스키마 생성
const UserSchema = new mongoose.Schema({
name: {
type: String,
trim: true,
required: true,
},
age: {
type: Number,
validate(value) {
if (value < 0) {
throw new Error("Age must be a postive number");
}
},
},
email: {
type: String,
required: true,
validate(value) {
if (!validator.isEmail(value)) {
throw new Error("Email is invalid");
}
},
},
saveDate: {
type: Date,
default: Date.now,
},
});
// 모델 생성
const User = mongoose.model("User", UserSchema);
module.exports = User;
만약 task라는 스키마와 모델이 있으면 task.js를 만들어서 입력해주면 되겠죠?
index.js는 최상위 문서를 의미합니다. 여기에서는 mogoose를 통해 MongoDB와 연동을 한 후에, POST /users API를 만들어서 위에서 만든 User 모델을 이용해 새로운 객체를 생성한후 저장하고 있습니다.
// index.js
const express = require("express");
const mongoose = require("mongoose");
const User = require("./models/user");
const app = express();
const port = process.env.PORT || 3000;
mongoose
.connect("mongodb://127.0.0.1:27017/task-manager", {
useNewUrlParser: true,
useCreateIndex: true,
})
.then(() => {
console.log("Connected to MongoDB");
})
.catch((err) => {
console.log(err);
});
app.use(express.json());
/*
POST /users 유저 추가
{
name: string,
email: string,
age: int
}
*/
app.post("/users", async (req, res) => {
const user = new User(req.body);
try {
await user.save();
res.status(204).send();
} catch (e) {
res.status(500).json({
message: "User 저장 실패",
});
}
});
app.listen(port, () => {
console.log("Server is up on port " + port);
});
postman을 통해 테스트 해볼 수 있습니다.
위에는 포스트맨이고 실행시켰더니 밑에 MongoDB에서 저장된것을 나타냅니다.
만약에 잘못된 형식에 값을 저장하려고 할때는 위와 같이 에러상태코드와 에러메시지를 반환하게 됩니다.
생성을 해봤으니까 Reading도 해봐야겠죠?
/*
GET /users 유저 조회
*/
app.get("/users", async (req, res) => {
try {
const users = await User.find({});
res.status(200).send(users);
} catch (e) {
res.status(500).json({
message: "User 조회 실패",
});
}
});
간단합니다. await User.find({}) 이렇게 작성하면 모든 User안에 다큐먼트들을 가져올 수 있습니다.
이번에는 특정 아이디를 가진 user를 찾아서 반환해보도록 하겠습니다. 이때는 findById
를 사용하면 됩니다.
/*
GET /users/:id 특정 유저 조회
*/
app.get("/users/:id", async (req, res) => {
const id = req.params.id;
try {
const user = await User.findById(id);
if (!user) {
return res.status(404).send();
}
res.status(200).send(user);
} catch (e) {
res.status(500).json({
message: "User 조회 실패",
});
}
});
이렇게 해준다음에 localhost:3000/users/5f68dbe10d82070328bae785 로 요청해봅니다. 뒤에 id는 각자 맞는 거로 수정하기 바람.
이번에는 특정 유저를 찾아서 변경해주는 코드를 작성해봅니다.
/*
PATCH /users/:id 특정 유저 특정 필드 변경
*/
app.patch("/users/:id", async (req, res) => {
const id = req.params.id;
try {
// new가 true이면 수정된 문서를 반환
// runValidators가 true인 경우 업데이트 유효성 검사기를 실행
const user = await User.findByIdAndUpdate(id, req.body, {
new: true,
runValidators: true,
});
if (!user) {
return res.status(404).send();
}
res.status(200).send(user);
} catch (e) {
res.status(500).json({
message: "User 변경 실패",
});
}
});
이번에는 특정 유저를 찾아서 삭제해주는 코드를 작성해봅니다.
/*
DELETE /users/:id 특정 유저 제거
*/
app.delete("/users/:id", async (req, res) => {
const id = req.params.id;
try {
const user = await User.findByIdAndDelete(id);
if (!user) {
return res.status(404).send();
}
res.status(200).send(user);
} catch (e) {
res.status(500).json({
message: "User 삭제 실패",
});
}
});
마지막 보너스로 index.js 안에 있는 라우터부분을 도려내서 깔끔하게 정리해보도록 하겠습니다.
일단 폴더와 파일구조는 위와 같습니다. 하향식으로 한번 살펴보겠습니다.
const express = require("express");
const mongoose = require("mongoose");
const api = require("./api");
const app = express();
const port = process.env.PORT || 3000;
mongoose
.connect("mongodb://127.0.0.1:27017/task-manager", {
useNewUrlParser: true,
useCreateIndex: true,
})
.then(() => {
console.log("Connected to MongoDB");
})
.catch((err) => {
console.log(err);
});
app.use(express.json());
app.use("/api", api);
app.listen(port, () => {
console.log("Server is up on port " + port);
});
app.use("/api", api);
로 말끔하게 대체된 것을 볼 수 있습니다. 그렇다면 이 api는 뭘까요?
const express = require("express");
const router = express.Router();
const user = require("./user");
router.use("/users", user);
module.exports = router;
express.Router 클래스를 사용하면 모듈식 마운팅 가능한 핸들러를 작성할 수 있습니다. Router 인스턴스는 완전한 미들웨어이자 라우팅 시스템이며, 따라서 “미니 앱(mini-app)”이라고 불리는 경우가 많습니다.
const express = require("express");
const router = express.Router();
const userController = require("./user.ctrl");
router.post("", userController.userCreate);
router.get("", userController.userList);
router.get("/:id", userController.userRead);
router.patch("/:id", userController.userUpdate);
router.delete("/:id", userController.userDelete);
module.exports = router;
마찬가지로 express.Router 클래스를 사용해서 분리시켜줍니다. 이번에는 그 경로가 다양하게 나눠져 있습니다. 다양하게 나눠진 경로와 컨트롤러 함수를 매칭시키면 됩니다.
const User = require("../../models/user");
/*
POST /users 유저 추가
{
name: string,
email: string,
age: int
}
*/
exports.userCreate = async (req, res) => {
const user = new User(req.body);
try {
await user.save();
res.status(204).send();
} catch (e) {
res.status(500).json({
message: "User 저장 실패",
});
}
};
/*
GET /users 유저 조회
*/
exports.userList = async (req, res) => {
try {
const users = await User.find({});
res.status(200).send(users);
} catch (e) {
res.status(500).json({
message: "User 조회 실패",
});
}
};
/*
GET /users/:id 특정 유저 조회
*/
exports.userRead = async (req, res) => {
const id = req.params.id;
try {
const user = await User.findById(id);
if (!user) {
return res.status(404).send();
}
res.status(200).send(user);
} catch (e) {
res.status(500).json({
message: "User 조회 실패",
});
}
};
/*
PATCH /users/:id 특정 유저 특정 필드 변경
*/
exports.userUpdate = async (req, res) => {
const id = req.params.id;
try {
// new가 true이면 수정된 문서를 반환
// runValidators가 true인 경우 업데이트 유효성 검사기를 실행
const user = await User.findByIdAndUpdate(id, req.body, {
new: true,
runValidators: true,
});
if (!user) {
return res.status(404).send();
}
res.status(200).send(user);
} catch (e) {
res.status(500).json({
message: "User 변경 실패",
});
}
};
/*
DELETE /users/:id 특정 유저 제거
*/
exports.userDelete = async (req, res) => {
const id = req.params.id;
try {
const user = await User.findByIdAndDelete(id);
if (!user) {
return res.status(404).send();
}
res.status(200).send(user);
} catch (e) {
res.status(500).json({
message: "User 삭제 실패",
});
}
};
컨트롤러에 각 함수는 동작을 나타냅니다. 요청을 받고 DB처리를 수행하고 성공 혹은 실패메시지를 전달해줍니다.
사실 이 부분이 핵심이고... 전부입니다. 개발하면서 이 틀을 벗어나는 일은 별로 없습니다.