長年にわたり、Kinstaのサービスはコントロールパネル「MyKinsta」を介した「手動で実行する」ものでした。そこに改善が加えられ、今では、Kinsta APIの導入と新たなAPIエンドポイントの継続的なリリースにより、さらなる柔軟性が確保されています。Kinstaサービスをパーソナライズしながら、ご自身にあった方法でご利用いただけます。このような手法の一つとして、サイト作成のような活動を監視および管理するSlackbotの開発が可能です。

構築するもの

こちらの記事では、Kinsta APIと相互作用して情報を取得し、Slack API Incoming Webhooksを使用して指定されたSlackチャンネルにリアルタイムメッセージを配信するSlackbot(Slackアプリケーション)を構築する方法をご説明します。

Expressフレームワークを使用しながらNode.jsアプリケーションを作成します。最終的には、Kinsta API統合型のWordPressサイト作成用UIができあがります。このアプリケーションで、フォームを使用しWordPressサイトの設定情報を取得することになります。そして、指定のSlackチャンネルにサイトとその動作状況についての情報が送信されます。

構築するアプリケーションの紹介(Slackにリアルタイムで情報が送信される)
構築するアプリケーションの紹介(Slackにリアルタイムで情報が送信される)

前提条件

このプロジェクトを進めるには、以下が必要になります。

  • JavaScriptとNode.jsの基本的な知識
  • Node.jsのバージョン12以上
  • npm(Node Package Manager)がPCにインストールされていること
  • Slackワークスペース

開発環境のセットアップ

はじめに、アプリケーションのディレクトリ作成と初期化を行います。

mkdir my-express-app 
cd my-express-app 
npm init -y

npm init -yコマンドを実行すると、プロジェクトのディレクトリに新しいpackage.jsonファイルがデフォルト値で作成されます。このファイルには、プロジェクトとその依存関係についての重要な情報が含まれます。

次に、プロジェクトに必要な依存関係をインストールします。以下の依存関係は必須です。

  • ejs:EJS(Embedded JavaScript)は、JavaScriptで動的なHTMLコンテンツを生成できるテンプレートエンジンです。
  • express:Expressは、Node.js用の高速かつミニマルなウェブアプリケーションフレームワークです。ルーティング、ミドルウェアのサポート、HTTPリクエストとレスポンスの処理など、必要不可欠な機能により、ウェブアプリケーションとAPIの構築を支援します。
  • express-ejs-layouts:Express EJS layoutsはExpressの拡張機能で、レイアウトやテンプレートを使用して複数のビューで一貫した構造を維持することができます。

これらの依存関係をインストールするには、以下のコマンドを実行します。

npm install ejs express express-ejs-layouts

さらに、Node.jsプロジェクトのビルドとテストを容易にするために、以下の開発依存関係をインストールします。

  • nodemon:ディレクトリのファイルで変更が検出されるたびに、Node.jsアプリケーションを自動で再起動してくれます。合理的な開発ワークフローを可能にするツールです。
  • dotenv.envファイルから環境変数を読み込む際に重要な役割を果たすゼロ依存モジュールです。

上記をインストールするには、次のコマンドを実行します。

npm install -D nodemon dotenv

package.jsonが初期化され、すべての依存関係がインストールされたら、新しいファイル、例えばapp.jsを作成します。

touch app.js

app.jsファイルのデフォルトのセットアップを以下に示します。ここで、必要なモジュールをインポートし、特定のポートで実行するように設定します。

//必要となるモジュールをインポート
const express = require('express');
const app = express();

// ここでルートとミドルウェアを設定
// ...

// 指定のポートをリッスンするサーバーを起動
app.listen(process.env.PORT || 3000, () => {
  console.log(`Server is running on port ${process.env.PORT || 3000}`);
});

Node.jsアプリケーションを実行するには、次のコマンドを使用します。

node app.js

