WordPressのブロック開発メモ その11 REST API へのアクセス

前回はワードプレスのデータを扱うストアの紹介をしました。

WordPressのブロック開発メモ その10 Redux ストアの作成

今回、この投稿は若干難易度が高いので飛ばしてもらっても構いません。

ストア機能の一部(リゾルバ)は非同期でREST APIにアクセスします。
WordPressにはより簡単にREST APIにアクセスできるようにapiFetch()が用意されてます。

より内部的なことを詳しく知りたい人向けに、ここでは標準のfetch()を使う方法と@wordpress/api-fetchapiFetch()を使う方法を紹介します。

投稿などの情報を取得したければ普通にgetEntityRecords()など用意されたものを使った方がいいです。

REST API

その前にREST APIについてです。
REST APIを知っていることが前提です。
知らない人向けに軽く解説すると、サーバとHTMLではなくJSONをやり取りしてデータを取得したり操作したりしようって機能です。

WordPressはREST APIを公開してます。

詳しい仕様については以下を参照してください(私も全部読んでないです)。

https://developer.wordpress.org/rest-api/
https://ja.wp-api.org/reference/posts/

本来ブロック開発の外の話ですが、一応最低限の使い方だけ先に説明します。

始めに

もしWordPressを以下のURLで運用していたとします。

https://kurage-worker.com

そこからwp-json/wp/v2に移動します。

https://kurage-worker.com/wp-json/wp/v2

アクセスすると何やらブラウザ画面にJSONが素で表示されます。
ブラウザに直接表示すると見づらいので開発者ツールから以下のコードを打ってJSONを整形して表示させて見ます。

(async () => console.log(JSON.stringify(await (await fetch('https://kurage-worker.com/wp-json/wp/v2')).json(), null, "\t")))();

上記はhttps://kurage-worker.com/wp-json/wp/v2にアクセスしデータ取ってきてJSONを整形してコンソールに出力します。

上がそのまま表示されたJSON、下が整形して表示されたJSON。

投稿一覧

さらにpostsに移動します。

https://kurage-worker.com/wp-json/wp/v2/posts'
(async () => console.log(JSON.stringify(await (await fetch('https://kurage-worker.com/wp-json/wp/v2/posts')).json(), null, "\t")))();

これで投稿10件分のデータが取得出来ました。

ただちょっと分かりづらいので投稿の中からIDとタイトルを抜き出して表示させることにしましょう。

(async () => {
    const json = (await (await fetch('https://kurage-worker.com/wp-json/wp/v2/posts')).json());
    console.log( json.map(_ => `${_.id}: ${_.title.rendered}`).join("\n") );
})();
645: WordPressのブロック開発メモ その10 Redux ストアの作成
629: WordPressのブロック開発メモ その9 データの操作(セレクタとアクション&ディスパッチ)
626: WordPressのブロック開発メモ その10ー1 リゾルバの解読
615: WordPressのブロック開発メモ その4ー2 ダイナミックブロック
580: WordPressのブロック開発メモ その8 フック(アクションとフィルター)
573: WordPressのブロック開発メモ その7 HOC(高階コンポーネント)
528: WordPressのブロック開発メモ その6 スロット
484: WordPressのブロック開発メモ その3 編集
470: WordPressのブロック開発メモ その2 TypeScript編
460: WordPressのブロック開発メモ その1

カテゴリ一覧

次はカテゴリ一覧を取得してみましょう。

categoriesに移動します。

https://kurage-worker.com/wp-json/wp/v2/categories
(async () => {
    const json = (await (await fetch('https://kurage-worker.com/wp-json/wp/v2/categories')).json());
    console.log( json.map(_ => `${_.id}: ${_.name} (${_.count})`).join("\n") );
})();
13: Doctrine (10)
7: PHP (10)
5: React (8)
8: Slim (6)
4: TypeScript(JavaScript) (2)
11: Ubuntu (3)
2: WordPress (4)
24: WordPress-Block (15)
6: その他 (4)
18: アルゴリズム (3)

カテゴリのID、タイトル、アイテム数の列をが表示されました。

タグ一覧

タグ一覧を表示してみましょう。

tagsに移動します。

