ノーコードAPI連携ツールn8nのノードをTypescriptで開発してみた

こんにちは、AI・IoTサービス開発部の濱村です。

前回はKubernetesについて投稿させていただきました。もし興味がありましたらこちらも見ていただけますと幸いです。

tech-blog.optim.co.jp

目次

はじめに

皆様はGitLabやRedmineからSlackへ通知を飛ばしたいときのようにAPIでサービスを連携したい場合はどのように連携しておりますでしょうか。

私はデバイスや外部のIoTプラットフォームをOPTiM Cloud IoT OS(以下CIOS)にAPIで連携する際、当初はデータを取得・加工して送信するようなプログラムを実装し連携しておりました。

その作成したプログラムの認証情報が間違っていたりAPIのスキーマが間違っていたりでトライ&エラーを繰り返し、毎回ビルドし直して実装をしていました。

実際に連携しようとすると私のようにAPIの仕様や、認証情報で手こずったりすることが多いのではないかと思います。

そんなときにn8nというツールがチームに導入され、n8nでAPI連携をすればプログラムを実装・ビルドする手間がなくなり、認証情報等で手こずることが少なくなりました。

n8nとは

n8nというサービスはMicrosoftのPower AutomateやIFTTTのようなサービスとサービスを連携する便利なツールです。

n8n.io

TriggerとNodeで構成されている一連の流れをWorkflowと呼び、「TriggerによってWorkflowが発火→Nodeが実行される」というような流れになっています。

具体的な活用事例になりますが、WebhookへのリクエストをトリガーにEmailを送信してくれるようなWorkflowになっております。

それぞれのNodeが必要とする入力情報は、認証情報と送信先の情報、内容のテキストくらいしかなく、それ以外の通信の部分などは勝手にやってくれます。

f:id:optim-tech:20220225174923p:plain

利用のしかた

利用の仕方についてですが、今回はソースコードを実装して独自のノードを作ることができるという利点から、セルフホストで利用しております。

セルフホストの仕方は下のリンクにありますのでご参考になれば幸いです。

https://docs.n8n.io/getting-started/installation/#self-hosting-n8n

実際にやってみたこと

CIOSからすでに登録してあるデバイスの情報を取得できるように、検証レベルではありますが簡単に実装してみました。

開発したノードの入力に認証情報とCIOSのDeviceIDを与えるとそのデバイスの情報が取得できるようなものになっております。

実装したコード

import { IExecuteFunctions } from "n8n-core";
import {
    INodeExecutionData,
    INodeType,
    INodeTypeDescription,
    JsonObject,
    NodeApiError,
} from "n8n-workflow";
export class CiosCollection implements INodeType {

    // Nodeの設定を書く部分
    description: INodeTypeDescription = {
        displayName: "CIOS Collection",
        name: "ciosCollection",
        icon: "file:CIOS.svg",
        group: ["transform"],
        version: 1,
        description: "Node converts input data to chocolate",
        defaults: {
            name: "CIOS Collection",
            color: "#772244",
        },
        inputs: ["main"],
        outputs: ["main"],
        credentials: [  // Nodeで使用する認証情報
            {
                name: "ciosOAuth2",
                required: true,
                displayOptions: {
                    show: {
                        authentication: ["oAuth2"],
                    },
                },
            },
        ],
        properties: [  // ユーザーからの入力を実装する部分
            {
                displayName: "Authentication",
                name: "authentication",
                type: "options",
                options: [
                    {
                        name: "OAuth2",
                        value: "oAuth2",
                    },
                ],
                default: "oAuth2",
            },
            {
                displayName: "Device ID",
                name: "deviceID",
                type: "string",
                default: "",
            },
        ],
    };

    // ロジックを実装する部分
    async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
        const items = this.getInputData();

        // ユーザーからの入力を取得
        const deviceID = this.getNodeParameter("deviceID", 0) as string;
        // 認証情報を取得
        const credential = (await this.getCredentials("ciosOAuth2")) as {
            url: string;
            accessToken: string;
        };
        if (credential === undefined) {
            const error = { message: "credential error" };
            throw new NodeApiError(this.getNode(), error as JsonObject);
        }

        // リクエストを作成
        const options = {
            headers: {
                "Content-Type": "application/json",
            },
            method: "GET",
            body: {},
            qs: {},
            uri: `https://device-management.preapis.cios.dev/v2/devices/${deviceID}`,
            json: true,
        };

        // リクエストを送信
        try {
            items[0].json.response = await this.helpers.requestOAuth2.call(
                this,
                "ciosOAuth2",
                options
            );
        } catch (error) {
            throw new NodeApiError(this.getNode(), error as JsonObject);
        }

        return this.prepareOutputData(items);
    }
}

ビルド・実行結果

実装したノードにデータを入力する

