Node.js - Express.js와 MongoDB로 웹서비스 만들기
Mongoose ODM 기초와 간단한 CRUD 구현 코드(Node.js)
1) Mongoose ODM이란? Object Data Modeling MongoDB의 Collection에 집중하여 관리하도록 도와주는 패키지다. Collection을 모델화하여, 관련 기능들을 쉽게 사용할 수 있게 도와준다. Mongoose ODM을 사용하는..
disdlzheld.tistory.com
1) Express.js + Mongoose로 CRUD 구현하기
(1) 모델 선언하기
MongoDB의 ObjectID는 URL에 사용하기 좋은 값이 아니기에 대체할 수 있는 shortId를 생성할 것이다.
---./models/schemas/post.js
const mongoose, { Schema } = require('mongoose')
const shortId = require('./types/short-id')
module.exports = new Schema({
shortId,
title: String,
content: String,
author: String,
}, {
timestamps: true,
})
---./models/index.js ---
exports.Post = mongoose.model('Post', PostSchema)
제목, 내용 , 작성자를 String 타입으로 스키마에 선언한 것이다.
timestamps 옵션으로 작성 시간과 수정 시간을 자동으로 기록해줄 것이다.
다음은 shortId를 만들 것이다.
const { nanoid } = require('nanoid')
const shortId = {
type: String,
default: () => {
return nanoid()
},
require: true,
index: true,
}
module.exports = shortId
중복 없는 문자열을 생성해주는 nanoid 패키지를 활용했다.
default를 이용해 모델 생성 시 자동으로 ObjectId를 대체할 아이디를 생성하는 코드이다.
(2) 게시글 작성
게시글 작성 시 흐름은 다음과 같다.
1. /posts?write=true 로 작성 페이지 접근
2. <form action="/posts" method="post"> 이용해 post요청 전송
3. router.post 이용해 post 요청 처리
4. res.redirect 이용해 post 완료 처리
./routes/posts.js
===== 작성 페이지 =============
const { Router } = require('express')
const router = Router()
router.get('/', (req, res, next) => {
if (req.query.write) {
res.render('posts/edit')
return
}
})
module.exports = router
===== POST 요청 처리 ==============
const { Post } = require('./models')
router.post('/', async (req, res, next) => {
const { title, content } = req.body
try {
await Post.create({
title,
content,
})
res.redirect('/') // Post 완료 처리
} catch(err) {
next(err)
}
})
POST 요청 시에 try, catch문을 사용하여 Post에 게시글을 create 해주고 '/' url로 돌아가도록 하였다.
에러가 발생 시에는 catch로 넘겨준다.
(3) 게시글 목록 및 상세
1. /posts로 목록 페이지 접근
2. <a href="/posts/:shortId"> 이용해 상세 url 링크 접근
3. router.get('/:shortId') path parameter 이용해 요청처리
- 게시글 목록
--- ./routes/posts.js ---
router.get('/', async (req, res, next) => {
const posts = await Post.find({})
res.render('posts/list', { posts })
})
find 함수를 통해 모든 글을 불러와 posts 변수에 담는다.
그리고 render를 통해 'posts/list' url에 posts를 뿌려준다.
* { } 안에 변수명을 입력하는 것을 디스트럭처링이라고 하여 객체의 값을 변수로 바로 받아올 수 있다.
- 게시글 상세
--- ./routes/posts.js ---
router.get('/:shortId', async (req, res, next) => {
const { shortId } = req.params
const post = await Post.findOne({ shortId })
if (!post) {
next(new Error('Post NotFound')
return
}
res.render('posts/view', { post })
})
path parameter인 shortId를 통해 그 아이디를 가진 게시글을 findOne 한다.
만약 !post (글이 존재하지 않으면) next로 error를 넘겨준다.
글이 존재한다면 'posts/view' url로 디스트럭처링 된 post 객체를 렌더링해 화면에 뿌려준다.
(4) 게시글 수정
1. /posts/{shortId}?edit=true로 수정페이지 접근
2. 작성페이지를 수정페이지로도 동작하도록 작성
3. <form action="/posts/:shortId" method="post">로 post 요청 전송
* html form은 put method 지원하지 않아서 post 사용
-- ./routes/posts/js ---
router.get('/:shortId', async (req, res, next) => {
if (req.query.edit) {
res.render('posts/edit', { post })
}
})
router.post('/:shortId', async (req, res, next) => {
const { shortId } = req.params
const { title, content } = req.body
const post = await Post.findOneAndUpdate({ shortId }), {
title, content,
})
if (!post) {
next(new Error('Post NotFound')
return
}
res.redirect(`/posts/${shortId}`)
shortId를 path parameter로 받아오고, 수정하려고 입력받은 title과 content를 req.body로 받아온다.
findOneAndUpdate를 사용해서 shortId인 게시글 하나의 title과 content를 수정한다.
만약 !post (shortId로 글을 찾지 못하였을 때) next로 error를 넘긴다.
정상 작동되면 redirect를 통해 수정된 게시글의 상세페이지로 넘어간다.
* 이때 주의할 점은, 작은 따옴표가 아닌 ` ` (백틱)을 사용해 ${ } 표현법으로 변수를 받아와 바로 뿌려준다.
(5) 게시글 삭제
1. 게시글 상세 페이지에 삭제 버튼 추가
2. html form은 delete method를 제공하지 않음
3. 자바스크립트를 이용해 fetch 함수로 HTTP Delete 요청 전송
4. router.delete의 응답을 fetch에서 처리
--- ./routes/posts.js ---
const { Post } = require('./models')
router.delete('/:shortId', async (req, res, next) => {
const { shortId } = req.params
try {
await Post.delete({ shortId })
res.send('OK')
} catch (err) {
next(err)
}
})
delete를 사용해 path parameter의 shortId를 가진 글을 지우고
try, catch를 사용해 성공하면 OK를 화면에 출력하고, error 발생 시 next로 err를 반환한다.
이 때 pug를 사용한 html 템플릿을 간략하게 살펴보자면
button.delete(
onclick = 'deletePost("${post.shortId}")'
) 삭제
...
script(type='text/javascript')
function deletePost(shortId) {
fetch('/posts/' + shortId, { method: 'delete' })
.then((res) => {
if (res.ok) {
alert('삭제되었습니다.')
window.location.href = '/posts'
} else {
alert('오류가 발생했습니다.')
console.log(res.statusText)
}
})
.catch((err) => {
console.log(err)
alert('오류가 발생했습니다.')
})
}
then, catch문을 사용해서 성공과 실패의 분기를 나누었다.
코드 및 내용 참고: 엘리스 AI 트랙