https://kurage-worker.com/wp-json/wp/v2/tags
(async () => {
    const json = (await (await fetch('https://kurage-worker.com/wp-json/wp/v2/tags')).json());
    console.log( json.map(_ => `${_.id}: ${_.name}`).join("\n") );
})();
14: certificate
20: Docker
25: Gutenberg
21: Hyper-V
15: openssl
9: PHP
23: React
22: ReduxToolkit
12: ubuntu
16: オレオレ認証局

他にも固定ページのpagesや、ユーザー情報のusersなどいろんな種類があります。

クエリ

結果を制御することが出来ます。

最大数

投稿一覧では最大10記事まで取得出来ました。
クエリにper_pageを追加することで最大数を変更出来ます。

https://kurage-worker.com/wp-json/wp/v2/posts?per_page=15
645: WordPressのブロック開発メモ その10 Redux ストアの作成
629: WordPressのブロック開発メモ その9 データの操作(セレクタとアクション&ディスパッチ)
626: WordPressのブロック開発メモ その10ー1 リゾルバの解読
615: WordPressのブロック開発メモ その4ー2 ダイナミックブロック
580: WordPressのブロック開発メモ その8 フック(アクションとフィルター)
573: WordPressのブロック開発メモ その7 HOC(高階コンポーネント)
528: WordPressのブロック開発メモ その6 スロット
484: WordPressのブロック開発メモ その3 編集
470: WordPressのブロック開発メモ その2 TypeScript編
460: WordPressのブロック開発メモ その1
592: WordPressのブロック開発メモ 目次!
544: WordPressのブロック開発メモ その6-1 スロットとプラグイン
519: WordPressのブロック開発メモ その5 ツールバーとサイドバー
515: WordPressのブロック開発メモ その4ー1 アイコン一覧
493: WordPressのブロック開発メモ その4 コンポーネント

今度は15件の投稿が表示されました。

カテゴリを選択

カテゴリを指定して投稿一覧を取得出来ます(11はUbuntuカテゴリ)。

https://kurage-worker.com/wp-json/wp/v2/posts?categories=11
203: プライベートなSSL、オレオレ認証局メモ
72: VirtualHost の DocumentRoot を HomeDirectory にしたい。
63: Ubuntuを使っていてネットワークの反応がとにかく遅い

ただしcategoriesにはカテゴリのIDを指定します。

以下のように複数指定することも出来ます(2はWordPressカテゴリ)。

https://kurage-worker.com/wp-json/wp/v2/posts?categories[]=11&categories[]=2
326: WordPressの記事を書く時のmoreが面倒臭い! カテゴリ一覧表示で「続きを読む」を自動的に。
298: WordPressのプラグイン「WP Githuber MD」のPrism.jsでハイライトされない、行番号表示されない問題
203: プライベートなSSL、オレオレ認証局メモ
72: VirtualHost の DocumentRoot を HomeDirectory にしたい。
63: Ubuntuを使っていてネットワークの反応がとにかく遅い
21: アイキャッチ一覧をカードデザインで表示する。
14: wp_nav_menu()の挙動がおかしい!?

タグを選択

タグを指定して投稿一覧を取得出来ます(25はGutenberg)

https://kurage-worker.com/wp-json/wp/v2/posts?tags=25
645: WordPressのブロック開発メモ その10 Redux ストアの作成
629: WordPressのブロック開発メモ その9 データの操作(セレクタとアクション&ディスパッチ)
626: WordPressのブロック開発メモ その10ー1 リゾルバの解読
  • 管理者のぐーたらな性格で投稿時タグを指定し忘れて3つしか取得出来てない・・・orz

ソート

カテゴリをソートします。
orderbyでソートする項目、orderで昇順・降順を指定します。
以下はカテゴリ名でソートした場合です。

https://kurage-worker.com/wp-json/wp/v2/categories?orderby=name&order=desc
1: 未分類 (0)
19: 技術的なメモ (3)
18: アルゴリズム (3)
6: その他 (4)
24: WordPress-Block (15)
2: WordPress (4)
11: Ubuntu (3)
4: TypeScript(JavaScript) (2)
8: Slim (6)
5: React (8)
https://kurage-worker.com/wp-json/wp/v2/categories?orderby=name&order=asc
13: Doctrine (10)
7: PHP (10)
5: React (8)
8: Slim (6)
4: TypeScript(JavaScript) (2)
11: Ubuntu (3)
2: WordPress (4)
24: WordPress-Block (15)
6: その他 (4)
18: アルゴリズム (3)

