Share via


Node.js と mssql npm パッケージを使用して Azure SQL Database に接続し、クエリを実行する

適用対象:Azure SQL Database

このクイックスタートでは、Node.js と mssql を使用して Azure SQL Database 内のデータベースにアプリケーションを接続し、クエリを実行する方法について説明します。 このクイックスタートでは、データベースに接続するための推奨されるパスワードレス アプローチに従います。

開発者向けのパスワードレス接続

パスワードレス接続は、Azure リソースにアクセスするためのより安全なメカニズムを提供します。 この記事では、パスワードレス接続を使って Azure SQL Database に接続するために次の大まかな手順を使用します。

  • パスワードなしの認証用に環境を準備します。
    • ローカル環境の場合: 個人 ID が使用されます。 この ID は、IDE、CLI、またはその他のローカル開発ツールから取得できます。
    • クラウド環境の場合: マネージド ID が使用されます。
  • 検証済みの資格情報を取得するには、Azure ID ライブラリから DefaultAzureCredential を使用して認証を行います。
  • 検証済みの資格情報を使用して、リソース アクセス用の Azure SDK クライアント オブジェクトを作成します。

パスワードレス接続について詳しくは、パスワードレス ハブに関する記事を参照してください。

前提条件

データベース サーバーを構成する

Azure SQL Database への安全なパスワードレス接続には、特定のデータベース構成が必要です。 Azure の論理サーバーで次の設定を確認し、ローカル環境とホスト環境の両方で Azure SQL Database に適切に接続します。

  1. ローカル開発の接続の場合は、ローカル コンピューターの IP アドレスやその他の Azure サービスで接続できるように論理サーバーが構成されていることを確認します。

    • お使いのサーバーの [ネットワーク] ページに移動します。

    • [選択されたネットワーク] ラジオ ボタンを切り替えて、追加の構成オプションを表示します。

    • [Add your client IPv4 address(xx.xx.xx.xx)] (クライアント IPv4 アドレスの追加 (xx.xx.xx.xx)) を選び、ローカル コンピューターの IPv4 アドレスからの接続を有効にするファイアウォール規則を追加します。 または、[+ Add a firewall rule] (ファイアウォール規則の追加) を選び、選んだ特定の IP アドレスを入力することもできます。

    • [Azure サービスおよびリソースにこのサーバーへのアクセスを許可する] チェックボックスがオンになっていることを確認します。

      ファイアウォール規則を構成する方法を示すスクリーンショット。

      警告

      [Azure サービスおよびリソースにこのサーバーへのアクセスを許可する] 設定を有効にすることは、運用環境のシナリオでは推奨されるセキュリティ プラクティスではありません。 実際のアプリケーションでは、より強力なファイアウォール制限や仮想ネットワーク構成など、より安全なアプローチを実装する必要があります。

      データベース セキュリティの構成について詳しくは、次のリソースを参照してください。

  2. また、サーバーでは Microsoft Entra 認証が有効になっており、Microsoft Entra 管理者アカウントが割り当てられている必要があります。 ローカル開発接続の場合、Microsoft Entra 管理者アカウントは、ローカルで Visual Studio または Azure CLI にログインできるアカウントである必要があります。 論理サーバーの Microsoft Entra ID ページで、サーバーで Microsoft Entra 認証が有効になっているかどうかを確認できます。

    Microsoft Entra 認証を有効にする方法を示すスクリーンショット。

  3. 個人の Azure アカウントを使用している場合は、アカウントをサーバー管理者として割り当てるために、Microsoft Entra がセットアップされ、Azure SQL Database 用に構成されていることを確認してください。企業アカウントを使用している場合は、Microsoft Entra ID がおそらくすでに構成済みになっています。

プロジェクトを作成する

このセクションの手順では、Node.js REST API を作成します。

  1. プロジェクト用の新しいディレクトリを作成し、そこに移動します。

  2. ターミナルで次のコマンドを実行してプロジェクトを初期化します。

    npm init -y
    
  3. この記事のサンプル コードで使用する必要があるパッケージをインストールします。

    npm install mssql swagger-ui-express yamljs
    
  4. この記事のサンプル コードで使用する開発パッケージをインストールします。

    npm install --save-dev dotenv 
    
  5. Visual Studio Code でプロジェクトを開きます。

    code .
    
  6. package.json ファイルを開き、name プロパティの後に次のプロパティと値を追加して、ESM モジュール用のプロジェクトを構成します。

    "type": "module",
    

