Ext JS の SimManager を利用してモック REST API サーバーを実装する

Ext JS の SimManager を利用してモック REST API サーバーを実装する方法をメモっとく。Sencha Fiddle でモック API サーバーを実装するときに便利なのだ。とくに今回はシンプルな API サーバーではなく複雑な REST API サーバーを実装してみる。

# SimManager

[Ext.ux.ajax.SimManager](https://docs.sencha.com/extjs/6.5.2/classic/Ext.ux.ajax.SimManager.html) はシミュレートされた Ajax レスポンスを管理するシングルトンクラスだ。[Ext.data.Connection](https://docs.sencha.com/extjs/6.5.2/classic/Ext.data.Connection.html) のメソッドをフックして実現しているので、[Ext.Ajax](https://docs.sencha.com/extjs/6.5.2/classic/Ext.Ajax.html) のような派生クラスの Ajax レスポンスもシミュレートされたものになる。

SimManager の `init` メソッドが呼び出されるか、`register` メソッドで最初の [Ext.ux.ajax.Simlet](https://docs.sencha.com/extjs/6.5.2/classic/Ext.ux.ajax.Simlet.html) が登録されたときに前述のフックが挿入される。

Simlet は Ajax リクエストに応答してレスポンスを生成するシミューレートされたサーバー (Simulated Server) の基本クラスになる。派生クラスとして [Ext.ux.ajax.DataSimlet](https://docs.sencha.com/extjs/6.5.2/classic/Ext.ux.ajax.DataSimlet.html)、[Ext.ux.ajax.XmlSimlet](https://docs.sencha.com/extjs/6.5.2/classic/Ext.ux.ajax.XmlSimlet.html)、[Ext.ux.ajax.JsonSimlet](https://docs.sencha.com/extjs/6.5.2/classic/Ext.ux.ajax.JsonSimlet.html)、[Ext.ux.ajax.PivotSimlet](https://docs.sencha.com/extjs/6.5.2/classic/Ext.ux.ajax.PivotSimlet.html) がある。おそらく JsonSimlet の使用頻度が高くなるはずだ。

[Ext.ux.ajax.SimXhr](https://docs.sencha.com/extjs/6.5.2/classic/Ext.ux.ajax.SimXhr.html) は XMLHttpRequest オブジェクトをシミュレートする。SimXhr の中には Simlet が含まれていて、その Simlet がレスポンスを生成しているのだ。

# 静的 API サーバー

![static-api-server.png](https://qiita-image-store.s3.amazonaws.com/0/67627/4067b1ea-2f71-4335-761a-9966aa81c482.png)

https://fiddle.sencha.com/#fiddle/2fg0

SimManager で静的な値を返す API サーバーを定義するには、`type` に JsonSimlet のエイリアスを設定し、`url` に URL を設定する。そして `data` にはレスポンスとして返したいデータを指定する。

```js
Ext.ux.ajax.SimManager.register([{
    type: 'json',
    url: '/users',
    data: [
        { id: 1, name: 'Alice'   },
        { id: 2, name: 'Bob'     },
        { id: 3, name: 'Charlie' },
        { id: 4, name: 'Dave'    }
    ]
}]);
```

次のように Ext.Ajax でリクエストを送信することで静的 API サーバーの動作を検証できる。

```js
Ext.Ajax.request({
    url: '/users',
    success: function (response) {
        console.dir(Ext.decode(response.responseText));
    }
});
```

実行結果は上記のスクリーンショットを参照してほしい。

# 動的 API サーバー

![dynamic-api-server.png](https://qiita-image-store.s3.amazonaws.com/0/67627/3a023804-c67d-3188-619c-4b33f526c0a2.png)

https://fiddle.sencha.com/#fiddle/2fgf

SimManager で動的な値を返す API サーバーを定義するには、`type` に JsonSimlet のエイリアスを設定し、`url` に URL を設定する。そして `data` にはレスポンスとして返したいデータを生成する関数を指定する。

```js
Ext.ux.ajax.SimManager.register([{
    type: 'json',
    url: '/users',
    data: function (ctx) {
        var users = [
            { id: 1, name: 'Alice'   },
            { id: 2, name: 'Bob'     },
            { id: 3, name: 'Charlie' },
            { id: 4, name: 'Dave'    }
        ];
        return users.filter(user => user.name.includes(ctx.params.query));
    }
}]);
```

関数の `ctx` パラメーターにはコンテキストを表すオブジェクトが渡される。オブジェクトには下記のプロパティが含まれる。

| プロパティ | 型           | 内容                         |
|------------|--------------|------------------------------|
| method     | 文字列       | HTTP メソッド                |
| params     | オブジェクト | クエリパラメーター           |
| url        | 文字列       | クエリパラメーターを含む URL |
| xhr        | オブジェクト | モック XHR オブジェクト      |

次のように Ext.Ajax でリクエストを送信することで動的 API サーバーの動作を検証できる。

```js
Ext.Ajax.request({
    url: '/users?query=li',
    success: function (response) {
        console.dir(Ext.decode(response.responseText));
    }
});
```

実行結果は上記のスクリーンショットを参照してほしい。

# REST API サーバー

![rest-api-server.png](https://qiita-image-store.s3.amazonaws.com/0/67627/fbda79b1-9af5-2d41-81a2-30f88e60cdd5.png)

https://fiddle.sencha.com/#fiddle/2fkk

REST API サーバーを定義するには、GET・POST・PUT・DELETE などの HTTP メソッドに対応した Simlet を使用しなければならない。標準では用意されていないので JsonSimlet を継承する RestSimlet を定義しよう。

`doGet` メソッドと `doPost` メソッドは JsonSimlet で定義されているので、`doPut` メソッドと `doDelete` メソッドを追加で定義する。それぞれ JsonSimlet で定義されている `doPost` メソッドと同じ定義にしている。

```js
Ext.define('RestSimlet', {
    extend: 'Ext.ux.ajax.JsonSimlet',
    alias: 'simlet.rest',

    doPut: function (ctx) {
        return this.doGet(ctx);
    },

    doDelete: function (ctx) {
        return this.doGet(ctx);
    }
});
```

そして SimManager で REST API サーバーを定義しよう。`type` に RestSimlet のエイリアスを設定し、`url` に URL を設定する。そして `data` にはレスポンスとして返したいデータを生成する関数を指定する。リクエストの HTTP メソッドによって処理を分岐させたり、レスポンスの HTTP ステータスコードを指定することもできる。モック REST API サーバーとしては十分な機能が実装できるはずだ。


```js
Ext.ux.ajax.SimManager.register([{
    type: 'rest',
    url: '/users',
    data: function (ctx) {
        switch (ctx.method.toUpperCase()) {
            case 'GET':
                this.status = 200;
                this.statusText = 'OK';
                return [
                    { id: 1, name: 'Alice'   },
                    { id: 2, name: 'Bob'     },
                    { id: 3, name: 'Charlie' },
                    { id: 4, name: 'Dave'    }
                ];
            case 'POST':
                this.status = 201;
                this.statusText = 'Created';
                return { id: 5, name: 'Eve' };
            case 'DELETE':
                this.status = 204;
                this.statusText = 'No Content';
                return;
        }
    }
}]);
```

次のように Ext.Ajax でリクエストを送信することで REST API サーバーの動作を検証できる。

```js
Ext.Ajax.request({
    method: 'GET',
    url: '/users',
    success: function (response) {
        console.log('GET /users');
        console.log(response.status + ' ' + response.statusText);
        console.dir(Ext.decode(response.responseText));
    }
});
Ext.Ajax.request({
    method: 'POST',
    url: '/users',
    success: function (response) {
        console.log('POST /users');
        console.log(response.status + ' ' + response.statusText);
        console.dir(Ext.decode(response.responseText));
    }
});
Ext.Ajax.request({
    method: 'DELETE',
    url: '/users',
    success: function (response) {
        console.log('DELETE /users');
        console.log(response.status + ' ' + response.statusText);
        console.dir(Ext.decode(response.responseText));
    }
});
```

実行結果は上記のスクリーンショットを参照してほしい。

# 名前付きパラメーターに対応した REST API サーバー

![rest-api-server-with-named-parameters.png](https://qiita-image-store.s3.amazonaws.com/0/67627/a9171bbc-6170-a4df-bade-a078927ec808.png)

https://fiddle.sencha.com/#fiddle/2flh

REST API サーバーを定義してみるとリソースの ID などパスに含まれる値によって処理を変更したいことがある。URL の指定には正規表現を利用できるので、それを活用してパスに含まれる値を `ctx.params` にマージするように RestSimlet を修正してみよう。

`doGet` メソッドをオーバーライドすることで実現する。今回は仕様として提案されている RegExp Named Capture Groups を使用してみる。

```js
Ext.define('RestSimlet', {
    extend: 'Ext.ux.ajax.JsonSimlet',
    alias: 'simlet.rest',

    doGet: function (ctx) {
        ctx.params = Ext.merge(ctx.params, this.getNamedParams(ctx));
        return this.callParent(arguments);
    },

    doPut: function (ctx) {
        return this.doGet(ctx);
    },

    doDelete: function (ctx) {
        return this.doGet(ctx);
    },

    privates: {
        getNamedParams: function (ctx) {
            var url = ctx.url.split(/\?|#/)[0],
                pattern = ctx.xhr.simlet.url;
            return url.match(pattern).groups;
        }
    }
});
```

この RestSimlet を使用して REST API サーバーを定義しよう。`type` に RestSimlet のエイリアスを設定し、`url` に正規表現で URL を設定する。名前付きキャプチャが含まれる正規表現を指定するのがポイントだ。そして `data` にはレスポンスとして返したいデータを生成する関数を指定する。`ctx.params.id` に `id` という名前でキャプチャされた値が入ってくる。

```js
Ext.ux.ajax.SimManager.register([{
    type: 'rest',
    url: RegExp('^/users(/(?<id>\\d+))?$'),
    data: function (ctx) {
        switch (ctx.method.toUpperCase()) {
            case 'GET':
                switch (Number(ctx.params.id)) {
                    case 1:
                        this.status = 200;
                        this.statusText = 'OK';
                        return { id: 1, name: 'Alice' };
                    case 2:
                        this.status = 200;
                        this.statusText = 'OK';
                        return { id: 2, name: 'Bob' };
                    default:
                        this.status = 404;
                        this.statusText = 'Not Found';
                        return;
                }
        }
    }
}]);
```

次のように Ext.Ajax でリクエストを送信することで REST API サーバーの動作を検証できる。

```js
Ext.Ajax.request({
    method: 'GET',
    url: '/users/1',
    success: function (response) {
        console.log('GET /users/1');
        console.log(response.status + ' ' + response.statusText);
        console.dir(Ext.decode(response.responseText));
    }
});
Ext.Ajax.request({
    method: 'GET',
    url: '/users/2',
    success: function (response) {
        console.log('GET /users/2');
        console.log(response.status + ' ' + response.statusText);
        console.dir(Ext.decode(response.responseText));
    }
});
Ext.Ajax.request({
    method: 'GET',
    url: '/users/3',
    failure: function (response) {
        console.log('GET /users/3');
        console.log(response.status + ' ' + response.statusText);
        console.dir(Ext.decode(response.responseText));
    }
});
```

実行結果は上記のスクリーンショットを参照してほしい。

# REST API サーバーを含むサンプルアプリケーション

![crud-sample-with-rest-api-server.png](https://qiita-image-store.s3.amazonaws.com/0/67627/2b7bc519-2232-8830-b8c2-7ef0a403b5ad.png)

https://fiddle.sencha.com/#fiddle/2fbu

紹介した方法を使用してサンプルアプリケーションを実装してみたので参考にしてほしい。

---

今回は。SimManager を利用してモック REST API サーバーを実装してみた。Sencha Fiddle でのサンプル実装にぜひ活用していただきたい。

# 参考文献

http://examples.sencha.com/extjs/6.2.0/examples/classic/ticket-app/
https://docs.sencha.com/extjs/6.2.0/classic/Ext.ux.ajax.SimManager.html
https://docs.sencha.com/extjs/6.2.0/classic/Ext.ux.ajax.Simlet.html
https://docs.sencha.com/extjs/6.2.0/classic/Ext.ux.ajax.SimXhr.html
https://github.com/tc39/proposal-regexp-named-groups