5
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Azure Static Web Apps のデータベース接続 (プレビュー) を試してみた - SQL Database

Last updated at Posted at 2023-05-03

はじめに

本記事で紹介する機能はプレビュー段階であるため、Microsoft Azure プレビューの追加使用条件 に同意した上で使用するようにしてください。

最近 (2023/03) Static Web Apps のデータベース接続という機能がパブリックプレビューになりました。
データベース接続機能を使用すると、バックエンド側のコードを一切書かずに、RESTやGraphQLのエンドポイントを通じて、データベースのテーブルやエンティティに対してCRUD操作や組み込みの認証を行うことができます。
Azure Cosmos DB, Azure SQL, Azure Database for MySQL, Azure Database for PostgreSQLなどのさまざまなデータベースタイプをサポートしています。
Data API Builder というエンジン (こちらもプレビュー版) が使われています。

前回は GraphQL を使用して Cosmos DB への接続を試してみたので、今回は REST を使用して Azure SQL Database への接続を試してみたいと思います。

まずはローカルで動かしてみる

公式のチュートリアルを参考に動かしてみます。

設定ファイル作成

Static Web Apps CLI を使用して DB 接続用の設定ファイルの雛形を作成できます。

  • データベースの種類
    • SQL Database
  • 接続文字列
    • @env('MSSQL_CONNECTION_STRING')
    • @env() は環境変数を参照
bash
npx swa db init \
    --database-type mssql \
    --connection-string "@env('MSSQL_CONNECTION_STRING')"

実行すると以下のフォルダーとファイルが出来上がります。
基本的には Cosmos DB の時と同じですが、GraphQL のスキーマ定義ファイルは作られませんでした。

swa-db-connections/
└── staticwebapp.database.config.json

staticwebapp.database.config.json

データベース接続の設定ファイルです。

staticwebapp.database.config.json (全体)
staticwebapp.database.config.json
{
  "$schema": "https://github.com/Azure/data-api-builder/releases/download/v0.6.14/dab.draft.schema.json",
  "data-source": {
    "database-type": "mssql",
    "options": {
      "set-session-context": false
    },
    "connection-string": "@env('MSSQL_CONNECTION_STRING')"
  },
  "runtime": {
    "rest": {
      "enabled": true,
      "path": "/rest"
    },
    "graphql": {
      "allow-introspection": true,
      "enabled": true,
      "path": "/graphql"
    },
    "host": {
      "mode": "production",
      "cors": {
        "origins": [],
        "allow-credentials": false
      },
      "authentication": {
        "provider": "StaticWebApps"
      }
    }
  },
  "entities": {}
}

ブロックごとに見ていきます。

まずはデータソースの設定ですね。
コマンドで指定した値が設定されています。

  "data-source": {
    "database-type": "mssql",
    "options": {
      "set-session-context": false
    },
    "connection-string": "@env('MSSQL_CONNECTION_STRING')"
  },