Express.js アプリケーション コードを作成する

Express.js OpenAPI を作成するには、いくつかのファイルを作成します。

ファイル 説明
.env.development ローカルの開発専用環境ファイル。
index.js メイン アプリケーション ファイル。これは、ポート 3000 で Express.js アプリを起動します。
person.js CRUD 操作を処理する Express.js /person ルート API ファイル。
openapi.js OpenAPI エクスプローラー UI の Express.js /api-docs ルート。 ルートはこのルートにリダイレクトされます。
openApiSchema.yml Person API を定義する OpenAPI 3.0 スキーマ ファイル。
config.js 環境変数を読み取り、適切な mssql 接続オブジェクトを構築するための構成ファイル。
database.js mssql npm パッケージを使用して Azure SQL CRUD 操作を処理するデータベース クラス。
./vscode/settings.json デプロイ中、glob パターンによってファイルを無視します。
  1. index.js ファイルを作成し、次のコードを追加します。

    import express from 'express';
    import { config } from './config.js';
    import Database from './database.js';
    
    // Import App routes
    import person from './person.js';
    import openapi from './openapi.js';
    
    const port = process.env.PORT || 3000;
    
    const app = express();
    
    // Development only - don't do in production
    // Run this to create the table in the database
    if (process.env.NODE_ENV === 'development') {
      const database = new Database(config);
      database
        .executeQuery(
          `CREATE TABLE Person (id int NOT NULL IDENTITY, firstName varchar(255), lastName varchar(255));`
        )
        .then(() => {
          console.log('Table created');
        })
        .catch((err) => {
          // Table may already exist
          console.error(`Error creating table: ${err}`);
        });
    }
    
    // Connect App routes
    app.use('/api-docs', openapi);
    app.use('/persons', person);
    app.use('*', (_, res) => {
      res.redirect('/api-docs');
    });
    
    // Start the server
    app.listen(port, () => {
      console.log(`Server started on port ${port}`);
    });
    
  2. person.js ルート ファイルを作成し、次のコードを追加します。

    import express from 'express';
    import { config } from './config.js';
    import Database from './database.js';
    
    const router = express.Router();
    router.use(express.json());
    
    // Development only - don't do in production
    console.log(config);
    
    // Create database object
    const database = new Database(config);
    
    router.get('/', async (_, res) => {
      try {
        // Return a list of persons
        const persons = await database.readAll();
        console.log(`persons: ${JSON.stringify(persons)}`);
        res.status(200).json(persons);
      } catch (err) {
        res.status(500).json({ error: err?.message });
      }
    });
    
    router.post('/', async (req, res) => {
      try {
        // Create a person
        const person = req.body;
        console.log(`person: ${JSON.stringify(person)}`);
        const rowsAffected = await database.create(person);
        res.status(201).json({ rowsAffected });
      } catch (err) {
        res.status(500).json({ error: err?.message });
      }
    });
    
    router.get('/:id', async (req, res) => {
      try {
        // Get the person with the specified ID
        const personId = req.params.id;
        console.log(`personId: ${personId}`);
        if (personId) {
          const result = await database.read(personId);
          console.log(`persons: ${JSON.stringify(result)}`);
          res.status(200).json(result);
        } else {
          res.status(404);
        }
      } catch (err) {
        res.status(500).json({ error: err?.message });
      }
    });
    
    router.put('/:id', async (req, res) => {
      try {
        // Update the person with the specified ID
        const personId = req.params.id;
        console.log(`personId: ${personId}`);
        const person = req.body;
    
        if (personId && person) {
          delete person.id;
          console.log(`person: ${JSON.stringify(person)}`);
          const rowsAffected = await database.update(personId, person);
          res.status(200).json({ rowsAffected });
        } else {
          res.status(404);
        }
      } catch (err) {
        res.status(500).json({ error: err?.message });
      }
    });
    
    router.delete('/:id', async (req, res) => {
      try {
        // Delete the person with the specified ID
        const personId = req.params.id;
        console.log(`personId: ${personId}`);
    
        if (!personId) {
          res.status(404);
        } else {
          const rowsAffected = await database.delete(personId);
          res.status(204).json({ rowsAffected });
        }
      } catch (err) {
        res.status(500).json({ error: err?.message });
      }
    });
    
    export default router;
    
    
  3. openapi.js ルート ファイルを作成し、OpenAPI UI エクスプローラーの次のコードを追加します。

    import express from 'express';
    import { join, dirname } from 'path';
    import swaggerUi from 'swagger-ui-express';
    import yaml from 'yamljs';
    import { fileURLToPath } from 'url';
    
    const __dirname = dirname(fileURLToPath(import.meta.url));
    
    const router = express.Router();
    router.use(express.json());
    
    const pathToSpec = join(__dirname, './openApiSchema.yml');
    const openApiSpec = yaml.load(pathToSpec);
    
    router.use('/', swaggerUi.serve, swaggerUi.setup(openApiSpec));
    
    export default router;
    
  4. openApiSchema.yml スキーマ ファイルを作成し、次の YAML コードを追加します。

    openapi: 3.0.0
    info:
      version: 1.0.0
      title: Persons API
    paths:
      /persons:
        get:
          summary: Get all persons
          responses:
            '200':
              description: OK
              content:
                application/json:
                  schema:
                    type: array
                    items:
                      $ref: '#/components/schemas/Person'
        post:
          summary: Create a new person
          requestBody:
            required: true
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/Person'
          responses:
            '201':
              description: Created
              content:
                application/json:
                  schema:
                    $ref: '#/components/schemas/Person'
      /persons/{id}:
        parameters:
          - name: id
            in: path
            required: true
            schema:
              type: integer
        get:
          summary: Get a person by ID
          responses:
            '200':
              description: OK
              content:
                application/json:
                  schema:
                    $ref: '#/components/schemas/Person'
            '404':
              description: Person not found
        put:
          summary: Update a person by ID
          requestBody:
            required: true
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/Person'
          responses:
            '200':
              description: OK
              content:
                application/json:
                  schema:
                    $ref: '#/components/schemas/Person'
            '404':
              description: Person not found
        delete:
          summary: Delete a person by ID
          responses:
            '204':
              description: No Content
            '404':
              description: Person not found
    components:
      schemas:
        Person:
          type: object
          properties:
            id:
              type: integer
              readOnly: true
            firstName:
              type: string
            lastName:
              type: string
    

