NodeJS REST API + MongoDB 사용

기운찬곰·2020년 9월 21일
3

MongoDB

목록 보기
4/4
post-thumbnail

시작하기에 앞서서...

이번시간에는 본격적으로 NodeJS로 REST API구현하고 MongoDB와 연동해서 동작하는 실습을 진행해볼 것입니다.


Resource Creation Endpoints

기존까지 만든 부분을 적절히 분리시켜볼 것입니다. 보통은 스키마와 모델 생성부분을 따로 구분해서 저장합니다.

user.js

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

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

Testing

postman을 통해 테스트 해볼 수 있습니다.

위에는 포스트맨이고 실행시켰더니 밑에 MongoDB에서 저장된것을 나타냅니다.

만약에 잘못된 형식에 값을 저장하려고 할때는 위와 같이 에러상태코드와 에러메시지를 반환하게 됩니다.


Resource Reading Endpoints

생성을 해봤으니까 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는 각자 맞는 거로 수정하기 바람.


Resource Updating Endpoints

이번에는 특정 유저를 찾아서 변경해주는 코드를 작성해봅니다.

/*
    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 변경 실패",
    });
  }
});

Resource Deleting Endpoints

이번에는 특정 유저를 찾아서 삭제해주는 코드를 작성해봅니다.

/*
    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 삭제 실패",
    });
  }
});

Separate Route files

마지막 보너스로 index.js 안에 있는 라우터부분을 도려내서 깔끔하게 정리해보도록 하겠습니다.

일단 폴더와 파일구조는 위와 같습니다. 하향식으로 한번 살펴보겠습니다.

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는 뭘까요?

/api/index.js

const express = require("express");
const router = express.Router();
const user = require("./user");

router.use("/users", user);

module.exports = router;

express.Router 클래스를 사용하면 모듈식 마운팅 가능한 핸들러를 작성할 수 있습니다. Router 인스턴스는 완전한 미들웨어이자 라우팅 시스템이며, 따라서 “미니 앱(mini-app)”이라고 불리는 경우가 많습니다.

/api/user/index.js

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 클래스를 사용해서 분리시켜줍니다. 이번에는 그 경로가 다양하게 나눠져 있습니다. 다양하게 나눠진 경로와 컨트롤러 함수를 매칭시키면 됩니다.

/api/user/user.ctrl.js

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처리를 수행하고 성공 혹은 실패메시지를 전달해줍니다.


마침

사실 이 부분이 핵심이고... 전부입니다. 개발하면서 이 틀을 벗어나는 일은 별로 없습니다.

Refercences

Hits

profile
배움을 좋아합니다. 새로운 것을 좋아합니다.

0개의 댓글