ランタイム (REST、GraphQL) 毎の設定です。
Cosmos DB (NoSQL) では GraphQL のみ対応でしたが、こちらは REST も有効となっています。

  "runtime": {
    "rest": {
      "enabled": true,
      "path": "/rest"
    },
    "graphql": {
      "allow-introspection": true,
      "enabled": true,
      "path": "/graphql"
    },

ホストの設定です。
Cosmos DB の時と同じです。
CORS や 認証プロバイダの設定があります。
認証プロバイダは Static Web Apps では StaticWebApps 固定になります。

    "host": {
      "mode": "production",
      "cors": {
        "origins": [],
        "allow-credentials": false
      },
      "authentication": {
        "provider": "StaticWebApps"
      }
    }

データベースのエンティティ (テーブル) の設定です。
最初は空です。

  "entities": {}

設定ファイル編集

staticwebapp.database.config.json

先ほど空だった entities を設定します。
設定内容は Cosmos DB の時と同様です。

  "entities": {
    "User": {
      "source": "users",
      "permissions": [
        {
          "role": "anonymous",
          "actions": ["*"]
        }
      ]
    }
  }

SQL Database の準備

SQL Server、SQL Database、テーブルを作成します。

  • SQL Server 名
    • <任意の名前>
  • データベース名
    • sqldb-swadb-demo
  • テーブル名
    • users

また、Static Web Apps からアクセスできるようにするため、SQL Server リソースのネットワーク設定で下記を設定します。

  • 選択したネットワーク (パブリックアクセス)
    • クライアントの IP アドレスを追加する
    • Azure サービスおよびリソースにこのサーバーへのアクセスを許可する

image.png

ローカル開発の場合は接続文字列が必要になるので、SQL データベースリソースの「接続文字列」メニューで「ADO.NET (SQL 認証)」の値を控えておきます。(Data API Builder は .NET で作られている)

az コマンドで作成する場合
bash
# リソースグループは作成されている前提

resourceGroup=<リソースグループ名>
sqlServerName=<SQL Server名>
sqldbName=sqldb-swadb-demo
adminUser=<管理者ユーザー名>
adminPassword=<管理者パスワード>
sqlServerFirewallName=sqlfw-swadb-demo

# クライアントのグローバルIPを取得
clientIp=$(curl inet-ip.info)

# SQL Server 作成
az sql server create \
    --name $sqlServerName \
    --resource-group $resourceGroup \
    --admin-user $adminUser \
    --admin-password $adminPassword

# ネットワーク設定
#   ローカルクライアントからのアクセスを許可
az sql server firewall-rule create \
    --name $sqlServerFirewallName \
    --resource-group $resourceGroup \
    --server $sqlServerName \
    --start-ip-address $clientIp \
    --end-ip-address $clientIp

#   Azureサービスからアクセスを許可
az sql server firewall-rule create \
    --name $sqlServerFirewallName \
    --resource-group $resourceGroup \
    --server $sqlServerName \
    --start-ip-address 0.0.0.0 \
    --end-ip-address 0.0.0.0

# SQL Database 作成
az sql db create \
    --name $sqldbName \
    --resource-group $resourceGroup \
    --server $sqlServerName \
    --compute-model Provisioned \
    --edition Basic \
    --capacity 5

# SQL Database の接続文字列取得
sqlDatabaseConnectionString=$(
    az sql db show-connection-string \
        --client ado.net \
        --name $sqldbName \
        --server $sqlServerName \
        --outpu tsv
)

echo 
echo SQL Database Connection String:
echo "    $sqlDatabaseConnectionString"

最後に出力される SQL Database Connection String の値を控えておきます。
※テーブルはコマンドで作成できないので、お好みのクライアントアプリ、またはポータル上から作成してください

環境変数の設定

上記で取得した Cosmos DB の接続文字列を環境変数に設定します。
(プロジェクトルートに配置した .env ファイルに記載しておいても読み込んでくれました)

bash
export MSSQL_CONNECTION_STRING="Server=tcp:<SQL Server名>.database.windows.net,1433;Initial Catalog=sqldb-swadb-demo;Persist Security Info=False;User ID=<adminユーザー名>;Password=<adminパスワード>;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;""

アプリの起動

--data-api-location オプションを指定してコマンドを実行します。

npx swa start --data-api-location swa-db-connections

動作確認

CRUD のリクエストを投げてレスポンスを表示するだけのアプリを作って確認します。
公開される REST エンドポイントには基本的な CRUD 操作が含まれています。
/data-api/rest/<エンティティ名>[/<プライマリキー名>][/プライマリキー値]

No. メソッド エンドポイント 概要
1 GET /data-api/rest/User 一覧取得
2 GET /data-api/rest/User/id/{id} プライマリキーを指定して取得
3 POST /data-api/rest/User 作成
4 PUT /data-api/rest/User/id/{id} 更新
5 DELETE /data-api/rest/User/id/{id} 削除

DB の初期状態

image.png

一覧取得

GET /data-api/rest/User にリクエストします。

リクエスト
/** 一覧 */
const listUsers: SendRequest<ListParams<User>> = async () => {
  return sendRequest('GET', '/data-api/rest/User')
}
実行結果

image.png

また、フィルターでの絞り込みや、ソートの指定も可能となっています。

実行結果 ※上記ソースコードから変更しています

image.png

更にページングにも対応しています。
パラメータに $first=n を指定すると、最初の n 件を取得し、残りのデータがある場合はレスポンスに nextLink プロパティが含まれます。
nextLink が次のページデータを取得するための REST エンドポイントとなっています。

実行結果

image.png

[2023/05/03 現在]
この nextLink ですが、現在 バグ があり、URLに /data-api が含まれていないため、Static Web Apps ではそのままリクエストすると 404 となってしまいます。

誤:"nextLink": "http://localhost:4280/rest/User?xxxxxxx..."
正:"nextLink": "http://localhost:4280/data-api/rest/User?xxxxxxx..."

なので、現状は自分でURLを加工してリクエストする必要があります。

const nextLink = new URL(response.nextLink)
// nextLink.origin ではなく location.origin にしているのは
// Azure にデプロイしたときも何故か nextLink のプロトコルが http のままだから
const nextLinkSwa = `${location.origin}/data-api${nextLink.pathname}${nextLink.search}`

指定できるパラメータ

指定できるパラメータは以下ドキュメントを参照。

プライマリキーを指定して取得

GET /data-api/rest/User/id/{id} にリクエストします。

リクエスト
/** キーを指定して取得 */
const getUser: SendRequest<Pick<User, 'id'>> = async ({ id }) => {
  return sendRequest('GET', `/data-api/rest/User/id/${id}`)
}
実行結果

image.png

登録

POST /data-api/rest/User にリクエストします。

リクエスト
/** 登録 */
const createUser: SendRequest<User> = async (user) => {
  return sendRequest('POST', '/data-api/rest/User', user)
}
実行結果

image.png
image.png

更新

PUT /data-api/rest/User/id/{id} にリクエストします。

リクエスト
/** 更新 */
const updateUser: SendRequest<User> = async ({ id, name, age }) => {
  return sendRequest('PUT', `/data-api/rest/User/id/${id}`, { name, age })
}
実行結果

image.png
image.png

削除

DELETE /data-api/rest/User/id/{id} にリクエストします。

リクエスト
/** 削除 */
const deleteUser: SendRequest<Pick<User, 'id'>> = async ({ id }) => {
  return sendRequest('DELETE', `/data-api/rest/User/id/${id}`)
}
実行結果

image.png
image.png

アクセス制御の確認

Cosmos DB (GraphQL) と同様にテーブルレベルでロールベースのアクセス制御を設定することができます。
更に Cosmos DB 以外の DB では、フィールドレベルでの制御も設定することができます。
ここではフィールドレベルでの制御を試してみたいと思います。

設定ファイル編集

staticwebapp.database.config.json を編集します。

staticwebapp.database.config.json
  "entities": {
    "User": {
      "source": "dbo.users",
      "permissions": [
        {
          "role": "anonymous",
-         "actions": ["*"]
+         "actions": [
+           "create",
+           "update",
+           "delete",
+           {
+             "action": "read",
+             "fields": {
+               "exclude": ["age"]
+             }
+           }
+         ]
        }
      ]
    }
  }

createupdatedelete は全てのフィールドにアクセス可能
readage フィールドのみアクセス不可
としました。

制限されたフィールドにアクセスしない場合

データ取得時に $select パラメータを指定すると取得するフィールドを指定できるので、age 以外を取得してみます。
image.png
アクセスできてますね。

制限されたフィールドにアクセスしようとした場合

今度は age も含めて取得しようとしてみます。
image.png
エラーになりました!ちゃんと制御できてますね🐤

リレーションの設定

Cosmos DB 以外のリレーショナルデータベースでは、テーブル間のリレーションも設定できます。
ただし、GraphQL 限定の機能となっているので、ここだけ GraphQL で検証します。
(もしかして Cosmos DB でもできる...?今度検証してみます → できませんでした・・・!)

テーブル構成変更

組織テーブルを追加して、1(組織):n(ユーザー) のリレーションを設定することにします。
organizationId は FK と書きましたが、実際には外部キー制約は設定していません

スキーマ定義作成

Cosmos DB の時と同様、staticwebapp.database.schema.gql を作成します。

staticwebapp.database.schema.gql
type User @model {
  id: ID!
  name: String!
  age: Int!
  organizationId: String
}

type Organization @model {
  id: ID!
  name: String!
  users: UserConnection!
}

設定ファイル編集

staticwebapp.database.config.json の entities にリレーションの設定を追加します。

staticwebapp.database.config.json
  "entities": {
    "User": {
      ・・・
    },
+   "Organization": {
+     "source": "dbo.organizations",
+     "permissions": [
+       {
+         "role": "anonymous",
+         "actions": ["*"]
+       }
+     ],
+     "relationships": {
+       "users": {
+         "source.fields": ["id"],
+         "target.entity": "User",
+         "target.fields": ["organizationId"],
+         "cardinality": "many"
+       }
+     }
    }
  }

source.fields は親テーブル (organizations) のキーとなるフィールド名
target.entity は子テーブル (users) のエンティティ名 (この設定ファイル上の名前)
target.fields は子テーブルが持つ外部キーのフィールド名
cardinality は子テーブルの関係。(1:1、1:n、n:n)

動作確認

データの状態

組織
image.png
ユーザー
image.png

リクエスト

こんな感じのクエリをリクエストします。

query Organizations {
    organizations {
        items {
            id
            name
            users {
                items {
                    id
                    name
                    age
                }
            }
        }
    }
}

レスポンス

組織とユーザーが紐づいたレスポンスが返ってきます!

{
  "data": {
    "organizations": {
      "items": [
        {
          "id": "org1",
          "name": "Organization001",
          "users": {
            "items": [
              {
                "id": "user1",
                "name": "Name001",
                "age": 20
              },
              {
                "id": "user3",
                "name": "Name333",
                "age": 5
              }
            ]
          }
        },
        {
          "id": "org2",
          "name": "Organization002",
          "users": {
            "items": [
              {
                "id": "user2",
                "name": "Name002",
                "age": 30
              }
            ]
          }
        }
      ]
    }
  }
}

Azure へのデプロイ

デプロイ

ローカルで動かすことができたので、Azure 環境にデプロイしてみます。
Cosmos DB の時と同様、Static Web Apps CLI でデプロイします。
デプロイ方法は以下の記事を参考にしてください。

SQL Database のリンク

デプロイできたら Cosmos DB を Static Web Apps にリンクさせます。
Static Web Apps のリソース画面で データベース接続(プレビュー) > 既存のデータベースのリンク を選択。
image.png

先ほど作成した SQL Database の情報を入力してリンクします。
認証の種類は接続文字列の他にマネージドIDが選べます。
ここでは接続文字列を選択しました。(というか Free プランだと接続文字列しか選択できない)

マネージドIDを選ぶには、Static Web Apps のホスティングプランを「Standard」に設定して、IDを有効にする必要があります

image.png

az コマンドで設定する場合
bash
# Static Web Apps のリソースが作成済みの前提

resourceGroup=<リソースグループ名>
swaName=<Static Web Appsリソース名>
sqlServerName=<SQL Server名>
sqldbName=sqldb-swadb-demo
adminUser=<adminユーザー名>
adminPassword=<adminパスワード>

# SQL Server の ID 取得
sqlServerId=$(
    az sql server show \
        --name $sqlServerName \
        --resource-group $resourceGroup \
        --query id \
        --output tsv
)

# データベース接続設定
echo
echo Creating Database Connection to $sqlServerId ...
az staticwebapp dbconnection create \
    --db-resource-id "$sqlServerId" \
    --db-name $sqldbName \
    --name $swaName \
    --resource-group $resourceGroup \
    --username $adminUser \
    --password $adminPassword

まとめ

バックエンドのコードを一切書かずに REST による DB 操作ができました。
データベース接続機能を使用すれば、背後にある DB が変わったとしてもソースコードはそのままで、設定ファイルの修正のみで済むため、アーキテクチャが固まっていない状態でも柔軟に対応できそうです。

実際のアプリケーションでは、もっと複雑な検索が必要だったり、複数のデータを一括更新したりする場面も当然あると思います。そういった複雑な部分は バックエンドを実装し、単純な CRUD はデータベース接続を使う というように使い分けていけば、実装を最小限に抑えられるかもしれませんね😊

5
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?