mssql 接続オブジェクトを作成する

mssql パッケージでは、認証の種類の構成設定を提供して Azure SQL Database への接続を実装します。

  1. Visual Studio Code で、config.js ファイルを作成し、次の mssql 構成コードを追加して、Azure SQL Database に対する認証を行います。

    import * as dotenv from 'dotenv';
    dotenv.config({ path: `.env.${process.env.NODE_ENV}`, debug: true });
    
    const server = process.env.AZURE_SQL_SERVER;
    const database = process.env.AZURE_SQL_DATABASE;
    const port = parseInt(process.env.AZURE_SQL_PORT);
    const type = process.env.AZURE_SQL_AUTHENTICATIONTYPE;
    
    export const config = {
        server,
        port,
        database,
        authentication: {
            type
        },
        options: {
            encrypt: true
        }
    };
    
  2. ローカル環境変数の .env.development ファイルを作成し、次のテキストを追加し、<YOURSERVERNAME><YOURDATABASENAME> を実際の値で更新します。

    AZURE_SQL_SERVER=<YOURSERVERNAME>.database.windows.net
    AZURE_SQL_DATABASE=<YOURDATABASENAME>
    AZURE_SQL_PORT=1433
    AZURE_SQL_AUTHENTICATIONTYPE=azure-active-directory-default
    

注意

パスワードレス構成オブジェクトには、ユーザー名、パスワード、またはアクセス キーが含まれていないため、このオブジェクトを使用すると、ソース管理に安全にコミットできます。

  1. .vscode フォルダーを作成し、そのフォルダー内に settings.json ファイルを作成します。

  2. zip のデプロイ中に環境変数と依存関係を無視するには、次を追加します。

    {
        "appService.zipIgnorePattern": ["./.env*","node_modules{,/**}"]
    }
    

