【Backlog 2021】 Googleさんの zx で Backlog API を扱う（JavaScript で課題を追加する）

この記事は、[2021年の Backlog のアドベントカレンダー＠Adventar](https://adventar.org/calendars/6325) の 24日目の記事です。

## この記事の内容
今回扱う Backlog の API は、既にいろいろな方が記事を書いていたりもする以下のページに書いてあるものです。

●Backlog API とは | Backlog Developer API | Nulab
　https://developer.nulab.com/ja/docs/backlog/
![Backlog API.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/3ef3b36f-b9d7-7146-cb83-ab134c041a1c.jpeg)

その API を扱う方法として、以前見かけて気になっていた Googleさんの zx を組み合わせてみます。
　●google/zx: A tool for writing better scripts
　　https://github.com/google/zx

zx は Google から提供されている、Javascript で ShellScript を実装できるものです。
環境を選びますが、Mac でよく JavaScript を扱っている自分としては、とても気になるものでした。

ここで「zx を使うやり方でなく、Node.js でシンプルなやり方ができるのでは？」と思われたかもしれないですが、その通りだと思います。
ただ、思いついたものを試してみたくて、今回の記事を書いてみてます。

zx は初めて使ってみたのですが、試してみたり機能を調べていると個人的には楽しかったです。

## Backlog の API　の簡単なお試し
「Backlog API とは？」という話は、[上に掲載していた公式ページの説明](https://developer.nulab.com/ja/docs/backlog/)をご参照ください。
本編に進む前に、まずは簡単なお試しをしてみます。

### 必要となる下準備
API を試しに使ってみるために、そのための下準備から進めていきます。

#### APIキーの発行
API　を使うためにはキーの情報が必要です。
Backlog の「個人設定」の「API」のページから、キーを作成しましょう。
![APIキーの発行.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/605b1aa1-9123-041c-336b-018a1e998921.jpeg)

キーに関する説明を書いて、登録ボタンを押せば OK です。

### ブラウザだけで試す
まずは、APIキーだけを使って情報が取得できるものを、ブラウザ上で試してみます。

具体的には、公式ドキュメントに書いてある「[認証ユーザー情報の取得](https://developer.nulab.com/ja/docs/backlog/api/2/get-own-user/#)」を使います。
![認証ユーザ情報の取得.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/56155c98-ddad-8f66-289f-e21eb24195e9.jpeg)

リクエスト例は、公式ドキュメントの「[認証と認可](https://developer.nulab.com/ja/docs/backlog/auth/#)」の部分にも書いてあるこの形です。
![認証と認可.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/475785bd-cf4b-96fe-ac0e-f1d6bc2c079f.jpeg)

以下は、「https://【スペースID】.backlog.jp/」という URL で利用している場合の例です。

```
https://【スペースID】.backlog.jp/api/v2/users/myself?apiKey=【APIキー】
```

上記の URL を Chrome のアドレスバーに入力して情報を取得した結果を以下にのせておきます（マスクしている情報ばかりですが...）。
![Chromeで取得できた結果.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/61fbce12-1a3e-2dbb-714d-a3fc4ccae6a1.jpeg)

### curl で試す
#### GET
次に [curl](https://curl.se/) を使って試してみます。
先ほどとは異なる「[スペース情報の取得](https://developer.nulab.com/ja/docs/backlog/api/2/get-space/#)」を試してみることにします。
![スペース情報の取得.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/0c25c8af-d0d0-be5e-d6a5-48b352dcf3b9.jpeg)

また、出力を [jqコマンド](https://stedolan.github.io/jq/)で少し整えることにします（jqコマンドは Homebrew でインストールしていたものがあって、それを使いました）。
以下が実行したコマンドと、得られた出力です。

```bash
curl https://【スペースID】.backlog.jp/api/v2/space?apiKey=【APIキー】 | jq
```

![curlとjq.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/596d11f3-9374-5b97-51a9-804ce9ca3547.jpeg)

無事、情報取得ができているようです。

## Backlog の課題追加を curl で試す
次は課題追加を試していきます。

### 課題追加の API仕様を確認する
公式ドキュメントで「[課題の追加](https://developer.nulab.com/ja/docs/backlog/api/2/add-issue/#)」の仕様を確認しておきます。
先ほど試した情報取得よりも少しややこしいです。

なお、先ほどまでとは異なり「POST」を使う形になります。
![課題の追加の概要.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/dcba604c-1d78-7869-c599-a4378d51c831.jpeg)

![課題追加のパラメータ.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/15eed829-0482-8e19-a246-67682bee44cc.jpeg)

パラメータがいろいろ書いてありますが、必須となるのは以下のみのようです。

- projectId (必須)  数値  課題を登録するプロジェクトのID
- summary (必須)  文字列  課題の件名
- issueTypeId (必須)  数値  課題の種別のID
- priorityId (必須)  数値  課題の優先度のID

### 必要なパラメータの値を確認する
上記の必須パラメータの中の、数値で指定するものの値を確認していきます。
API を使った取得もできるようですが、Backlog のページでも確認ができそうでした。

#### projectId
「projectId」は、プロジェクト設定ページへ遷移すると、その際の URL に書かれています。
プロジェクト設定ページへ遷移して URL を見てみてください（以下のような URL となっているはずです）。

```
https://【スペースID】.backlog.jp/EditProject.action?project.id=【projectId】
```

#### issueTypeId
「issueTypeId」は、「プロジェクト設定」から「種別」を選び、どれか 1つの課題の種別を選んだ後、URL を見てみると以下のように書かれています。

```
https://【スペースID】.backlog.jp/EditIssueType.action?issueType.id=【issueTypeId】&issueType.projectId=【projectId】
```

参考情報として、この時にプロジェクト設定でたどった画面のキャプチャをのせておきます。
![種別の画面1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/0c56b3ef-907f-9d89-bdb3-1dc2d7f5122e.jpeg)

![種別の画面2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/5864e0e4-0b4a-ad4d-81d1-d5ac7a5ef182.jpeg)

#### priorityId
「priorityId」は、公式ドキュメントの「[優先度一覧の取得](https://developer.nulab.com/ja/docs/backlog/api/2/get-priority-list/#)」に書いてあるものが固定で利用されている気がします。
![優先度一覧の取得も例.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/ead98cc9-2145-7a7b-ce8b-b6fd4bd280ef.jpeg)

### curl による POST
課題追加を行うのに必要な情報がそろったので、curl で POSTリクエストを送り、実際に課題を追加してみます。

```bash
curl -X POST https://【スペースID】.backlog.jp/api/v2/issues?apiKey=【APIキー】 -d "projectId=【projectId】" -d "summary=課題追加のテスト(curlによる)" -d "issueTypeId=【issueTypeId】" -d "priorityId=【priorityId】"
```

issueTypeId は「タスク」に該当するものにして、priorityId は「中」にあたる「3」にしてみました。
以下が実際に登録した課題です（「課題の詳細」に少し書かれている内容がありますが、これは課題追加を API 経由で行った後に、ブラウザ上から足したもので、当初は空でした）。
![curlで追加した課題.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/06764c1b-d031-e724-2874-70d3308e3155.jpeg)

これで準備は整いました。

## zx で課題の追加を行う
### zx で POSTリクエストを行う方法
先ほど最後に試した課題の追加を、zx を用いて行ってみます。

zx についてきちんと調べる前は、zx で curlコマンドを使うような形かと思っていたのですが、以下の [fetch の処理](https://github.com/google/zx#fetch)がデフォルトで使えるようだったので、こちらを使っていきます。
![fetch.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/92043ae9-1377-e8ea-7f4e-ab819a9819b5.jpeg)

どうやら、[node-fetch](https://www.npmjs.com/package/node-fetch#common-usage) がデフォルトで使えるようになっているようです。

そして、軽く試してみた感じだと以下の「[Post with form parameters](https://www.npmjs.com/package/node-fetch#post-with-form-parameters)」というものが zx でも良い感じに使えそうでした。
![Post with form parameters.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/815edaea-9163-2566-7291-487111646ea5.jpeg)

### zx をローカルインストールで動かす
それでは、zx を使えるようにしていきます。
なお、Node.js のバージョンは「14.13.1以上」となっているようなのでご注意ください。

[公式の説明では最初にグローバルインストールをするやり方がのっています](https://github.com/google/zx#install)が、自分はローカルインストールで進めました。
`npm i zx` でローカルにインストールします。

グローバルインストールだと公式ページにあるように `zx ./script.mjs` でプログラムを実行できますが、ローカルインストールをしたので `npx zx ./script.mjs` などとする必要があります（npxコマンドは、ローカルインストールしたものを実行するときに重宝してます）。

#### nodeコマンドでプログラムを実行する
上記の zxコマンドを使う方法もあったのですが、調べていくと nodeコマンドで普通に実行できる形にもできるようでした。
具体的には、以下に出てきている `zx/globals` をインポートするやり方です。
![zx/globalsを使う.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/e2cb446e-9559-784d-46bd-1e9c8ab7a9fc.jpeg)
この後のプログラムでは、この方法を試してみることにします。

#### mjs で Top-Level Await
ここまでで、mjs を拡張子にしていることに触れずに来ましたが、これは Top-Level Await を手軽に有効にするためです。
ちなみに、公式の説明で「.js を使いたい場合は async function でラップしたりしてね」というような記載があります。
（ `If you prefer the .js extension, wrap your scripts in something like void async function () {...}().` という部分）
![mjsを使う.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/26db679a-b406-0336-5d77-eaf065529aae.jpeg)

### 実際に課題を追加してみる
#### 1件のみの追加
前置きがいろいろと長くなりましたが、ここがメインとなる部分です。
今回用いたプログラムを掲載します（url指定の変数名が適当なのは、気にしないでください）。

```javascript
import "zx/globals";

const url1 = 'https://【スペースID】..backlog.jp/api/v2';
const url2 = '/issues';
const url3 = '?apiKey=【APIキー】';

const params = new URLSearchParams();
params.append('projectId', 【projectId】);
params.append('summary', '課題追加のテスト(zxによる)');
params.append('issueTypeId', 【issueTypeId】);
params.append('priorityId', 【priorityId】);

const resp = await fetch(url1+url2+url3, {method: 'POST', body: params});
if (resp.ok) {
  console.log(await resp.json());
}
```

あとは nodeコマンドでプログラムを実行します。
コマンド実行結果として、以下のような出力が得られます。
![コマンド実行時の表示.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/f5e8e72b-efad-10ac-32fe-7d8a63ff4891.jpeg)

そして、Backlog のページ側を確認すると、以下のように課題が追加されていました。
![zxで追加された課題.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/a50e40cb-86ae-3e4c-0d29-47ac76a2eb22.jpeg)

#### 3件まとめて追加
1件の追加はうまくいったので、今度はまとめて3件の追加を行ってみます。
3件の課題のタイトルを配列で適当に用意して、それを使った課題追加を行います。

プログラムの内容と実行結果は、それぞれ以下のとおりです。

```javascript
import "zx/globals";

const url1 = 'https://【スペースID】..backlog.jp/api/v2';
const url2 = '/issues';
const url3 = '?apiKey=【APIキー】';

const summaryList = [
  "課題追加のテスト1(zxによる)",
  "課題追加のテスト2(zxによる)",
  "課題追加のテスト3(zxによる)",
];

for (const summary of summaryList) {
  const params = new URLSearchParams();
  params.append('projectId', 【projectId】);
  params.append("summary", summary);
  params.append('issueTypeId', 【issueTypeId】);
  params.append('priorityId', 【priorityId】);

  const resp = await fetch(url1 + url2 + url3, {
    method: "POST",
    body: params,
  });
  if (resp.ok) {
    console.log(await resp.json());
  }
}
```

![3件の追加.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/0156e409-9062-416a-8c0f-b5d614bc415a.jpeg)

想定通りに課題が追加されるのが確認できました。

## おわりに
今回、Backlog API と zx を組み合わせた課題追加を行ってみて、無事に想定通りの結果を得ることができました。
今現在の内容だと、「Node.js の node-fetch を使うだけでも良いのでは？」という感じなので、zxコマンドならではの要素をこの後は加えていけたらと思っています。

### 他にやってみたいこと・やったほうが良いこと
#### 環境変数を使う
今回、ソースコードの中にキーを直書きしましたが、本当は以下の公式の FAQ に登場もしている環境変数を使ったほうが良いかと思います。
![環境変数を使う.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/d5ed4dbb-24d0-2dca-efa2-a6754e2947f8.jpeg)

#### 外部ファイルに記載された複数の課題の情報を一気に追加する
今回、課題を 1つだけ追加しましたが、別に用意したリストの内容に基づいて、複数の課題を一括登録するものも作れたら良いかな、と思いました。
自分以外の誰かが作ることも想定すると、Excel で出力された csv を使うとかが良いのかな...
（[csv-parse](https://www.npmjs.com/package/csv-parse) とかを使うと良さそう？）

#### 外部から取得したプログラムを実行する
zx の機能に、以下で記載されている「[リモートのスクリプトの実行](https://github.com/google/zx#executing-remote-scripts)」が、機能の中で気になりました。
![リモートのスクリプトを実行.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/39e34635-a369-d625-798d-b2c44be88236.jpeg)

#### 他に気になったもの
このあたりも何か面白い使い方がないかな、と気になったところでした。
![pipe.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/ccc8f190-4da9-b76e-c0d9-3ebaf9a83acd.jpeg)
[zx/pipelines.md](https://github.com/google/zx/blob/main/docs/pipelines.md) とかを見てみると良いのかな。
![pipelineの詳細.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/53450e82-789b-5dc5-fc39-c3eae32f9821.jpeg)

## 【追記】 その後試してみたこと
zx にフォーカスしたものになりますが、こんなことを試して記事をまた 1つ書いてみました。

●Googleさんの zx をローカルインストールして「nodeコマンドで実行」／「ファイルを直接指定して実行」 - Qiita
　https://qiita.com/youtoy/items/792c8253c14f1b66f4c7