他にもいろいろありますが割愛します。

アクション

REST APIなのでアクションの種類でCRUD出来ます。
実運用で実験するわけにはいかないのでhttps://kurage-worker.comからローカル環境のhttps://kurageに移してます。
まずはテスト用の投稿をします。

特定のIDの投稿を取得する

アクション: GET

https://kurage/wp-json/wp/v2/posts/427

このようにpostsの後に投稿ID(今回は427)を指定することで特定の投稿を取得出来ます。

(async () => {
    const post = (await (await fetch('https://kurage/wp-json/wp/v2/posts/427')).json());
    console.log( `${post.id}: ${post.title.rendered}` );
})();

特定のIDの投稿を編集する

アクション: POST

https://kurage/wp-json/wp/v2/posts/427

今回はタイトルを変更するのでbodytitle: Update Post!!!を追加したリテラルオブジェクトを渡しました。

(async () => {
    const options = {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': '**********' },
        body: JSON.stringify( { title: 'Update Post!!!' } )
    }
    const post = (await (await fetch('https://kurage/wp-json/wp/v2/posts/427', options)).json());
    console.log( post );
})();

ヘッダーにJSONであること、
そしてX-WP-Nonceにナンスを渡すことをしないといけないのがちょっと面倒ですが変更出来ました。

'X-WP-Nonce': '**********'

ナンスは伏せてますが、自身のナンスの取得はwp_create_nonce('wp_rest')で出来ます。
ここで得られたナンスは一定時間有効ですが時間帯によっては直ぐ変更されてしまいます。

開発環境下であれば以下のように一時的にthe_contentフィルタで取得するのもありですね。
プレビューから記事下部にナンスが表示されます。

add_filter('the_content', function($content){
    $nonce = wp_create_nonce('wp_rest');
    return $content . "<div>$nonce</div>";
});

特定のIDの投稿を削除する

アクション: DELETE

https://kurage/wp-json/wp/v2/posts/427

アクションをDELETEにしてアクセスすると削除出来ます。
こちらでもナンスが必要なんす!

(async () => {
    const options = {
        method: 'DELETE',
        headers: { 'X-WP-Nonce': '**********' },
    }
    const post = (await (await fetch('https://kurage/wp-json/wp/v2/posts/427', options)).json());
    console.log( post );
})();

投稿を新規追加する

アクション: POST

https://kurage/wp-json/wp/v2/posts
(async () => {
    const options = {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': '**********' },
        body: JSON.stringify( { title: 'New post item!!', content: 'REST APIで投稿しちゃうぞ!', status: 'publish' } )
    }
    const post = (await (await fetch('https://kurage/wp-json/wp/v2/posts', options)).json());
    console.log( post );
})();

contentで本文を投稿出来ますが、ブロックエディタとは相性悪そうですね。
statuspublishにしないと公開にならず、初期値の下書きになってしまいます。

纏め

このようにアクションとURLの組合せでリスト取得、対象取得、対象変更、対象削除、新規作成が出来ます。

操作 アクション URL
一覧取得 GET https://kurage/wp-json/wp/v2/posts
対象取得 GET https://kurage/wp-json/wp/v2/posts/427
対象変更 POST https://kurage/wp-json/wp/v2/posts/427
対象削除 DELETE https://kurage/wp-json/wp/v2/posts/427
新規作成 POST https://kurage/wp-json/wp/v2/posts

対象変更に関してはPUTが一般的だと思うのですがドキュメントにはそう書いてあったのでPOSTにしました。
PUTでもいけるようですが。

他にもカテゴリやタグなどのエンドポイントもありますが割愛します。

@wordpress/api-fetch

先ほどはJavaScriptの標準関数fetch()を使ってきました。
今回はWordPressで用意されている@wordpress/api-fetchapiFetchを使って操作してみましょう。
apiFetchを使うとJSON.stringify()を使ったり、ナンスを設定したりContent-Typeを設定する手間が省けます。

まずはブラウザから軽く実行してみましょう。

const { apiFetch, url } = wp;
const { addQueryArgs } = url;
const test = async () =>
{
    const posts = await apiFetch({
        path: '/wp/v2/posts'
    });

    console.log(posts.map(_ => `${_.id}: ${_.title.rendered}`));

    const query = {
        per_page: 5,
        categories: [11, 2],
        orderby: 'title',
        order: 'desc'
    };

    const qposts = await apiFetch({
        path: addQueryArgs('/wp/v2/posts', query)
    });

    console.log(qposts.map(_ => `${_.id}: ${_.title.rendered}`));

}
test();