Azure SQL Database に接続するコードを追加する

  1. database.js ファイルを作成し、次のコードを追加します。

    import sql from 'mssql';
    
    export default class Database {
      config = {};
      poolconnection = null;
      connected = false;
    
      constructor(config) {
        this.config = config;
        console.log(`Database: config: ${JSON.stringify(config)}`);
      }
    
      async connect() {
        try {
          console.log(`Database connecting...${this.connected}`);
          if (this.connected === false) {
            this.poolconnection = await sql.connect(this.config);
            this.connected = true;
            console.log('Database connection successful');
          } else {
            console.log('Database already connected');
          }
        } catch (error) {
          console.error(`Error connecting to database: ${JSON.stringify(error)}`);
        }
      }
    
      async disconnect() {
        try {
          this.poolconnection.close();
          console.log('Database connection closed');
        } catch (error) {
          console.error(`Error closing database connection: ${error}`);
        }
      }
    
      async executeQuery(query) {
        await this.connect();
        const request = this.poolconnection.request();
        const result = await request.query(query);
    
        return result.rowsAffected[0];
      }
    
      async create(data) {
        await this.connect();
        const request = this.poolconnection.request();
    
        request.input('firstName', sql.NVarChar(255), data.firstName);
        request.input('lastName', sql.NVarChar(255), data.lastName);
    
        const result = await request.query(
          `INSERT INTO Person (firstName, lastName) VALUES (@firstName, @lastName)`
        );
    
        return result.rowsAffected[0];
      }
    
      async readAll() {
        await this.connect();
        const request = this.poolconnection.request();
        const result = await request.query(`SELECT * FROM Person`);
    
        return result.recordsets[0];
      }
    
      async read(id) {
        await this.connect();
    
        const request = this.poolconnection.request();
        const result = await request
          .input('id', sql.Int, +id)
          .query(`SELECT * FROM Person WHERE id = @id`);
    
        return result.recordset[0];
      }
    
      async update(id, data) {
        await this.connect();
    
        const request = this.poolconnection.request();
    
        request.input('id', sql.Int, +id);
        request.input('firstName', sql.NVarChar(255), data.firstName);
        request.input('lastName', sql.NVarChar(255), data.lastName);
    
        const result = await request.query(
          `UPDATE Person SET firstName=@firstName, lastName=@lastName WHERE id = @id`
        );
    
        return result.rowsAffected[0];
      }
    
      async delete(id) {
        await this.connect();
    
        const idAsNumber = Number(id);
    
        const request = this.poolconnection.request();
        const result = await request
          .input('id', sql.Int, idAsNumber)
          .query(`DELETE FROM Person WHERE id = @id`);
    
        return result.rowsAffected[0];
      }
    }
    

アプリをローカルでテストする