しかし、この方法では、プロジェクトに変更を加えるたびに手動で再起動する必要性が生じます。この不便さを解消するために、すでにインストール済みのnodemonを使います。package.jsonファイル内でスクリプトコマンドを作成して、この設定を行います。

  "scripts": {
    "dev": "nodemon app.js"
  },

続いて、Node.jsアプリケーションを自動で再起動して実行してみます。以下のコマンドを実行してください。

npm run dev

ExpressとEJSテンプレートの使用

この説明では、ブラウザにコンテンツを表示するNode.jsアプリケーションを構築します。これを実現するために、ウェブフレームワークとしてexpress.jsを、そして、テンプレートエンジンとしてEJS(Embedded JavaScript)を使用します。

EJSをビューエンジンに設定するには、app.jsファイルに以下の行を追加します。これにより、.ejsファイルを実行できるようになります。

// ビューエンジンとしてEJSを使う
app.set('view engine', 'ejs');

ExpressでのEJS設定が完了したので、ルートの定義へと進みます。ウェブアプリケーションにおいて、ルートは異なるHTTPリクエスト(GETやPOSTなど)に対するアプリケーションの応答方法を決定するもので、特定のURLにアクセスしたときに実行されるアクションが指定されます。

例えば、ユーザーがインデックスページ(/)に移動したときに特定のページを表示するルートを作成することができます。これを行うには、GETリクエストメソッドを使用します。

// ホームページのルートを定義する
app.get('/', (req, res) => {
  // ここで誰かがホームページにアクセスしたときに何をするかを指定
  // 例えば、EJSテンプレートをレンダリングしたり、HTMLコンテンツを送信したり
});

上記のコードの役割は次の通りです。ユーザーがアプリケーションのindexにアクセスすると、サーバーは2番目のパラメータとして指定されたコールバック関数を実行します。このコールバック関数の内部で、EJSテンプレートをレンダリングしたり、ホームページに表示するHTMLコンテンツを送信したりするロジックを処理できます。

res.render()メソッドを使用してEJSテンプレートをレンダリングしたり、res.send()を使用してシンプルな HTMLコンテンツを送信したりできます。

app.get('/', (req, res) => {
    res.send('Hello World');
});

アプリケーションを実行すると「Hello World」がindexページに表示されます。

EJSテンプレート

今回の説明はロジックに重点を置いたものなので、スターターファイルを使用します。ゼロから全てを記述する必要はありません。以下のステップに従ってください。

  1. GitHubのテンプレートにアクセスし、新しいリポジトリを作成
  2. リポジトリ作成時にすべてのブランチを含めるオプションを選択
  3. リポジトリを作成したら、Gitを使ってプロジェクトをPCに複製
  4. スターターコードにアクセスするには、ローカルリポジトリのstarter-filesブランチに切り替える

スターターコードには、大きく分けてpublicviewsの2のフォルダがあります。publicフォルダには、すべての静的アセット (CSS ファイルと画像) が格納されています。これは静的ファイルとしてテンプレートに追加されます。

// 静的ファイル
app.use(express.static('/public'));
app.use('/css', express.static(__dirname + '/public/css'));
app.use('/images', express.static(__dirname + '/public/images'));

viewsフォルダには、layout.ejsファイルと、pagespartialsの2つのフォルダがあります。layout.ejsファイルでこのプロジェクトの一般的なレイアウトを定義するので、ページ毎に同じコードを繰り返し記述する必要はありません。express-ejs-layoutsライブラリをapp.jsファイルにインポートし、設定を行います。

// インポート
const expressEjsLayouts = require('express-ejs-layouts');

// 構成
app.use(expressEjsLayouts);