apiFetch()のオプションでpathを指定します。
投稿一覧の場合は/wp/v2/postsを指定します(カテゴリの場合は/wp/v2/categories)

const posts = await apiFetch({ path: '/wp/v2/posts' });

投稿が10件取得されます。

またaddQueryArgs()を使うことでクエリ付きのパスを作成出来ます。

const qposts = await apiFetch({ path: addQueryArgs('/wp/v2/posts', query) });

このクエリ部分を実際に表示したのが以下になります。

const query = {
    per_page: 5,
    categories: [11, 2],
    orderby: 'title',
    order: 'desc'
};

wp.url.addQueryArgs('/wp/v2/posts', query);
/wp/v2/posts?per_page=5&categories%5B0%5D=11&categories%5B1%5D=2&orderby=title&order=desc

edit.tsxでは以下のようにも書けます。

import React from 'react';
import { useState } from '@wordpress/element';
import { useBlockProps } from '@wordpress/block-editor';
import { BlockEditProps } from '@wordpress/blocks';

import KurageExampleBlockProps from './props';
import './editor.scss';

import apiFetch from '@wordpress/api-fetch';
import { addQueryArgs } from '@wordpress/url';

import { Button } from '@wordpress/components';

const test = async () =>
{
    const posts = await apiFetch({
        path: '/wp/v2/posts'
    });

    // @ts-ignore
    console.log(posts.map(_ => `${_.id}: ${_.title.rendered}`));

    const query = {
        per_page: 5,
        categories: [11, 2],
        orderby: 'title',
        order: 'desc'
    };

    const qposts = await apiFetch({
        path: addQueryArgs('/wp/v2/posts', query)
    });

    // @ts-ignore
    console.log(qposts.map(_ => `${_.id}: ${_.title.rendered}`));

}

export default (props: BlockEditProps<KurageExampleBlockProps>) =>
{

    return (
        <div {...useBlockProps()}>

            Hello World

            <Button variant="primary" onClick={e => test()}>実験</Button>

        </div>
    );
}

もしPOSTなどの独自のアクションを設定するには、オプションにmethoddataを追加します。
以下は新しく投稿する場合のコードです。

wp.apiFetch({
    path: '/wp/v2/posts',
    method: 'POST',
    data: {
        title: 'Add New Post!!!',
        content: 'Hello Kurage Network!',
        status: 'publish',
    }
})

以前のfetch()を使った時と比較してみましょう。

(async () => {
    const options = {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': '**********' },
        body: JSON.stringify( { title: 'New post item!!', content: 'REST APIで投稿しちゃうぞ!', status: 'publish' } )
    }
    const post = (await (await fetch('https://kurage/wp-json/wp/v2/posts', options)).json());
    console.log( post );
})();

apiFetch()を使うとfetch()に比べ次のようなメリットがあります。

  1. JSON.stringify()を使う必要がない
  2. Content-Typeでapplication/jsonを指定する必要がない
  3. X-WP-Nonceにナンスを指定する必要がない

追記

ただし、1及び2においてはデフォルトのハンドラを使用していること、POSTのリクエストでdataを設定していること。

  • 他にもあるかもしれませんがそこまで調べてません!

1と2はデフォルトハンドラで実装積み、3についてはミドルウェアで自動的に追加されます。
どういうことかを次で説明します。

apiFetch()の仕組み

apiFetch()は独自のミドルウェアの仕組みを実装してます。
デフォルトでいくつかのミドルウェアがあり、さらに独自に追加することも出来ます。
ミドルウェアは順に実行されていき、最後にハンドラを実行します。
このハンドラは内部でwindow.fetch()を実行します。

ミドルウェア

ミドルウェアを追加することでハンドラの実行前後を制御できるようになります。
ミドルウェアを追加するにはapiFetch.use()を使用します。

デフォルトハンドラ

apiFetch()はデフォルトのハンドラを実行します。
このハンドラではオプションのdataはJSON(String)化され、ヘッダーにはContent-Typeapplication/jsonが設定されます。
そしてそのオプションを元にwindow.fetch()を実行します。

