1
1

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 5 years have passed since last update.

Netlify functionsを利用したサーバーレスアプリケーションの開発

Last updated at Posted at 2020-04-20

概要

Netlify Functionsの設定、およびNetlify Functionsのコード(以下コードと呼ぶ)の作成、コードを呼び出すwebページの作成について説明する。サンプルとして、ボタンをクリックするとコードにアクセスして、コードから返された文字列を表示するwebページを作成した。

作成したwebページのソースコードはこちらのリポジトリ、webサイトはこちらのサイトである。

Netlifyのドキュメントを参考にして、以下の手順で設定する。

GitHubとNetlifyの環境を設定する

GitHubのリポジトリを作成する

作成したプログラムを管理するリポジトリを作成する。

GitHubのリポジトリとNetlifyのサイトを関連付ける

Netlifyのサイトを作成して、GitHubのリポジトリと関連付けて、masterブランチが更新されるとNetlifyのサイトが更新されるように設定する。

ローカルの環境を設定する

ローカル環境にクローンする

作成したリポジトリを、ローカル環境にクローンする。ここではクローンしたディレクトリをfunctionsとする。

vueプロジェクトを作成する

コードを呼び出すwebページを作成するために、vue create functionsを実行する。オプションのテストツールとしてJestを選択する。

ボタン1つとテキストを表示するwebページを作成する

テスト駆動開発でbuttonを追加を参考にして、ボタン1つとテキストを表示するwebページを作成する。作成したソースコードをローカル環境のgitのリポジトリにコミットして、GitHubにプッシュする。

masterブランチを更新して、関連付けたNetlifyのサイトに、作成したwebページが表示されることを確認する。

netlify-lambdaをインストールする

Netlify Functionsをローカル環境でテストするために、以下のコマンドでnetlify-lambdaをインストールする。

% yarn add netlify-lambda

続いて以下のコマンドで、netlify-lambdaパッケージのvue-cliプラグインをインストールする。

% vue add netlify-lambda

プラグインをインストールすると、以下のファイルが生成される。

  • /netlify.toml
  • /src/lambda/hello.js
netlify.toml
[build]
  command = "yarn build"
  functions = "lambda"
  publish = "dist"
hello.js
export function handler(event, context, callback) {
  console.log(event);
  callback(null, {
    statusCode: 200,
    body: JSON.stringify({ msg: "Hello, World!" })
  });
}

axiosをインストールする

Netlify functionsのコードにアクセスするHTTPクライアントとして、axiosを以下のコマンドでインストールする。

% yarn add axios

Netlify Functionsのコードを作成する

コードを確認するテストを追加する

コードの仕様を以下のとおりとする。

これらを確認するテストを作成する。

import axios from "axios";

describe("Netlify functions", () => {
  it("ステータスコードとデータを確認する。", async () => {
    let response;
    try {
      response = await axios.get(
        "http://localhost:9000/.netlify/functions/sample"
      );
    } catch (e) {
      console.error(e);
      return;
    }
    expect(response.status).toBe(200);
    expect(response.data).toBe("sample");
  });
});

対象とするURLが存在しないため、ここではテストは失敗する。

Jestの実行モード

テストを実行するとError: Cross origin http://localhost forbiddenでエラーになる。Jestの実行環境がjsdomモードのため、CORSに違反するためである。nodeモードでJestを実行することでCORSを回避できる。テストモードの切替単位は、全体とファイル単位の2通りがある。他のテストはjsdomモードで実行する必要があり、jsdomモードとnodeモードのテストを共存する必要があるため、ファイル単位での切替とする。

Jestのドキュメントを参照して、ファイルごとに実行環境を設定するために、jsdomモードで実行するfunctions.spec.jsとnodeモードで実行するlambda.spec.jsに分割して、lambda.jsファイルの先頭に以下を追加することで、実行環境をnodeに設定する。

lambda.spec.js
/**
 * @jest-environment jsdom
 */
import axios from "axios";

describe("Netlify functions", () => {
  it("ステータスコードとデータを確認する。", async () => {
    let response;
    try {
      response = await axios.get(
        "http://localhost:9000/.netlify/functions/sample"
      );
    } catch (e) {
      console.error(e);
      return;
    }
    expect(response.status).toBe(200);
    expect(response.data).toBe("sample");
  });
});

ローカル環境でfunctionsを起動する

以下のコマンドをローカル環境でfunctionsを起動する。

% netlify-lambda serve src/lambda

http://localhost:9000/.netlify/functions/helloにwebブラウズでアクセスすると、{"msg":"Hello, World!"}が表示されることを確認する。

コードをテストする

hello.jsをsample.jsにファイル名を変更して、 仕様にあわせて以下のように修正するとテストが成功する。

sample.js
export function handler(event, context, callback) {
  callback(null, {
    statusCode: 200,
    data: "sample"
  });
}

ここまでで、Netlify Functionsの設定およびコードが作成されている。

ビルドのエラーを解消する

yarn buildを実行するとUnknown browser query 'dead'というエラーになる。

package.jsonの"browserslist"ブロックから"not dead"を削除するとエラーがなくなる。

Netlify Functionsのコードの呼び出しを作成する

コードの呼び出しのテストを作成する