アプリをローカルでテストする準備ができました。 データベースの管理者として設定したものと同じアカウントを使用して、Visual Studio Code で Azure Cloud にサインインしていることを確認します。

  1. 次のコマンドでアプリケーションを実行します。 このアプリケーションは、ポート 3000 で起動されます。

    NODE_ENV=development node index.js
    

    このアプリケーションを実行すると、データベース内に Person テーブルが作成されます。

  2. ブラウザーで、OpenAPI エクスプローラー (http://localhost:3000) に移動します。

  3. Swagger UI ページで、POST メソッドを展開し、[テスト] を選択します。

  4. プロパティの値を含むようにサンプル JSON を変更します。 ID プロパティは無視されます。

    API をテストする方法を示すスクリーンショット。

  5. [実行] を選択して、新しいレコードをデータベースに追加します。 API は正常な応答を返します。

  6. Swagger UI ページで GET メソッドを展開し、[テスト] を選択します。 [実行] を選択すると、先ほど作成した人物が返されます。

Azure App Service にデプロイする

アプリを Azure にデプロイする準備ができました。 Visual Studio Code では、Azure App Service を作成し、単一のワークフローにアプリケーションをデプロイできます。

  1. アプリが停止されていることを確認します。

  2. まだ Azure にサインインしていない場合は、コマンド パレット (Ctrl + Shift + P キー) で [Azure: Azure Cloud にサインインする] コマンドを選択してサインインします

  3. Visual Studio Code の Azure Explorer ウィンドウで [App Services] ノードを右クリックし、[新しい Web アプリを作成する (詳細)] を選択します。

  4. 次の表を使用して App Service を作成します。

    Prompt
    新しい Web アプリのグローバルに一意の名前を入力します。 azure-sql-passwordless などのプロンプトを入力します。 末尾に 123 などの一意の文字列を追加します。
    新しいリソース用のリソース グループを選択してください [+ 新しいリソース グループの作成] を選択し、既定の名前を選択します。
    ランタイム スタックを選択してください Node.js スタックの LTS バージョンを選択します。
    OS を選択します。 [Linux] を選択します。
    新しいリソースの場所の選択。 近くの [場所] を選択します。
    Linux App Service プランを選択します。 [新しい App Service プランの作成] を選択し、既定の名前を選択します。
    価格レベルを選択します。 [無料 (F1)] を選択します。
    アプリの Application Insights のリソースを選択してください [後で確認する] を選択します。
  5. アプリが作成されたことを示す通知が表示されるのを待って、続行します。

  6. Azure Explorer で、[App Services] ノードを展開し、新しいアプリを右クリックします。

  7. [Web アプリにデプロイ] を選択します。

    Azure Explorer で [Web アプリにデプロイ] が強調表示されている Visual Studio Code のスクリーンショット。

  8. JavaScript プロジェクトのルート フォルダーを選択します。

  9. Visual Studio Code ポップアップが表示されたら、[デプロイ] を選択します。

デプロイが完了すると、アプリは Azure 上で正しく動作しなくなります。 データを取得するために、App Service と SQL データベースの間にセキュリティで保護された接続を構成する必要があります。

App Service を Azure SQL Database に接続する

App Service インスタンスを Azure SQL Database に接続するには、次の手順が必要です。

  1. App Service 用のマネージド ID を作成します。
  2. SQL データベース ユーザーを作成し、App Service のマネージド ID に関連付けます。
  3. 読み取り、書き込み、場合によってはその他のアクセス許可を付与する SQL ロールをデータベース ユーザーに割り当てます。

これらの手順を実施するために使用できるツールは複数あります。

Service Connector は、Azure のさまざまなサービス間の認証された接続を合理化するツールです。 現在、Service Connector では、az webapp connection create sql コマンドを使用して Azure CLI 経由で App Service を Azure SQL データベースに接続する方法がサポートされています。 この 1 つのコマンドによって、上記の 3 つの手順が完了します。

Service Connector を使用してマネージド ID を作成する

Azure portal の Cloud Shell で次のコマンドを実行します。 Cloud Shell には、最新バージョンの Azure CLI が含まれています。 <> の変数を実際の値に置き換えます。

az webapp connection create sql \
    -g <app-service-resource-group> \
    -n <app-service-name> \
    --tg <database-server-resource-group> \
    --server <database-server-name> \
    --database <database-name> \
    --system-identity

App Service アプリの設定を確認する

Service Connector によって行われた変更は、App Service の設定で確認できます。

  1. Visual Studio Code の Azure Explorer で、[App Service] を右クリックし、[ポータルで開く] を選択します。

  2. App Service の [ID] ページに移動します。 [システム割り当て済み] タブで、[状態][オン] に設定されているはずです。 この値は、システム割り当てマネージド ID がアプリに対して有効になっていたことを意味します。

  3. App Service リソースの [構成] ページに移動します。 [アプリケーション設定] タブの下に、mssql 構成オブジェクトに既に含まれているいくつかの環境変数が表示されます。

    • AZURE_SQL_SERVER
    • AZURE_SQL_DATABASE
    • AZURE_SQL_PORT
    • AZURE_SQL_AUTHENTICATIONTYPE

    プロパティの名前や値を削除したり、変更したりしないでください。

デプロイされたアプリケーションをテストする

アプリの URL を参照して、Azure SQL Database への接続が動作していることをテストします。 アプリの URL は、App Service の概要ページで確認できます。

ローカルで作成した人物がブラウザーに表示されるはずです。 おめでとうございます。 これで、ローカル環境とホスト環境の両方で、アプリケーションを Azure SQL Database に接続することができました。

ヒント

テスト中に 500 内部サーバー エラーが発生した場合は、データベース ネットワーク構成が原因である可能性があります。 論理サーバーが、「データベースを構成する」セクションで概説されている設定で構成されていることを確認します。

リソースのクリーンアップ

Azure SQL Database の操作が完了したら、意図しないコストを回避するためにリソースを削除します。

  1. Azure portal の検索バーで「Azure SQL」を検索し、一致する結果を選択します。

  2. データベースの一覧でデータベースを見つけて選択します。

  3. Azure SQL Database の [概要] ページで、[削除] を選択します。

  4. 開かれる [削除しますか...] ページで、データベースの名前を入力して確認し、[削除] を選択します。

サンプル コード

このアプリケーションのサンプル コードは、GitHub で入手できます。

次の手順