🌾

【AppSheet】REST APIの作成方法

2024/08/10に公開

Google workspace系でアプリケーションを作成するには、これまで主にスプレッドシートをDB代わりに用いてきましたが、GASで自作クエリを作成するのはいろいろ面倒ですし、データの取得や書き込みが非常に遅いです。

今回ふと思い立ってAppSheetをDBとして使ったところ、実装の手間もそれほどかからず動作も早かったので、非常に便利だということがわかりました。

公式ドキュメントはこちら
https://support.google.com/appsheet/topic/10105767?hl=en&ref_topic=10101404&sjid=4235716407689701435-AP

ですが、公式ドキュメントはなかなかわかりづらいので、メモとして残します。

DBの作成

1. 注文履歴

2. 注文アイテム

3. 備品マスタ

必ずやっておくこと
AppSheetエディタで、各テーブルをDataに加えておく必要があります。
(これをやらないと、REST APIを実行しても何も書き込めず返されません。)

REST APIの作成

共通

setting > integrationsでApp Idを確認することができます。
また、Access Keyを発行することができます。

これらは以下のREST APIで使うことになります。

const APP_ID = "7b6b4413-ec3a-4d5d-b9d0-*******"
const ACCESS_KEY = "V2-************************"

CREATE

payloadのActionは"Add"を指定します。

function create注文アイテム() {
  const TABLE_NAME = "注文アイテム"
  
  const url = `https://www.appsheet.com/api/v2/apps/${APP_ID}/tables/${TABLE_NAME}/Action?applicationAccessKey=${ACCESS_KEY}`

  const rows = [
    {"アイテムコード":'item0002',"備品コード":'ballpen',"備品名":'黒色ボールペン',"金額":2000}
  ]

  const payload = {
    "Action":"Add",
    "Properties":{
      "Locale":"ja-JP"
    },
    "Rows":rows
  }

  const options = {
    "method":"post",
    "contentType":"application/json",
    "payload":JSON.stringify(payload)
  }

  UrlFetchApp.fetch(url,options)

}

READ

payloadのActionは"Find"を指定します。
selectorに色々な検索式を入れることができます。
下の例では、「備品マスタ」テーブルの「格納場所」列に「実験室」が入っているレコードだけを取り出しています。

function get備品マスタ(){
  const TABLE_NAME = "備品マスタ"
  const url = `https://www.appsheet.com/api/v2/apps/${APP_ID}/tables/${TABLE_NAME}/Action?applicationAccessKey=${ACCESS_KEY}`

  const payload = {
    "Action":"Find",
    "Properties":{
      "Locale":"ja-JP",
      "Selector": "FILTER('備品マスタ', IN('実験室', [格納場所]))"
    }
  }

  const options = {
    "method":"post",
    "contentType":"application/json",
    "payload":JSON.stringify(payload)
  }

  try {
    const res = UrlFetchApp.fetch(url, options);
    const responseText = res.getContentText();
    console.log("Response Data: ", responseText);
  } catch (error) {
    console.error("Error: " + error.toString());
  }
}

UPDATE

更新は一レコードずつではなく、まとめて更新します(以下の例ではrowsに入れています)

「注文アイテム」テーブルでは、「アイテムコード」がkeyとなっています。
更新すべきレコードは「アイテムコード」を指定していますので、APIはそのアイテムコードを手掛かりに該当するレコードを更新します。

function update注文アイテム(){
  const TABLE_NAME = "注文アイテム"
  
  const url = `https://api.appsheet.com/api/v2/apps/${APP_ID}/tables/${TABLE_NAME}/Action?applicationAccessKey=${ACCESS_KEY}`

  const rows = [
    {"アイテムコード":'item0003',"備品コード":'ballpen',"備品名":'黒色ボールペン',"金額":3000}
  ]

  const payload = {
    "Action":"Edit",
    "Properties":{
      "Locale":"ja-JP"
    },
    "Rows":rows
  }

  const options = {
    "method":"post",
    "contentType":"application/json",
    "payload":JSON.stringify(payload)
  }

  UrlFetchApp.fetch(url,options)
}

Nodejs

