REST API와 커스텀 포스트 연동 시 주의할 사항.

REST API v2를 사용하면 프론트 구성을 대폭 간결하게 만들 수 있다. 그리고 워드프레스 자체에서 backbone.js, underscore.js를 내장하고 있고, 이를 곳곳에 활용하고 있으며, 심지어 backbone.js의 model과 collection 개념을 사용하여 REST API를 구성하고 있다. 아직 REST API의 여러 부분을 깊숙하게 파악하지는 못했지만, 얼추 프로그램을 작성해 보면 한결 가볍게 페이지 구성이 가능해짐을 직접 느낄 수 있었다.

이 포스트는 커스텀 포스트와 REST API를 사용하면서 삽질한 결과를 간단히 남겨 놓기 위해 작성한다.

REST API와 커스텀 포스트

커스텀 포스트에서 REST API를 사용할 때 문제인데, 이전에는 이렇게 자바스크립트를 이용해 커스텀 포스트를 작성했었다.

var CptModel = wp.api.models.Post.extend({
    urlRoot: wpApiSettings.root + wpApiSettings.versionString + 'cpt'
);

var CptCollection = wp.api.collections.Posts.extend({
    url: wpApiSettings.root + wpApiSettings.versionString + 'cpt',
    model: CptModel
});

뭐 인터넷에 떠도는 이러저러한 글들을 참고하여 이렇게 썼지만, 이렇게 하면 콜렉션에 의해 fetch는 가능하지만 제대로 insert, update, delete가 불가능하다. 가령 CptModel에 대해 destory() 함수를 부르면 정상적으로 함수 호출은 되는데, URL이 엉뚱하게 /wp/posts/ 쪽으로 나가서 404 응답을 받게 된다. 이를 위해 엄청 찾아 봤지만, 제대로 된 글을 찾을 수가 없었다. … 시간을 많이 들여 직접 소스를 일이리 파면서 삽질에 삽질을 거듭하였고, 드디어 알아내었다!

WordPress Backbone.js Client 기본 동작

문제는 그 어디에서도 제대로 워드프레스의 백본 클라이언트인 wp-api.js 동작을 제대로 알려 주지 않았던 (혹은 내가 무시했던) 탓이었다. 보통 커스텀 포스트를 생성하고, 이 포스트를 REST API에서 쓸 수 있게 하려면 register_post_type() 함수의 두 번째 인자에 ‘rest_base’, ‘rest_controller_class’, ‘show_in_rest’ 이 세 키를 선언해 주면 된다. 이 때 처음 두 개는 기본값으로 써도 무방하고, show_in_rest만이 기본값이 false이어서 이를 true로 바꿔 주면 된다.

그리고 /wp-json/wp/v2 URL로 접속을 하면 워드프레스가 어떤 REST API 응답을 준다. 이 응답을 보면 어떤 REST API를 사용할 수 있는지를 공개해 주고 있다. 대략 아래 그림처럼 응답을 볼 수 있다.

여기까지 딱히 우리가 뭔가를 많이 해 줄 필요는 없다. 그냥 커스텀 포스트를 만들 때 show_in_rest 옵션만 잘 챙겨 주면 된다.

 

중요! URL 스키마는 캐싱된다.

wp-api에서는 이걸 ‘Schema’라는 모델에 저장해 두고 재활용한다. 그리고 여기서 정말정말 중요한 점! 매번 URL 호출하는 것이 부담되기 때문에 캐싱을 한다는 것이다. URL 호출이 한 번 일어나면 결과는 웹브라우저의 session storage에 저장된다. 그러므로 플러그인을 신나게 만들고 커스텀 포스트를 등록하고 난 다음, 내 커스텀 포스트의 URL 주소가 안 나온다고 당황하지 말자. 살포시 캐시를 지워 보자. 간단하게 브라우저를 끄고 다시 켜든지, 아니면 다음과 같은 자바스크립트로 캐시를 강제 삭제하면 된다. 커스텀 포스트를 생성한 후 rewrite 가 당장 안 적용되는 것과 동일한 원리.

sessionStorage.removeItem('wp-api-schema-model' + wpApiSettings.apiRoot + wpApiSettings.versionString);
브라우저가 처음 해당 워드프레스에 접속하면 이렇게 REST API 정보부터 탐색한다.

 

중요! 콜렉션과 모델은 스키마를 통해 자동 생성됨.

그리고 정말 중요한 정보를 하나 포스팅한다. 이렇게 커스텀 포스트를 만들게 되면 wp-api.js는 스키마를 바탕으로 우리의 커스텀 포스트도 적절히 알아서 백본의 콜렉션과 모델을 만들어 준다는 점. 아래는 워드프레스 4.8.1에 포함된 wp-api.js 파일의 일부이다. 대략 1229번째 줄의 내용이다.

// This is a model without a parent in its route
modelClassName = wp.api.utils.capitalizeAndCamelCaseDashes( routeName );
modelClassName = mapping.models[ modelClassName ] || modelClassName;
loadingObjects.models[ modelClassName ] = wp.api.WPApiBaseModel.extend( {

  // Function that returns a constructed url based on the id.
  url: function() {
    var url = routeModel.get( 'apiRoot' ) +
      routeModel.get( 'versionString' ) +
      ( ( 'me' === routeName ) ? 'users/me' : routeName );

    if ( ! _.isUndefined( this.get( 'id' ) ) ) {
      url +=  '/' + this.get( 'id' );
    }
    return url;
  },

  // Include a reference to the original route object.
  route: modelRoute,

  // Include a reference to the original class name.
  name: modelClassName,

  // Include the array of route methods for easy reference.
  methods: modelRoute.route.methods
} );

이 부분은 URL 주소가 ‘/wp/v2/bbcpt/’ 처럼 ‘/wp/v2’ 다음 rest_base가 바로 나오는 패턴에 대해 실행되는 코드이다. 세번째에 loadingObjects.models[ modelClassName ] = wp.api.WPApiBaseModel.extend() 를 보자. 객체 이름이 동적으로 결정된다. 객체 이름은 routeName에서 오고, 이것은 우리가 커스텀 포스트를 생성할 때 rest_base이다. 이것을 camelCase로 변환한 것이 모델의 이름이 된다. 콜렉션의 경우도 마찬가지다.

그러므로 내가 ‘bbcpt’라는 커스텀 포스트를 작성하였다면 이 커스텀 포트스를 위한 콜렉션은 wp.api.collections.Bbcpt, 모델은 wp.api.models.Bbcpt로 이미 만들어져 있다. 아까 위에서 적은 Post를 extend한 코드는 적절하지 않은 것이다. 커스텀 포스트의 콜렉션과 모델은 아래 코드처럼 쓰면 되는 것이다.

만약 내가 커스텀 포스트 이름을 ‘bbcpt’로 정했다면,

var cptCollection = new wp.api.collections.Bbcpt();

var cptModel = new wp.api.models.Bbcpt();

camelCase화된 이름이 웃기긴 하지만, 아무튼 저렇게 된다.

 

Promise다, Promise!

이건 REST API 도큐멘트에서도 언급된 내용인데, REST API 처음에 스키마를 읽어 온다고 했다. 그렇기 때문에 REST API를 처음 사용할 때

$(document).ready(function() {
 ...
});

이렇게만 사용하면 반드시 첫번 실행에는 REST API가 제대로 동작하지 않는 에러를 겪을 것이다. 그러므로 아래처럼 해야 깔끔하게 동작할 것이다.

wp.api.loadPromise.done(function() {
  .... 
});

wp.api.loadPromise.done() 을 $(document).ready() 에 넣어 주어도 무방하다.

 

wp_register_script 체크

wp_register_script() 함수 3번째 인자로 의존성을 적는 부분이 있다. 여기에 반드시 ‘wp-api’를 넣어 준다. wp.tempate()까지 활용하려면 ‘wp-util’ 도 같이 적어 준다.

wp_register_script(
  'bbcpt-admin-menu',
  plugin_dir_url( __FILE__ ) . 'assets/js/admin-menu.js',
  array(
    'wp-api',
    'wp-util',
  ),
  '1.0.0',
  TRUE
);

혹시 이러고도 문제가 생긴다면 wp_enqueue_script() 함수를 직접 호출하여 의존성을 해결하면 된다. 함수의 첫 인자로는 다음을 쓰면 된다.

  • backbone: backbone.js 로드
  • underscore: underscorejs 로드
  • wp-api: wp-api.js 로드
  • wp-util: wp-util.js 로드

 

3줄 정리

  1. REST API URL 스키마는 캐싱되니, 수정이 생기면 세션 캐시를 다시 생성할 것.
    The schema of your WP’s REST API shall be cached. If you touched it, then refresh your browser’s session cache.
  2. 커스텀 포스트의 backbone.js 콜렉션과 모델은 wp-api.js 에서 자동으로 생성된다. wp.api.{collections,models}.Posts.extend()를 사용하는 것이 아님.
    wp-api.js will create your custom post’s backbone.js collection and model for you. Do not use wp.api.{collections,models}.Posts.extend()
  3. wp-api.js 스크립트 의존성을 체크할 것, 그리고 promise 를 사용해야 최초 실행 시 스키마 로컬라이즈 단계에서 에러를 내지 않음
    Check wp-api.js dependancey, and use promise to avoid error when the schema is localized for the first time.

이 포스팅과 관련된 예제 플러그인을 github에 올려 두었다. 참고할 분들은 참고하시길.

댓글 남기기