pagesフォルダにはルートファイル(index.ejsoperation.ejs)があり、partialsフォルダには各種コンポーネント(header.ejsfooter.ejs)があります。これらを次のようにレイアウトに追加していきます。

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <link rel="icon" href="/images/favicon.ico" />
        <link rel="stylesheet" href="/css/index.css" />
        <title>Site Builder</title>
    </head>
    <body>
        <div class="app-container">
            <%- include('partials/header') %>
            <div class="container"><%- body %></div>
            <%- include('partials/footer') %>
        </div>
    </body>
</html>

Node.jsアプリケーションを実行すると、UIが読み込まれます。これから、このアプリケーションにロジックを追加し、フォームデータをKinsta APIに送信し、操作開始時にサイトに関する情報をSlackに送信する処理を設定します。

Slack Incoming Webhooksを使う

Slack Incoming Webhooksを使うことで、外部アプリケーションからSlackにメッセージを簡単に送信することができます。Slack Incoming Webhooksを使用するには、Slackアプリケーションを作成して設定を行い、Slackにメッセージを送信するためのWebhook URLをコピーします。

SlackアプリのセットアップとWebhook URLの取得

以下の手順でSlackアプリケーションを作成します。

  1. Slack APIダッシュボードに移動
  2. Create New App]ボタンをクリック(モーダルが開く)
  3. アプリを一から作り始めるので「From Scratch」を選択
  4. Kinsta Bot」などのSlackアプリの名前を入力する
  5. 次に、アプリをインストールするワークスペースを選択し、「Create App」ボタンをクリックする

Slackアプリが作成できたら、「Features」に移動し、「Incoming Webhooks」を選択することで有効化できます。スイッチを切り替えて、アプリのIncoming Webhooksを有効にします。

Webhook URLs for Your Workspace」セクションまでスクロールし、「Add New Webhook to Workspace」をクリックします。プロンプトが表示されるので、そこでメッセージ送信先のチャンネルを選択します。必要なチャンネルを選択し「Authorize」をクリックします。

承認後、選択したチャンネルのWebhook URLが表示されます。このURLを使用して、Slackにメッセージを送信することになります。Webhook URLは以下のようになります。

https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX

このWebhookはユーザーやチャンネルに固有のものです。アプリの認証トークンとして機能するため、/services/に続くコードを.envファイルに安全に保管してください。変更内容を保存し有効にするために、ワークスペースにアプリを再インストールするよう指示が表示されます。

Node.jsとKinsta APIでSlackにメッセージを送信する

Node.jsアプリケーションのインターフェースをセットアップし、Slackbot(WebHook URL)が用意できたので、次はロジックの処理を行います。

Node.jsでのフォームデータの取得

indexページにはフォームがあります。これを使ってKinsta APIにデータを送信し、WordPressサイトの新規作成が行われます。これを機能させるには、indexページからPOSTリクエストを実行する必要があります。フォームにPOSTメソッドがあり、入力フィールドにname属性があることを確認してください。これをapp.jsファイルで使用します。

app.post('/', (req, res) => {
    // フォームデータを使って必要な操作を行う
});

Node.jsでフォームからデータを取得するには、以下のミドルウェアを使用します。

app.use(express.json());
app.use(express.urlencoded({ extended: true }));

これで、req.body.[form field name]を使ってフォームの値にアクセスできるようになります。例えば、req.body.displayNameは、フォームから送信された表示名がわかります。フォームデータ全体をログとして出力してみます。

app.post('/', (req, res) => {
    console.log(req.body);
});

コードを実行すると、フォームに入力して送信ボタンをクリックした後のフォームデータが表示されます。

Node.jsのreqパラメータから取得したフォーム情報をログに出力する
Node.jsのreqパラメータから取得したフォーム情報をログに出力する

Node.jsでのKinsta APIを使用したサイトの作成

Node.jsでKinsta APIを使用してWordPressサイトを作成するには、fetch() メソッドを使用します。このメソッドは最新のNode.jsバージョンでサポートされており、効率的に機能します。