クリックで展開
<!DOCTYPE html>
    <html>
        <head>

            <script>
                const TABLE_NAME = "Table 1"
                const APP_ID = '203e9cf7-d9c0-49a5-a1ca-6849094bb282'
                const ACCESS_KEY = 'V2-AZWbc-ELpfC-x6lCG-LB1y0-96p3q-la2gT-cYKyO-BMvPv'             
                const url = `https://www.appsheet.com/api/v2/apps/${APP_ID}/tables/${TABLE_NAME}/Action?applicationAccessKey=${ACCESS_KEY}`;

                async function getData() {
                    const payload = {
                        Action: "Find",
                        Properties: {
                            Locale: "ja-JP",
                            // Selectorを省略すると、全データを取得する
                            // Selector:"SELECT(備品マスタ[備品コード],TRUE)"  // 備品コードが何で合っても取得する(つまり全データが取得される)
                            // Selector:"FILTER('備品マスタ',[備品コード] = 'ballpen')" // '備品コード'が'ballpen'のレコード
                            //Selector:"FILTER('備品マスタ',[備品コード] = 'ballpen')" // '備品コード'が'ballpen'のレコード
                            // Selector: "FILTER('備品マスタ', IN('実験室', [格納場所]))"  // EnumListの'格納場所'に'実験室'が含まれるレコード
                        }
                    };

                    const options = {
                        method: 'POST',
                        headers: {'Content-Type': 'application/json'},
                        body: JSON.stringify(payload)
                    };

                    try {
                        const response = await fetch(url, options);
                        if (!response.ok) {
                            throw new Error(`HTTP error! status: ${response.status}`);
                        }
                        return await response.text();
                        // return await response.json()
                    } catch (error) {
                        console.error("Error: " + error.message);
                        return error.message
                    }
                }

                async function  addData() {
                    const payload = {
                        Action: "Add",
                        Properties: {
                            Locale: "ja-JP",
                        },
                        Rows:[
                            {
                                category:'保健体育'
                                // 備品コード:"note",
                                // 備品名:"ノート",
                                // 格納場所:"居室 , 実験室",
                                // 価格:140,
                                // 発注単位:10,
                                // 申込番号:"mono-4",
                                // 発注先名:"モノタロウ",
                                // JANコード:4987072088980
                            }
                        ]
                    };

                    const options = {
                        method: 'POST',
                        headers: {'Content-Type': 'application/json'},
                        body: JSON.stringify(payload)
                    };

                    try {
                        const response = await fetch(url, options);
                        if (!response.ok) {
                            throw new Error(`HTTP error! status: ${response.status}`);
                        }
                        return await response.text();
                        // return await response.json()
                    } catch (error) {
                        console.error("Error: " + error);
                        return error.message
                    }
                }

                async function  updateData() {
                    const payload = {
                        Action: "Edit",
                        Properties: {
                            Locale: "ja-JP",
                        },
                        Rows:[
                            {
                                'Row ID':"ZOiLnf4HQz45yF_W686ZT1",
                                fileName:'aaa'
                            }
                        ]
                    };

                    const options = {
                        method: 'POST',
                        headers: {'Content-Type': 'application/json'},
                        body: JSON.stringify(payload)
                    };

                    try {
                        const response = await fetch(url, options);
                        if (!response.ok) {
                            throw new Error(`HTTP error! status: ${response.status}`);
                        }
                        return await response.text();
                        // return await response.json()
                    } catch (error) {
                        console.error("Error: " + error);
                        return error.message
                    }
                }

                async function  deleteData() {
                    const payload = {
                        Action: "Delete",
                        Properties: {
                            Locale: "ja-JP",
                        },
                        Rows:[
                            {
                                備品コード:"note",
                            }
                        ]
                    };

                    const options = {
                        method: 'POST',
                        headers: {'Content-Type': 'application/json'},
                        body: JSON.stringify(payload)
                    };

                    try {
                        const response = await fetch(url, options);
                        if (!response.ok) {
                            throw new Error(`HTTP error! status: ${response.status}`);
                        }
                        return await response.text();
                        // return await response.json()
                    } catch (error) {
                        console.error("Error: " + error);
                        return error.message
                    }
                }

                const doFunction=()=>{
                    console.time('test')
                    // getData()
                    //     .then(res=>{
                    //         const dataDisplayElem = document.getElementById("data-display")
                    //         dataDisplayElem.innerText = res
                    //         console.timeEnd('test')
                    //     })
                    //     .catch(err=>{
                    //         const dataDisplayElem = document.getElementById("data-display")
                    //         dataDisplayElem.innerText = err
                    //     })

                    // addData()
                    //     .then(res=>{
                    //         const dataDisplayElem = document.getElementById("data-display")
                    //         dataDisplayElem.innerText = res
                    //     })
                    //     .catch(err=>{
                    //         const dataDisplayElem = document.getElementById("data-display")
                    //         dataDisplayElem.innerText = err
                    //     })

                    updateData()
                        .then(res=>{
                            const dataDisplayElem = document.getElementById("data-display")
                            dataDisplayElem.innerText = res
                        })
                        .catch(err=>{
                            const dataDisplayElem = document.getElementById("data-display")
                            dataDisplayElem.innerText = err
                        })

                    // deleteData()
                    //     .then(res=>{
                    //         const dataDisplayElem = document.getElementById("data-display")
                    //         dataDisplayElem.innerText = res
                    //     })
                    //     .catch(err=>{
                    //         const dataDisplayElem = document.getElementById("data-display")
                    //         dataDisplayElem.innerText = err
                    //     })
                    
                }
            </script>
        </head>
        <body>
            <button type="button" onclick=doFunction()>テスト</button>

            <div id="data-display">
                ここに取得したデータが表示されます
            </div>
        </body>
    </html>
