12-2. 좋아요 API 만들기

이번엔 좋아요 API 를 만들어보겠습니다. 좋아요 API 관련 코드는 posts 디렉토리에 likes.controller.js 파일을 만들어서 관리를 하도록 하겠습니다.

좋아요와 좋아요 취소의 코드는 다음과 같은 방식으로 작동합니다:

  1. 로그인 확인
  2. 포스트 찾기
    • 포스트를 불러올 때, likesCount 값과 likes 배열에 API 를 요청한 유저의 아이디가 있는지 없는지만 체크합니다.
    • 포스트가 존재하지 않으면 에러를 띄웁니다
    • 해당 유저가 이미 포스트를 좋아요 한 상태라면, 현재 포스트의 좋아요 관련 정보를 반환하고 작업을 중지합니다.
  3. like 혹은 unlike 메소드를 호출하여 해당 포스트의 좋아요 정보를 업데이트 합니다.
  4. 좋아요 관련 정보를 반환합니다.

이제 코드를 한번 살펴볼까요?

src/posts/likes.controller.js

const Post = require('models/post');

exports.like = async (ctx) => {
    /* 로그인 확인 */
    const { user } = ctx.request;
    if(!user) {
        ctx.status = 403; // Forbidden
        return;
    }

    /* 포스트 찾기 */
    const { postId } = ctx.params;
    const { username } = user.profile;

    let post = null;
    try {
        post = await Post.findById(postId, { // 두번째 파라미터에서는 포스트를 찾을 때 불러올 값을 설정
            likesCount: 1,
            likes: {
                '$elemMatch': { '$eq': username }
            }
        });
    } catch (e) {
        ctx.throw(500, e);
    }

    if(!post) {
        ctx.status = 404; // not found
        return;
    }

    // 이미 좋아한 경우엔 기존 값 반환
    if(post.likes[0] === username) {
        ctx.body = {
            liked: true,
            likesCount: post.likesCount
        };
        return;
    }

    /* 업데이트 */
    try {
        post = await Post.like({
            _id: postId,
            username: username
        });
    } catch (e) {
        ctx.throw(500, e);
    }

    /* 좋아요 관련정보 반환 */
    ctx.body = {
        liked: true,
        likesCount: post.likesCount
    };
};

exports.unlike = async (ctx) => {
    /* 로그인 확인 */
    const { user } = ctx.request;
    if(!user) {
        ctx.status = 403; // Forbidden
        return;
    }

    /* 포스트 찾기 */
    const { postId } = ctx.params;
    const { username } = user.profile;

    let post = null;
    try {
        post = await Post.findById(postId, {
            likesCount: 1,
            likes: {
                '$elemMatch': { '$eq': username }
            }
        });
    } catch (e) {
        ctx.throw(500, e);
    }

    if(!post) {
        ctx.status = 404; // not found
        return;
    }

    // 이미 좋아요 하지 않은 상태면 기본값 반환
    if(post.likes.length === 0) {
        ctx.body = {
            liked: false,
            likesCount: post.likesCount
        };
        return;
    }

    /* 업데이트 */
    try {
        post = await Post.unlike({
            _id: postId,
            username: username
        });
    } catch (e) {
        ctx.throw(500, e);
    }

    /* 좋아요 관련정보 반환 */
    ctx.body = {
        liked: false,
        likesCount: post.likesCount
    };
};

포스트를 찾을 때,

{ 
    likesCount: 1,
    likes: {
        '$elemMatch': { '$eq': username }
    }
}

두번째 파라미터로 위와 같은 값을 주었는데요, 위 값은 projection 설정이라고 부르는데요, 쿼리를 할 때 어떤 값들을 불러올 지 설정을 해줍니다.

만약에, 이 값을 다음과 같이 설정 했다면:

{
    likesCount: 1,
    likes: 1
}

데이터를 받을 때 다음과 같은 형식으로 불러오게 됩니다:

{ _id: 595a7318fdb2a4410b7cc030,
  likes: [ 'foo', 'bar', 'foobar' ],
  likesCount: 3 }

여기서 likes 쪽에 {'$elemMatch': { '$eq': username }} 를 설정해주면, 배열안에 주어진 username 값과 동일한 값이 있으면 다음과 같이 반환합니다.

// 예: bar 라는 유저가 요청 했을 때
{ _id: 595a7318fdb2a4410b7cc030,
  likes: [ 'bar' ],
  likesCount: 3 }

만약에 좋아요하지 않은 포스트를 쿼리하게 될 때에는 다음과 같은 형식으로 보여지게 됩니다:

// 예: bar 라는 유저가 요청 했을 때
{ _id: 595a7318fdb2a4410b7cc030,
  likes: [ ],
  likesCount: 3 }

posts API 라우트 설정

컨트롤러 파일을 다 작성하였으니 posts API 라우터에서 불러와서 설정을 해보도록 하겠습니다.

const Router = require('koa-router');
const posts = new Router();

const postsCtrl = require('./posts.controller');
const likesCtrl = require('./likes.controller');

posts.post('/', postsCtrl.write);
posts.get('/', postsCtrl.list);
posts.post('/:postId/likes', likesCtrl.like);
posts.delete('/:postId/likes', likesCtrl.unlike);

module.exports = posts;

자, 이제 좋아요 API 가 개발되었습니다. 한번 Postman 을 통하여 테스팅을 해보세요. 여기서 postId 는 리스팅 API 를 호출하여 여러분의 데이터베이스에 있는 포스트의 id 를 사용하세요.

POST http://localhost:4000/api/posts/:postId/likes
DELETE http://localhost:4000/api/posts/:postId/likes

결과:

{
    "liked": true,
    "likesCount": 1
}

{
    "liked": false,
    "likesCount": 0
}

results matching ""

    No results matching ""