ハンドラを上書したい場合はapiFetch.setFetchHandler()を使用します。

サンプル

これまでのことを踏まえ、ミドルウェアとハンドラを追って見ましょう

edit.tsx

import React from 'react';
import { useState } from '@wordpress/element';
import { useBlockProps } from '@wordpress/block-editor';
import { BlockEditProps } from '@wordpress/blocks';

import KurageExampleBlockProps from './props';
import './editor.scss';

import apiFetch, { } from '@wordpress/api-fetch';
import { Button } from '@wordpress/components';

const test = async () =>
{

    // A middleware
    apiFetch.use((options, next) => {

        console.log('A Middleware');

        options = {
            ...options,
            headers: {
                ...options.headers,
                'X-A': 'AAA'
            }
        };

        return next(options);
    });

    // B middleware
    apiFetch.use((options, next) => {

        console.log('B Middleware');

        options = {
            ...options,
            headers: {
                ...options.headers,
                'X-B': 'BBB'
            }
        };

        return next(options);
    });

    // C middleware
    apiFetch.use((options, next) => {

        console.log('C Middleware');

        options = {
            ...options,
            headers: {
                ...options.headers,
                'X-C': 'CCC'
            }
        };

        return next(options);
    });

    // Handler(ドキュメントを読んでください。面倒なので非同期関数を渡してます)
    apiFetch.setFetchHandler(async options => {

        console.log('Handler');
        console.log(options);

        // デフォルトのハンドラではoptionsを元にwindow.fetch()を実行する
        return 'DATAAAAAAAAAAAAAAA';
    });

    const value = await apiFetch({ path: 'myPath' });
    console.log(value);

};

export default (props: BlockEditProps<KurageExampleBlockProps>) =>
{

    return (
        <div {...useBlockProps()}>

            Hello World

            <Button variant="primary" onClick={e => test()}>実験</Button>

        </div>
    );
}

ミドルウェアにはA middleware, B Middleware, C Middleware を追加しました。
ミドルウェアはそれぞれヘッダーにX-A, X-B, X-Cを追加する単純な機能です。

ハンドラも上書しました。
デフォルトではwindow.fetch()を実行しますがaxiosなどに書き換えるような使い方をします。
今回は適当に文字列返しているだけの実装です。

コンソールの出力を見るとC B A Handlerの順で実行されているのが確認出来ます。
ミドルウェアは追加順とは逆の順で実行され(next()よりも上にあるコードの場合)、最後にハンドラが実行されているのが確認出来ます。

オプションのヘッダーにはX-A X-B X-Cが追加されていることが確認できます。

Nonce

【メモ】

ナンスはミドルウェアとして実装してあり、そのミドルウェアはapiFetch.createNonceMiddleware()関数にナンスを渡すことで作成出来る。
このミドルウェアが実行される時、ヘッダにX-WP-Nonceが設定される。

ブラウザ上で以下を実行すると

wp.apiFetch.nonceMiddleware

すでにNonceのミドルウェアが設定してあることに気づく。
ところがGutenbergのソースコード内ではその設定箇所が見当たらない。

ブラウザから検索するとpost.php内に記述してあった。

WordPress本体から検索するとscript-loader.phpにてインラインで書かれているみたいだ。

ナンスの取得

ところでナンスを取得するにはどうすればいいのでしょう!?
結構どうでもいい話なんですが、以前全く別の件でワードプレスのREST APIにアクセスする時にナンスの取得に難儀したことがありました。

調べていたら以下のようなコードがありました。

https://github.com/WordPress/gutenberg/blob/36e3b335bf08c8d321d32a444ae1e5e50eef56e3/packages/e2e-test-utils/src/rest-api.js

なるほど。
まずはナンスを取得するページを取得して、

後はこんな感じで取得すればいいのね。

fetch('https://kurage-worker.com/wp-admin/admin-ajax.php?action=rest-nonce')
    .then(_ => _.text())
    .then(_ => console.log(_));

これでノンスが簡単に取得出来ました。

次回はブロックの作成についてです。

WordPressのブロック開発メモ その12 ブロックの作成とシリアライズ/デシリアライズ

certificate Docker Gutenberg Hyper-V openssl PHP React ReduxToolkit REST ubuntu WordPress オレオレ認証局 フレームワーク