コードの呼び出しのテストを作成する。 テストの要件は以下の通りとする。

  • ボタンをクリックされると呼び出される。
  • axios.getが呼び出される。
  • 引数は、コードのURL(ローカルの場合はhttp://localhost:9000/.netlify/functions/sample)である。
  • ボタンがクリックされた後は、テキスト領域に'sample'が設定されている。

describeの前でaxiosをモックにする必要があるため、わかりやすくするため、別ファイル(click.spec.js)を作成する。

clisk.spec.js
import axios from "axios";
import { shallowMount } from "@vue/test-utils";
import SampleFunctions from "@/components/SampleFunctions.vue";

jest.mock("axios");
axios.get.mockImplementation(() =>
  Promise.resolve({ statusCode: 200, data: "sample" })
);

describe("コードの呼び出し", () => {
  let wrapper;

  beforeEach(() => {
    wrapper = shallowMount(SampleFunctions);
  });

  it("axios.getを呼び出す。", () => {
    wrapper.find("#sampleButton").trigger("click");

    expect(axios.get.mock.calls.length).toBe(1);
  });

  it("引数を確認する。", () => {
    wrapper.find("#sampleButton").trigger("click");

    expect(axios.get.mock.calls[0].length).toBe(1);
    expect(axios.get.mock.calls[0][0]).toBe(
      "http://localhost:9000/.netlify/functions/sample"
    );
  });

  it("ボタンがクリックされた後は、テキスト領域に'sample'が設定されている。", async () => {
    await wrapper.find("#sampleButton").trigger("click");
    expect(wrapper.vm.sampleText).toBe("sample");
  });
});

上記のテストを成功するように作成したgetFunctionUrlは以下の通りである。

SampleFunctions.vue
    getFunctionUrl(pageUrl) {
      const url = new URL(pageUrl);
      if (url.hostname === "localhost") {
        url.port = 9000;
      }
      url.pathname = ".netlify/functions/sample";
      return url.href;
    }

テストを成功するために、SampleFunctions.vueのonClickメソッドを以下のように修正する。

SampleFunctions.vue
    onClick() {
      axios
        .get("http://localhost:9000/.netlify/functions/sample")
        .then(response => {
          this.sampleText = response.data;
        })
        .catch(error => {
          console.error(error);
        });
    }

コードのURLを取得する

ここまではコードのURLを決め打ちでハードコードしているが、Netlify環境にあわせて、取得する機能を追加する。

コードのURLを返すメソッドをgetFunctionUrlとして、このメソッドのテストを追加する。テストの要件は以下の通りとする。

  • 引数としてアクセスしているwebページのURL(location.href)を受け取る。
  • ホスト名がlocalhostの場合には、ポート番号を9000とする。
  • webページのURLに/.netlify/functions/sampleを追加したURLを返す。

テストするURLは以下の通りとする。

引数として渡すURL 返すべきURL
http://localhost/ http://localhost:9000/.netlify/functions/sample
http://localhost:8080 http://localhost:9000/.netlify/functions/sample
https://kubotama-sample-functions.netlify.app/ https://kubotama-sample-functions.netlify.app/.netlify/functions/sample

click.spec.jsに以下を追加する。

click.spec.js
describe("コードのURLを取得する。", () => {
  let wrapper;

  beforeEach(() => {
    wrapper = shallowMount(SampleFunctions);
  });

  it.each`
    beforeUrl                                           | afterUrl
    ${"http://localhost/"}                              | ${"http://localhost:9000/.netlify/functions/sample"}
    ${"http://localhost:8080"}                          | ${"http://localhost:9000/.netlify/functions/sample"}
    ${"https://kubotama-sample-functions.netlify.com/"} | ${"https://kubotama-sample-functions.netlify.com/.netlify/functions/sample"}
  `("$before -> $after", ({ beforeUrl, afterUrl }) => {
    expect(wrapper.vm.getFunctionUrl(beforeUrl)).toBe(afterUrl);
  });
});

SampleFunctions.vueのonClickメソッドをgetFunctionUrlを呼び出すように書き換える。

SampleFunctions.vue
    onClick() {
      axios
        .get(this.getFunctionUrl(window.location.href))
        .then(response => {
          this.sampleText = response.data;
        })
        .catch(error => {
          console.error(error);
        });
    },

Netlify環境への適用

作成したプログラムをGitのリポジトリにコミットして、GitHubのリポジトリにプッシュする。GitHubのリポジトリとNetlifyのサイトが正しく連携していれば、自動的にNetlifyのサイトが更新されて、自動的にNetlify Functionsが開始される。

Netlifyのサイトを表示して、ボタンをクリックすると、ボタンの下に表示されている「メッセージを表示する場所」が「sample」に変更する。

(オプション)ローカルでの検証環境でのCORSを回避する

ローカルの検証環境のボタンを表示するwebページのURLはhttp://localhost:8080で、コードのURLはhttp://localhost:9090/.netlify/functions/sampleのため、CORS制約に違反する。

sample.jsを以下の様に修正して、検証環境のみAccess-Control-Allow-Originを追加することでCORSを回避する。

sample.js
export function handler(event, context, callback) {
  const data = {
    statusCode: 200,
    body: "sample"
  };
  if (event.headers.host === "localhost:9000") {
    data["headers"] = {
      "Access-Control-Allow-Origin": event.headers.origin
    };
  }
  callback(null, data);
}

大変長くなってしまいましたが、最後まで読んでいただきまして、ありがとうございました。よければLGTMをいただけると励みになります。

1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?