Kinsta APIで何らかの操作を行うには、APIキーを作成する必要があります。APIキーを生成するには以下の手順を踏みます。

  1. MyKinstaにログイン
  2. APIキー」ページに移動(「(画面右上のアカウント名)」>「企業の設定」>「APIキー」)
  3. APIキーを作成」をクリック
  4. 有効期限を選択するかカスタム設定で指定
  5. キーに一意の名前を付ける
  6. 生成」をクリック

生成したAPIキーは、この瞬間しか見ることができないので、必ず忘れずにコピーして安全に保管してください。このプロジェクトでは、ルートディレクトリに.envファイルを作成し、APIキーをKINSTA_API_KEYとして保存します。

さらに、Kinsta APIを使用してWordPressサイトを作成するには、企業IDが必要です。これはMyKinstaの「企業の設定」>「請求先情報」>「企業ID」で確認できます。このIDを.envファイルにも保存し、process.envから環境変数にアクセスできるようにします。この機能を有効にするには、app.jsファイルの先頭でdotenv依存関係を次のように設定してください。

require('dotenv').config();

Kinsta APIを通じてWordPressサイトの作成を続行するには、/sitesエンドポイントにPOSTリクエストを送信し、必要なデータをreq.bodyオブジェクトで提供します。

const KinstaAPIUrl = 'https://api.kinsta.com/v2';

app.post('/', (req, res) => {
    const createSite = async () => {
        const resp = await fetch(`${KinstaAPIUrl}/sites`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                Authorization: `Bearer ${process.env.REACT_APP_KINSTA_API_KEY}`,
            },
            body: JSON.stringify({
                company: process.env.REACT_APP_KINSTA_COMPANY_ID,
                display_name: req.body.displayName,
                region: req.body.location,
                install_mode: 'new',
                is_subdomain_multisite: false,
                admin_email: req.body.email,
                admin_password: req.body.password,
                admin_user: req.body.username,
                is_multisite: false,
                site_title: req.body.siteTitle,
                woocommerce: false,
                wordpressseo: false,
                wp_language: 'en_US',
            }),
        });
        const data = await resp.json();
        console.log(data);
    };
    createSite();
});

上記のコードを実行すると、Kinsta APIでWordPressサイトが新規作成されます。とは言え、今回は、これが主な目的ではありません。サイト作成操作が成功したときに、サイトに関する情報を含むメッセージをSlackに送信することが目標です。

Incoming Webhook URLでのSlackメッセージの送信

これを行うには、APIリクエストのレスポンスステータスをチェックするIf文を作成します。これが202であれば、「サイト作成が開始された」ことを意味し、Incoming Webhooks URL を使ってSlackにメッセージを送信できます。お好みのHTTPリクエストライブラリ(Axiosなど)やメソッドを使用して、SlackにPOSTリクエストを送信します。fetch() メソッドを使ってみましょう。

if (data.status === 202) {
    fetch(
        `https://hooks.slack.com/services/${process.env.SLACK_WEBHOOK_ID}`,
        {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                text: 'Hello, world.',
            }),
        }
    );
}

上記のコードを実行し、サイト作成フォームに情報を入力します。処理が成功すると、すぐにSlackにメッセージが送信されます。

Incoming Webhooksを使ってNode.jsからSlackに送信されたHello Worldのメッセージ
Incoming Webhooksを使ってNode.jsからSlackに送信されたHello Worldのメッセージ

Slackメッセージのカスタマイズ

上の例では基本的なテキストメッセージを送信していますが、Slack Incoming Webhooksは単純なテキスト以上のものをサポートしています。メッセージをカスタマイズして、添付ファイル、リンク、画像、ボタンなどを利用することもできます。

Slackメッセージをカスタマイズする方法の1つとして、Slack block Kit Builderがあります。ブロックキットはSlackが提供するUIフレームワークで、様々なコンテンツ要素を使って、リッチかつインタラクティブなメッセージを構築することができます。

メッセージを整えフォームとサイト作成レスポンスから取得した値を追加するために、上記ビルダーで作成したブロックを利用します。

