• 글 삭제/수정은 작성자만 할 수 있어야 한다.
  • 미들웨어를 수정
  • /src/lib/checkLoggedIn.js를 생성
  • /src/api/posts/posts.ctrl.jscheckOwnPost, getPostById를 추가
export const checkOwnPost = (ctx, next) => {
  const { user, post } = ctx.state;
  if (post.user._id.toString() !== user._id) {
    ctx.status = 403;
    return;
  }
  return next();
};

export const getPostById = async (ctx, next) => {
  const { id } = ctx.params;
  if (!ObjectId.isValid(id)) {
    ctx.status = 400; // Bad Request
    return;
  }
  try {
    const post = await Post.findById(id);
    if (!post) {
      ctx.status = 404; // Not found
      return;
    }
    ctx.state.post = post;
    return next();
  } catch (e) {
    ctx.throw(500, e);
  }
  return next();
};
  • /src/api/posts/index.js에 아래와 같이 미들웨어 추가
posts.get('/', postsCtrl.list);
posts.post('/', checkLoggedIn, postsCtrl.write);

const post = new Router(); // /api/posts/:id
post.get('/', postsCtrl.read);
post.delete('/', checkLoggedIn, postsCtrl.checkOwnPost, postsCtrl.remove);
post.patch('/', checkLoggedIn, postsCtrl.checkOwnPost, postsCtrl.update);

posts.use('/:id', postsCtrl.getPostById, post.routes());
export default posts;

username/tags로 포스트 필터

  • 포스트 조회, 태그 조회
/* 포스트 목록 조회
GET /api/posts
/api/posts?username=jhl
/api/posts?tag=다리디리
*/
export const list = async ctx => {
  const page = parseInt(ctx.query.page | '1', 10);

  if (page < 1) {
    ctx.status = 400;
    return;
  }

  const { tag, username } = ctx.query;
  const query = {
    // username, tag가 유효할때만 객체 안에 해당 값을 넣겠다는 의미
    ...(username ? { 'user.username': username } : {}),
    ...(tag ? { tags: tag } : {}),
  };

  try {
    const posts = await Post.find(query)
      .sort({ _id: -1 })
      .limit(10)
      .skip((page - 1) * 10)
      .exec();
    const postCount = await Post.countDocuments(query).exec();
    ctx.set('Last-Page', Math.ceil(postCount / 10));
    ctx.body = posts
      .map(post => post.toJSON())
      .map(post => ({
        ...post,
        body:
          post.body.length < 200 ? post.body : `${post.body.slice(0, 200)}...`,
      }));
  } catch (e) {
    ctx.throw(500, e);
  }
};
$ yarn add koa-router
const Koa = require('koa');
const Router = require('koa-router');

const app = new Koa();
const router = new Router();

// 라우터 설정
router.get('/', ctx => {
  ctx.body = '홈';
});
router.get('/about', ctx => {
  ctx.body = '소개';
});

// app instance에 라우터 적용
app.use(router.routes()).use(router.allowedMethods());

app.listen(4000, () => {
  console.log('Listening to port 4000');
});

Router Parameter, Query

  • /about/:name의 형식으로 :을 사용하여 라우트 경로 설정
  • ctx.params의 객체에서 조회
  • /posts/?id=10 처럼 조회하면 ctx.query에서 조회
    • 문자열 형태는 ctx.querystring
router.get('/about/:name?', ctx => {
  const { name } = ctx.params;
  ctx.body = name ? `${name}의 소개` : '소개';
});

router.get('/posts', ctx => {
  const { id } = ctx.query;
  ctx.body = id ? `포스트 #${id}` : `포스트 없어`;
});
  • http://localhost:4000/about/icecream
  • http://localhost:4000/posts?id=10

Parameter, Query Example

  • /user/lee
  • /search?keyword="hi

위에서 user에 lee를 넘기는 방식이 parameter로 특정 사용자를 조회할때 사용하고, query는 키워드를 검색할때 페이지에 필요한 옵션을 전달할 때 사용을 하게 된다.

URL Parameter

  • match라는 객체안의 params 값을 참조
import React from 'react';

const data = {
  seoul: {
    name: '서울',
    location: 'seoul'
  },
  gyonggi: {
    name: '경기도',
    location: 'gyonngi-do'
  }
};

const Location = ({ match }) => {
  const { cityname } = match.params;
  const city = data[cityname];
  if (!city) {
    return (
      <div>
        <h1> {cityname} 은 존재하지 않는다.</h1>
      </div>
    );
  }

  return (
    <div>
      <h1>
        {' '}
        {cityname} ({city.name})
      </h1>
      <p>{city.location}</p>
    </div>
  );
};

export default Location;
  • App.js에서 routing을 정의
import React from 'react';
import { Route, Link } from 'react-router-dom';
import './App.css';

import Home from './components/Home';
import Search from './components/Search';
import Location from './components/Location';

const App = () => {
  return (
    <div>
      <Link to="/">홈</Link>
      <Link to="/search">검색</Link>
      <Link to="/location/seoul">서울</Link>
      <Link to="/location/gyonngi">경기</Link>
      <Link to="/location/star">별나라</Link>

      <Route path="/" component={Home} exact={true}></Route>
      <Route path="/search" component={Search}></Route>
      <Route path="/location/:cityname" component={Location}></Route>
    </div>
  );
};

export default App;

URL 쿼리

  • url 쿼리의 경우에는 match와 다르게 location의 객체에 있는 값을 조회가 가능하다.
  • /search?keyword=hi의 url을 호출했을때 location의 객체의 형태는 아래와 같다.
{
  "pathname": "/search",
  "search": "?keyword=hi"
}
  • query 문자열을 객체로 변환할때는 qs의 라이브러리를 사용한다
yarn add qs
import React from 'react';
import qs from 'qs';

const Search = ({ location }) => {
  // ?keyword=
  const query = qs.parse(location.search, {
    ignoreQueryPrefix: true // prefix: ?
  });

  const keyword = query.keyword;

  return (
    <div>
      <h1>Search</h1>
      <p> 검색 쿼리는 {keyword}</p>
    </div>
  );
};

export default Search;
  • 다음과 같이 추가한 이후에 url을 http://localhost:3000/search?keyword=hi을 통해 접속하면 원하는 결과를 얻을 수 있다.

+ Recent posts