export default (APP_ID:string, ACCESS_KEY:string, TABLE_NAME:string)=>{

    const request = async(payload)=>{
        const url = `https://www.appsheet.com/api/v2/apps/${APP_ID}/tables/${TABLE_NAME}/Action?applicationAccessKey=${ACCESS_KEY}`;

        const options = {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify(payload)
        };

        try {
            const response = await fetch(url, options);
            if (!response.ok) {
                throw new Error(`HTTP error! status: ${response.status}`);
            }
            return await response.text();
            // return await response.json()
        } catch (error) {
            console.error("Error: " + error.message);
            return error.message
        }
    }

    type Operator = "=="|">="|"<="

    const getRows=(fieldName:string, operator:Operator, parameter:string)=>{

        const payload = {
            Action: "Find",
            Properties: {
                Locale: "ja-JP",
                // Selectorを省略すると、全データを取得する
                // Selector:"SELECT(備品マスタ[備品コード],TRUE)"  // 備品コードが何で合っても取得する(つまり全データが取得される)
                // Selector:"FILTER('備品マスタ',[備品コード] = 'ballpen')" // '備品コード'が'ballpen'のレコード
                Selector:"FILTER('備品マスタ',[備品コード] = 'ballpen')" // '備品コード'が'ballpen'のレコード
                // Selector: "FILTER('備品マスタ', IN('実験室', [格納場所]))"  // EnumListの'格納場所'に'実験室'が含まれるレコード
            }
        };

        var selector;

        switch(operator){
            case "==":{
                selector = `SELECT(${TABLE_NAME})`
            }
        }

        return request(payload)
    }

    const addRows=()=>{
        const payload = {
            Action: "Add",
            Properties: {
                Locale: "ja-JP",
            },
            Rows:[
                {
                    備品コード:"note",
                    備品名:"ノート",
                    格納場所:"居室 , 実験室",
                    価格:140,
                    発注単位:10,
                    申込番号:"mono-4",
                    発注先名:"モノタロウ",
                    JANコード:4987072088980
                }
            ]
        };

        return request(payload)
    }

    const updateRows=(rows:object[])=>{
        const payload = {
            Action: "Edit",
            Properties: {
                Locale: "ja-JP",
            },
            Rows:rows
        };

        return request(payload)
    }

    const deleteRows=(rows:object[])=>{
        const payload = {
            Action: "Delete",
            Properties: {
                Locale: "ja-JP",
            },
            Rows:rows
        };

        return request(payload)
    }

    return {getRows, addRows, updateRows, deleteRows}
}


Discussion