const message = {
    blocks: [
        {
            type: 'section',
            text: {
                type: 'mrkdwn',
                text: `Hello, your new site (${req.body.displayName}) has started building. It takes minutes to build. You can check the operation status intermittently via https://site-builder-nodejs-xvsph.kinsta.app/operation/${req.body.displayName}/${data.operation_id}.`,
            },
        },
        {
            type: 'divider',
        },
        {
            type: 'section',
            text: {
                type: 'mrkdwn',
                text: "_Here are your site's details:_",
            },
        },
        {
            type: 'section',
            text: {
                type: 'mrkdwn',
                text: `1. *Site URL:* http://${req.body.displayName}.kinsta.cloud/n2. *WP Admin URL:* http://${req.body.displayName}.kinsta.cloud/wp-admin/`,
            },
        },
    ],
};

このコードでは、ブロックの配列を含むメッセージオブジェクトを作成しています。各ブロックはSlackメッセージの特定のセクションを表し、それぞれ異なるタイプのコンテンツを持つことができます。

  1. sectionブロック:このブロックタイプは、テキストのセクションを表示するのに使用されます。セクションブロックであることを示すには、type: 'section'を使用します。セクションブロックの内部では、type: 'mrkdwn'とともにtextプロパティを使用して、テキストコンテンツがMarkdown形式として解釈されるように指定します。実際のテキストコンテンツはtextプロパティで提供し、テンプレートリテラルを使用して、req.body.displayNamedata.operation_idなど、フォームやサイト作成レスポンスからの動的な値を利用することができます。
  2. dividerブロック:このブロックタイプで、メッセージのセクションに区切り線を追加できます。これを利用するには、type: 'divider'を指定します。

このメッセージがIncoming Webhookを使用してSlackに送信されると、Slackチャンネルで視覚的に美しいメッセージを確認することができます。フォームに入力された動的な値(サイト名など)とサイト作成レスポンスからの情報が組み込まれ、高度にカスタマイズ(パーソナライズ)されたメッセージになります。

このメッセージを送信するには、fetch()の本文のオブジェクトを message変数の内容に置き換えます。

if (data.status === 202) {
    fetch(
        `https://hooks.slack.com/services/${process.env.SLACK_WEBHOOK_ID}`,
        {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify(message),
        }
    );
}
Slack block Kit BuilderでカスタマイズしたSlackメッセージ
Slack block Kit BuilderでカスタマイズしたSlackメッセージ

Kinsta APIでサイト作成操作を処理する

Slackに送信されるメッセージには、操作IDと表示名を持つリンクが付帯します。Operationsページの新しいルートを作成し、このデータを使用して操作状況を確認することができます。

Expressでは、reqを使ってURLパラメータにアクセス可能です。たとえば、オペレーションIDを取得するには、req.params.operationIdを使用します。

const KinstaAPIUrl = 'https://api.kinsta.com/v2';

app.get('/operation/:displayName/:operationId', (req, res) => {
    const checkOperation = async () => {
        const operationId = req.params.operationId;
        const resp = await fetch(`${KinstaAPIUrl}/operations/${operationId}`, {
            method: 'GET',
            headers: {
                Authorization: `Bearer ${process.env.REACT_APP_KINSTA_API_KEY}`,
            },
        });
        const data = await resp.json();
        res.render('pages/operation', {
            operationID: req.params.operationId,
            displayName: req.params.displayName,
            operationMessage: data.message,
        });
    };
    checkOperation();
});

上記のコードで、Slackのリンクをクリックすると、Kinsta APIにリクエストを行い、サイトの状況を確認できます。operation.ejsファイルの中身を編集し、動的データを追加します。

<div class="container-title">
    <h1 class="title">Check Site Operation Status</h1>
    <p>
        Check the status of your site tools operation via the id. Feel free to copy
        the ID and check in few seconds.
    </p>
