Netlify Functions のパス変更方法は?リクエストボディってどう取るの?など調べた
最近 Cloudflare Pages Workers ばかり触っていて、久々に Netlify Functions に戻ってきたらバージョンが上がっていてワケワカメだったので、調べたことをメモしておく。
目次
- とりあえず Netlify CLI をグローバルインストールしておくと楽
- Vite 製プロジェクト向けの開発用コマンドを整理する
- Vite でビルドした資材を本番相当のプレビュー環境として起動したい
- Netlify Functions にアクセスするパスを変更したい
- Netlify Functions で POST リクエストの Body を受け取りたい
- 以上
とりあえず Netlify CLI をグローバルインストールしておくと楽
まずはとりあえず Netlify CLI をグローバルインストールしておき、ログイン連携をしておく。
$ npm install -g netlify-cli
$ netlify login
Vite 製プロジェクト向けの開発用コマンドを整理する
Vite で作ったプロジェクトで、Netlify Functions などを有効にする開発環境を構築するには、以下のようにする。
$ npm run dev
でvite
による開発サーバを起動できるようにしておく$ netlify dev
コマンドで$ npm run dev
コマンドを参照するよう、netlify.toml
に定義を書いておく$ netlify dev
コマンドで開発用サーバを起動する
$ netlify dev
コマンドは専用のポートで開発用サーバを立ち上げるのだが、内部的には $ npm run dev
で立ち上げた Vite の開発用サーバにプロキシしていて、Netlify Functions へのアクセスなどだけ自前で調整してくれるような挙動をしている。Vite サーバをラップして Netlify CLI サーバが立ち上がる、というワケだ。
そんなワケで、必要なモジュール類、スクリプトの定義はこんな感じ。開発用サーバを立ち上げる時、ユーザは $ npm run dev
の方ではなく $ npm run local-dev
の方を叩くが、Netlify CLI 的が内部で $ npm run dev
を叩くので両方の定義が package.json
に必要、となるワケだ。
package.json
{
"name": "example",
"private": true,
"type": "module",
"scripts": {
"build": "tsc && vite build",
"serve": "netlify serve",
"dev" : "vite --host",
"local-dev": "netlify dev",
"netlify": "netlify"
},
"dependencies": {
"@netlify/functions": "2.8.1"
},
"devDependencies": {
"@types/node": "22.7.0",
"netlify-cli": "17.36.2",
"typescript": "5.6.2",
"vite": "5.4.7"
}
}
netlify.toml
[build]
command = "npm run build"
publish = "dist"
functions = "api"
[[redirects]]
from = "/api/*"
to = "/.netlify/functions/:splat"
status = 200
[dev]
command = "npm run dev"
[dev]
セクションで、npm run dev
を内部的に実行し、そこで立ち上がった開発用サーバのポートを自動的に捕まえてプロキシしているようだ。賢い。
Vite でビルドした資材を本番相当のプレビュー環境として起動したい
前述の package.json
に書いたとおり、$ npm run build
では tsc
による TypeScript のチェックと vite build
によるビルドを行っている。
こうしてビルドした静的な資材をプレビューしたい時、Netlify Functions も一緒にプレビュー環境に立ち上げる方法がある。
$ netlify serve
コマンドを叩くと、netlify.toml
に書いた定義に沿って、$ npm run build
が実行され、その資材を利用してプレビュー用サーバが立ち上がる。つまり参照しているのは ./src/
の方ではなく ./dist/
の方になり、ついでに Netlify Functions も動作するプレビューサーバが立ち上がっている状態になる。
netlify.toml
の[build]
セクションでcommand = "npm run build"
と定義しているためこのコマンドで事前ビルドが行われるpublish = "dist"
の記述により、ビルド後の静的な資材は./dist/
に存在することが分かり、Netlify CLI はこのディレクトリを Serve する- このプロジェクトでは
./api/
ディレクトリ配下に Netlify Functions のソースコードを置いているのだが、それが分かるようにfunctions = "api"
の指定をしている
Netlify Functions にアクセスするパスを変更したい
Netlify Functions のデフォルトの API エンドポイントパスは /.netlify/functions/【ファイル名】
という形になる。
実際のファイルは、プロジェクト配下の ./netlify/functions/【ファイル名】.ts
といった形で置いておけば対応付けされる。
…はて、netlify/
ディレクトリに置くのに、API エンドポイントは /.netlify
になるのか…。キモチワルイ…。
できれば Cloudflare Pages Workers のように、/api
が API エンドポイントになると分かりやすいので、そのように直す。
前述の netlify.toml
にこの設定が既に書かれている。
[build]
セクションのfunctions = "api"
で、ビルド後の資材として./api/
ディレクトリ内を Functions の資材として見なすようにしておく[[redirects]]
セクションの記述によって、/api
配下へのアクセスを/.netlify/functions
配下へのアクセスにリダイレクトする
というワケで、Netlify Functions の資材は ./api/hello.ts
のように配置すれば良くなり、フロントエンドから Netlify Functions にアクセスする際のエンドポイントは次のように /api/hello
となる。
fetch('/api/hello')
.then(response => response.json())
.then(json => { /* Do Something */ });
Netlify Functions で POST リクエストの Body を受け取りたい
Netlify Functions は AWS Lambda 互換の書き方 (exports.handler = (event, context) => {};
) も出来るのだが、今回は package.json
にて "type": "module"
を指定していることもあり、exports.
が使えない。
というかそもそも ES Modules 互換の Netlify Functions の書き方が分からないので調べ直したところ、次のように書くらしい。
./api/hello.ts
import { Context } from '@netlify/functions';
export default async (request: Request, context: Context) => {
return new Response(JSON.stringify({ message: 'Hello World' }), { status: 200 });
}
Request と Response は特にインポート不要。Deno とかと同じ、Web 標準というヤツみたい。
Netlify Functions は API の呼び出されるメソッドを限定しないため、/api/hello
は GET
でも POST
でも実行できてしまう。
このメソッド判定は if(request.method === 'POST')
などのように if
文でチェックが可能。POST
しか受け付けたくない API の場合は、このように request.method
をチェックして HTTP 405 あたりを返しておくと良いだろう。
そんで、POST
リクエストだった場合はリクエストボディってどうやって取得するの?ということなのだが、答えはこう。
import { Context } from '@netlify/functions';
export default async (request: Request, context: Context) => {
if(request.method !== 'POST') {
return new Response(JSON.stringify({ error: 'POST Method Only!' }), { status: 405 });
}
// POST 時のリクエストボディを取り出す
const body = await request.json();
// 例えばこの中に `id` プロパティが入っていたとしたら…こうやって取り出す
const id = body.id;
return new Response(JSON.stringify({ message: 'POST ID is ... ' + id }), { status: 200 });
}
await request.json()
でした!! response.json()
ならぬ request.json()
とな!! 分かるかこんなモン!! (分からなかった……)
request.json()
というのも Web 標準の仕組みらしい。そうなんだ…知らなかった…。
ちなみに request.body
自体は ReadableStream という型で、Deno なんかだと Deno.readAll()
で解体・読み込みできるらしい。
以上
Web 系のことも、気を抜くと一瞬で知らない情報が増えている…。忘れないように書いておかなきゃ。