TypeORMをAzure Functionsで使ってAzure Databaseに接続する

2022年8月2日

Install

Functionsを作成しておく

こちらを使います

https://github.com/xiaotiantakumi/az-func-ts-starter

npm install typeorm --save
npm install reflect-metadata --save
npm install mysql --save

index.tsでreflect-metadataをimportしておく。

import "reflect-metadata";

tsconfig.jsonを修正する。

{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es6",
    "outDir": "dist",
    "rootDir": ".",
    "sourceMap": true,
    "strict": false,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true
  }
}

ソースコード

TypeORMの公式を少しアレンジしてます。Entityは一緒です。

import { Entity, PrimaryGeneratedColumn, Column, BaseEntity } from "typeorm";

@Entity()
export class User extends BaseEntity {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  firstName: string;

  @Column()
  lastName: string;

  @Column()
  age: number;
}

index.tsはこちら

import "reflect-metadata";
import { AzureFunction, Context, HttpRequest } from "@azure/functions";
import { DataSource } from "typeorm";
import { User } from "../Entity/entity/User";

const httpTrigger: AzureFunction = async function (
  context: Context,
  req: HttpRequest
): Promise<void> {
  console.log(process.env.SQL_PASSWORD);
  console.log(process.env.SQL_CRT);
  const AppDataSource = new DataSource({
    type: "mysql",
    host: process.env.SQL_HOST,
    port: 3306,
    username: process.env.SQL_USER,
    password: process.env.SQL_PASSWORD,
    database: "quickstartdb",
    synchronize: true,
    logging: false,
    entities: [User],
    migrations: [],
    subscribers: [],
    ssl: {
      ca: process.env.SQL_CRT,
    },
  });
  AppDataSource.initialize()
    .then(async () => {
      const user = new User();
      user.firstName = "Timber";
      user.lastName = "Saw";
      user.age = Math.floor(Math.random() * 101);
      await user.save();
   AppDataSource.destroy();
    })
    .catch((error) => console.log(error));
};

export default httpTrigger;

Error: self signed certificate in certificate chainについて

これ以前も出ていた。
証明書を構成情報に持たせることでいけているはず。
ただ、なーんかうまくいかない。

なので、今回はSSL証明書を一旦諦めることにした。
以下でSSLを無効にする方法を書きました。

Active RecordかData Mapperどちらのパターンを選ぶか

詳細についてはこちらを参照していただければいいんですが、個人的に簡単に使えそうだなと思ったのはActive Recordでした。
C#のEntity Frameworkを使っていたので、Data Mapperのリポジトリーでデータをいじるというのもありかなと思いました。ただ、当時は個人開発での利用だったこともあり小規模でメリットをあまり感じなかったです。
公式にも書いてましたが、データマッパーの方は大規模なアプリケーションのメンテナンスに有効とあります。サンプル程度であれば、小規模なアプリケーションのメンテナンスに有効なActive Recordパターンでいいやという感じです。

明示的に接続を切断しておいたほうがよさそう

Azure Functionsの従量課金プランの場合、Functionsのインスタンスが常に立ち上がっているわけではないです。なので、Functions呼び出し時にDataSourceインスタンスのinitializeメソッドを呼び出し、データベースでの作業が完全に終了したら破棄します。 バックエンドサーバーが常に稼働している場合、DataSourceを破棄する必要はないとのことです。

if you are building a backend for your site and your backend server always stays running – you never destroy a DataSource.

https://typeorm.io/data-source

レスポンスが受け取れない?

Azure Functionsを使っていて、いつもであればこんなコードを書けばレスポンスを返すことができていました。

context.res = {
    // status: 200, /* Defaults to 200 */
    body: result,
  };

しかし、以下のように書いてみるとレスポンスがちゃんと帰ってきませんでした。(index.tsを少し編集してます)

  AppDataSource.initialize()
    .then(async () => {
      const user = new User();
      user.firstName = "Timber";
      user.lastName = "Saw";
      user.age = Math.floor(Math.random() * 101);
      await user.save();
   AppDataSource.destroy();
      context.res = {
          // status: 200, /* Defaults to 200 */
          body: "response message",
      };
    .catch((error) => console.log(error));
};

これは、非同期で実行されているところでcontextを修正しているです。
なので、これを以下のようにすれば正しくレスポンスを返すことができるようになります。

await AppDataSource.initialize().catch((error) => console.log(error));
const user = new User();
      user.firstName = "Timber";
      user.lastName = "Saw";
      user.age = Math.floor(Math.random() * 101);
      await user.save();
   AppDataSource.destroy();
      context.res = {
          // status: 200, /* Defaults to 200 */
          body: "response message",
      };