[MOONDEUK 6일차 -3] 포스트 CRUD

💡 서론

일단 대략적인 포스트를 쓸 준비를 마쳤다!
참 신기한게, 맨처음에는 "기존 것과 다르게, 더 좋게" 만드려 노력했는데,
정말 돌고 돌다 공식문서들을 확인해보면, 모르는 것들을 알아가는 기쁨과,
동시에 기존 것과 겹치는 것들은 다르게 바꾸면서 만들어야 할지에 대한 고민도 든다.
아무래도 '포트폴리오'의 목적성이 있는 프로젝트다 보니, 그럴 수 있다.
(예를 들자면 mongoose의 findByIdAndUpdate라는 것을 기존에 배울 때 쓰다보니,
난 좀 다르게 find로 구현하고 싶어지는 반항심 등... 근데 코드가 길어지잖아)

여튼, 중요한 건 어차피 면접에서 증명하면 되고, 나는 내가 공부한 만큼 구현해왔으니. 당당하게 그냥 개발이나 하자 이거야~


💡 본론

일단 CRUD에 맞게 하나씩 접근한다.

route/post/write

const post = express.Router();

/* create - write */ 
post.post('/write', postValidationCheck, writeController);

이제는 이정도의 난이도는 괜찮다.
여기서 고민이었던 것은, [String]과 같은 배열의 형태에서 어떻게 유효성을 검사할 지였는데,
express-validator 공식 문서에서는 친절하게 custom이라는 사용자 지정 방식이 있었다.
map으로 먼저 다 판별후, 조건을 어긋나는 게 있는지 필터링하는 식으로 했다.

import { check, validationResult } from 'express-validator';
import Post from '../../models/post.js';
export const postValidationCheck = async (req, res, next) => {
    await check('title')
        .exists()
        .withMessage("제목을 입력해주세요.")
        .run(req);

    await check('body')
        .exists()
        .withMessage("내용을 입력해주세요.")
        .run(req);

    await check('tags')
        .custom(tag => {
            return (tag.map(t => typeof(t) !== 'string')).includes(true) ? false : true;
        })
        .withMessage("태그는 문자로 이루어져야 합니다.")
        .bail()
        .custom(tag => {
            if ((tag.map(t => t.trim().length === 0)).includes(true)) {
                return false
            }
            return true;
        })
        .withMessage("최소 1자 이상의 문자 태그를 입력해주세요.")
        .run(req)

    const result = validationResult(req);
    if (!result.isEmpty()) return res.status(400).json({ errors: result.array() });
    return next();
}
const writeController = async (req, res) => {
    const { title, body, tags } = req.body;
    // const { author } = req.user;
    const post = new Post({
        title,
        body,
        tags,
        // author,
    });
    try {
        await post.save();
        res.send(post);
    } catch(e) {
        res.status(500).send(e);
    }
}
export default writeController;

route/post/list

/* read (all) - list */ 
post.get('/list', listController);

전체를 조회하는 컨트롤러. 그냥 전체 조회하면 뚝딱이다.
주의는 exec. 쿼리를 조회할 때 쓰는데, 안 쓸시 오류가 발생하니 참고하자.

import Post from '../../models/post.js';

const listController = async (req, res) => {
    try {
        const posts = await Post.find().exec();
        res.send(posts);
    } catch(e) {
        res.status(500).send(e);
    }
};

export default listController;

routesMiddleware/checkValidId

여기서부터는 특정 아이디를 조회한다.
따라서 먼저 ObjectId가 유효한 것인지부터 검사해야 한다.
따라서, routes 하위 디렉토리 쪽에서 적용할 middleware를 하나 추가하자.

import mongoose from 'mongoose';

// check invalid ObjectId type.

const checkValidId = (req, res, next) => {
    const { id } = req.params;
    const { ObjectId } = mongoose.Types;
    if (!ObjectId.isValid(id)) {
        return res.status(400).send('NOT INVALID REQUEST (id)');
    } else {
        return next();
    }
}

export default checkValidId;

route/post/read

/* read (id) - read */
post.get('/:id',checkValidId, readController);

이것도 위의 것에서 id만 추가한 느낌.
다만, 여기서는 callback함수가 개인적으로 더 깔끔해보여서 썼다.
(여기서 이렇게 에러가 처리됐다!는 게 좀 더 직관적이라 판단)

import Post from '../../models/post.js';

const readController = async (req, res) => {
    const { id } = req.params;
    try {
        await Post.findById(id).exec((err, result) => {
            // not exists post.
            if (err) return res.status(404).send('NOT FOUND POST DATA');
            return res.send(result);
        });
    } catch(e) {
        res.status(500).send(e);
    }
}

export default readController;

route/post/update

/* update (PATCH /:id) - update */ 
post.patch('/update/:id', checkValidId, updateController);

findByIdAndUpdate를 통해 처리했다.
find로 하려 했는데 코드가 비교적 길어지는 느낌이 싫어서
역시 구관이 명관이다, 싶어서 사용했다.
그래도 꽤나 이득도 많았다. 더 나은 소스를 찾기 위해 mongoose 공식문서 역시 많이 볼 수 있었던 좋은 기회였다 :) 정말 유용한 메서드가 많다!

import Post from '../../models/post.js';

const updateController = async (req, res) => {
    const { id } = req.params;
    try {
        console.log(req.body);
        await Post.findByIdAndUpdate(id, req.body, { new: true }, (err, result) => {
            if (err) res.status(404).send('NOT FOUND');
            return res.send(result);
        }).exec();
    } catch(e) {
        res.status(500).send(e);
    }
}
export default updateController;

route/post/delete/:id

/* delete (DELETE /:id) - delete */
post.delete('/delete/:id', checkValidId, deleteController);

그냥 지워버린다. 여기서 어차피 보낼 건 없으니 204상태를 띄운다.

import Post from '../../models/post.js';

const deleteController = async (req, res) => {
    const { id } = req.params;
    await Post.findByIdAndDelete(id, (err, result) => {
        if (err) return res.status(404).send('NOT FOUND');
        return res.status(204).send();
    })
}

export default deleteController;

💡 후기

이제 초간단 CRUD는 끝냈다.
한 번 컴포넌트 디자인을 나중에 하고, 먼저 기능 구현에 초점을 맞춰보자!
내일 목표는 라우트 + 상태관리를 서버에 정상적으로 연동시키는 것.
처음 배우는 express에 쩔쩔매던 게 엊그제인데
일주일 만에 꽤나 많은 것을 해냈다. awesome⭐👏

좋은 웹페이지 즐겨찾기