右上のプラスボタンから使用するノードを検索、選択するとモーダルが開きますので、ノードに必要な情報を入力します。

今回は通信に必要な認証情報とDeviceIDをフィールドに入力します。

※ 下のデモでは認証情報をプルダウンで選択していますが、事前にOAuth2認証の情報を入力する必要があります。

f:id:optim-tech:20220202161700g:plain

ノードを実行して結果を取得する

StartノードとCIOSノードを繋げます。

画面下にあるExecute Workflowボタンを押してStartノードをトリガーします。

StartノードにつながっているCIOSノードが実行されてレスポンスが返ってきます。

f:id:optim-tech:20220202161718g:plain

ノードのUI実装

description: INodeTypeDescription

ノードのアイコンや表示名などの見た目や、ユーザーからの入力可能なフィールドはこの変数で変更することができます。

一例にはなりますが、下のようなobjectをpropertiesの配列に追加することでユーザーが入力できるフィールドを増やすことができます。

properties: [  // ユーザーからの入力を実装する部分
            {
                displayName: "Authentication",
                name: "authentication",
                type: "options",
                options: [
                    {
                        name: "OAuth2",
                        value: "oAuth2",
                    },
                ],
                default: "oAuth2",
            },
            {
                displayName: "Device ID",
                name: "deviceID",
                type: "string",
                default: "",
            },
+           {
+               displayName: "Device Number",
+               name: "deviceNumber",
+               type: "number",
+               default: 0,
+           },
        ],

再度ビルドしてサーバーを立て直すと下のようにフィールドが追加されます。

f:id:optim-tech:20220202161746p:plain

ノードのロジック実装

async execute()

ノードを実行した際に処理が走る関数になっているためこの関数の中にロジックを実装します。

this.getNodeParameter("deviceID", 0)

ユーザーがノードに入力したフィールドの値を取得することができます。

今回はユーザーが入力したDeviceIDを取得しています。

this.getCredentials("ciosOAuth2")

ユーザーがノードに入力した認証情報を取得することができます。

引数にciosOAuth2を指定していますが、こちらはdescription: INodeTypeDescriptionの中にあるcredentialsnameと紐づいています。

credentials: [  // Nodeで使用する認証情報
            {
                name: "ciosOAuth2",
                required: true,
                displayOptions: {
                    show: {
                        authentication: ["oAuth2"],
                    },
                },
            },

※ 次に紹介するリクエストを送信する関数の中でもgetCredentials()を呼んでいるのであらかじめgetCredentials()を実行する必要はありません。認証情報が取得できているかどうかの判定に使用しているだけです。

this.helpers.requestOAuth2.call(this, "ciosOAuth2", options)

最後に本題のリクエストを送信する関数です。

第1引数のthisは必須パラメータとなっておりましたので関数に与えます。

第2引数のciosOAuth2getCredential()の引数と同じようにdescription: INodeTypeDescriptionの中にあるcredentialsnameを文字列で与えると勝手に認証情報を取得してリクエストに含めてくれます。

第3引数のoptionsにはエンドポイントの情報などを入れて渡します。

上記3つの引数のみでOAuth2認証を突破してリクエストを実行してくれるようになっております。

わかったこと

ほとんどノーコードで接続できる良さ

n8nの公式ページにあるWorkflowのサンプルを見ていただくとわかりますが、SlackやGitlab、AWSなど様々なサービスと簡単に連携できるノードが初めから実装されており、簡単にAPI連携できるというメリットを存分に感じることができました。

連携したいサービスのノードが実装されていない場合でもjavascriptがわかる人であればFunctionNodeというJavascriptを実行できるノードがありますので簡単な処理でしたらコードを少し書くだけで連携することができました。

Workflowのサンプル

デバッグがつらいこと

実装をしてみてわかったのですが、普通の実装と違ってエラーが出たときにサーバーやコンソールに出力されずに下のようにNodeのUIで表示されます。

それもエラーハンドリングでNodeのUIで表示するようにしなければundefinedや空のオブジェクトで返ってくることが多々ありました。

どこでエラーが出ているのか、そもそもエラーなのかどうかすらわからないためデバッグでつらい思いをしました。

f:id:optim-tech:20220202161740p:plain

パッケージのバージョンを確認すること

パッケージのバージョンが古く、this.helpers.requestOAuth2.call()などの関数がなく詰まってしまうことがありました。

パッケージを利用するときはいつでもですが、改めてバージョンの確認の重要さを認識しました。

おわりに

今回はマイナーではありますが、n8nというサービスの紹介とノードの実装について執筆させていただきました。

n8nを用いたIoTやAPI連携について少しでも興味を持っていただけましたら幸いです!


OPTiMでは技術領域に関わらず、新しい技術に挑戦してみたいというエンジニアを募集しております!

www.optim.co.jp