</div>
<div class="form-container">
    <div class="input-div">
        <input class="form-control" value="<%= operationID %>" readOnly />
    </div>
    <button class="btn" type="submit" onclick="window.location.reload()">
        Refresh Operation Status
    </button>
</div>
<div class="services">
    <div class="details">
        <p><%= operationMessage %>..</p>
    </div>
</div>
<div class="services">
    <p class="description">
        If message above indicates that "Operation has successfully finished", use
        the links below to access your WP admin and the site itself.
    </p>
    <div class="details">
        <a
            href="http://<%= displayName %>.kinsta.cloud/wp-admin/"
            target="_blank"
            rel="noreferrer"
            class="detail-link"
        >
            <p>Open WordPress admin</p>
            <FiExternalLink />
        </a>
        <a
            href="http://<%= displayName %>.kinsta.cloud/"
            target="_blank"
            rel="noreferrer"
            class="detail-link"
        >
            <p>Open URL</p>
            <FiExternalLink />
        </a>
    </div>
</div>
オペレーションIDとサイトの情報を表示するページ
オペレーションIDとサイトの情報を表示するページ

最後にもう1点、サイト作成プロセス開始時に、リダイレクトメソッドを使って操作ページに移動することができます。

if (data.status === 202) {
    fetch(
        `https://hooks.slack.com/services/${process.env.SLACK_WEBHOOK_ID}`,
        {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify(message),
        }
    );
    res.redirect(`/operation/${req.body.displayName}/${data.operation_id}`);
}

このプロジェクトのソースコードは、こちらのGitHubリポジトリのメインブランチをご覧ください。

Node.jsアプリケーションをKinstaにデプロイする

このNode.jsアプリケーションをKinstaのアプリケーションホスティングプラットフォームに簡単にデプロイすることができます。コードをお好みのGitプロバイダ(BitbucketGitHub、またはGitLab)にプッシュするだけです。その後、以下の手順に従ってください。

  1. MyKinstaでKinstaアカウントにログイン
  2. サービスを追加」をクリック
  3. ドロップダウンメニューから「アプリケーション」を選択
  4. 表示されたモーダルで、デプロイしたいリポジトリを選択(複数のブランチがある場合は、希望のブランチを選択し、アプリケーションに名前を付ける)
  5. 利用するデータセンターを1つ選択(Kinstaのシステムにより自動でpackage.jsonからアプリの依存関係が検出され、インストール、ビルド、デプロイが行われる)

最後に注意点です。APIキーをGitサービスのようなパブリックホストにプッシュするのは安全ではありません。ホスティングの際には、.envファイルで指定したのと同じ変数名と値を使用して、環境変数として追加することができます。

デプロイ時にDevKinsta上で環境変数を設定する
デプロイ時にDevKinsta上で環境変数を設定する

アプリケーションのデプロイ操作を開始すると、通常は数分以内にビルドとデプロイが行われます。完了すると、アプリケーションのリンクが表示されます。これは「https://site-builder-nodejs-xvsph.kinsta.app」のようなものになります。

まとめ

今回の記事では、Incoming Webhooksを使用してNode.jsアプリケーションからSlackにメッセージを送信する方法と、Block Kit Builderを使用してSlackメッセージをカスタマイズする方法をご紹介しました。

SlackとKinsta APIの可能性は広大です。この記事はほんの始まりに過ぎません。各種ツールを統合することで、チームに十分な情報をもたらし、生産性を高めるシームレスなワークフローを構築することができます。

あなたはKinsta APIをどのように使っていますか?どのような機能の追加をご希望ですか?コメント欄でお聞かせください。

Joel Olawanle Kinsta

Kinstaでテクニカルエディターとして働くフロントエンド開発者。オープンソースをこよなく愛する講師でもあり、JavaScriptとそのフレームワークを中心に200件以上の技術記事を執筆している。