Fastify入門としてJSON Schemaを使った検証を試してみた

2022.10.27

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

Fastifyを使ってみました。まずは簡単なサーバを起動する方法と、JSON Schemaを利用した検証を試してみたので、備忘録を兼ねてまとめておきます。

Fastifyとは?

Fastifyとは、HapiやExpressに触発された、比較的新しいNode.jsのWebフレームワークです。名前の通り、レスポンスが非常に高速でオーバーヘッドが少ないのが特徴のようです。

Fastify, Fast and low overhead web framework, for Node.js

使ってみる

早速、公式のドキュメントに従って使ってみます。

環境

本記事を執筆した環境は以下の通りです。

  • Windows10 Pro
  • Node.js v14.17.4

インストール

まずはプロジェクトフォルダを作成します。ターミナルのカレントフォルダをプロジェクトフォルダの直下に移動しておきます。

Fastifyのインストールはnpmまたはyarnで行います。

npm i fastify

または

yarn add fastify

すると色々な依存モジュールとともにFastifyがインストールされます。

私の環境でインストールされたFastifyのバージョンは4.9.2、後述しますがAjvというライブラリのバージョンは8.11.0です。以下の記事はこのバージョンで試した内容となります。

簡単なサーバを作る

簡単なサーバを作ってみます。

プロジェクトフォルダの直下にserver.jsを作成し、以下のように記述します。

const fastify = require('fastify')({
    logger: true
})

fastify.get('/', function(req, rep) {
    rep.send({
        hello: 'world'
    })
})

fastify.listen({
    port: 3000
}, function(err, address) {
    if(err){
        fastify.log.error(err)
        process.exit(1)
    }
})

ターミナルで以下のコマンドを実行し、このJavaScriptファイルを動かします。

node server.js

コンソールに以下のように表示されたら、サーバが無事に起動できリクエストが受け付けられる状態になりました。

ブラウザにhttp://localhost:3000と入力すると、

サーバから文字を返してもらうことができました。

ルーティングを追加する

上記のコードでは、パスが「/」、つまりルートにアクセスされたときの動作しか定義していませんでした。

このコードに「/test」パスにアクセスされたときのルーティングを追加してみます。

fastify.get('/', function(req, rep) {
    rep.send({
        hello: 'world'
    })
})

//追加
fastify.get('/test', function(req, rep) {
    rep.send({
        page: 'test page'
    })
})

サーバを起動しなおして、ブラウザにhttp://localhost:3000/testと入力します。

ルーティングが追加できました。

POSTメソッドを追加する

GETだけでなくPOSTメソッドも追加してみます。リクエストボディの内容をそのままレスポンスとして返すという単純な内容です。

fastify.get('/test', function(req, rep) {
    rep.send({
        page: 'test page'
    })
})

//追加
fastify.post('/users', function(req, rep) {
    rep.send(req.body)
})

POSTリクエストを送るためにPostmanを使います。

リクエストボディの内容がレスポンスとして返ってくることが確認できます。

JSON Schemaの利用

Fastifyでは、リクエストやレスポンスにJSON Schemaを使うことができます。

JSON Schemaとは?

JSON Schemaとは、JSONの構造をJSON形式で定義したものです。例えば、どんなフィールドが含まれているか、フィールドの型は何か、といったことを定義することができます。JSONに対する型定義のようなものです。JSON Schemaを使うことで、JSONの構造が意図したものになっているかを確認することができます。

FastifyにはJSON Schemaを使ってリクエストやレスポンスのJSONを検証する機能が備わっています。また、レスポンスとして返すJSON Schemaをあらかじめ定義しておくことで、JSONを高速にシリアライズしているようです。

ちなみに、Fastifyの検証はAjvというライブラリを標準で使用しています。上でも述べましたが、本記事で使用しているバージョンは8.11.0になります。

リクエスト検証を行う

JSON Schemaを使ってリクエストの検証をしてみます。

まず、schema:から始まるオブジェクトの中でJSONの構造を定義します。ここでは、bodyのタイプはオブジェクトで、userIdとuserName、roleというフィールドを持ち、すべて必須であるという内容で定義しています。

そして、fastify.postメソッドの引数で定義した内容を渡します。

「/users」に対するPOSTメソッドで送られてくるリクエストボディが、この形のJSONであることを期待しているわけですね。

//追加
const opts = {
    schema: {
        body: {
            type: 'object',
            required: ['userId', 'userName', 'role'],
            properties: {
                userId: {type: 'string'},
                userName: {type: 'string'},
                role: {type: 'string'}
            }
        }
    }
}
//第2引数にオプションとしてJSON Schemaを渡す
fastify.post('/users', opts, function(req, rep) {
    rep.send(req.body)
})

では、先ほどと同じ内容でPostmanから送ってみます。

レスポンスを見ると、400 Bad Requestになってしまいました。

Fastifyは、リクエスト検証に失敗すると自動的に400を返す仕組みになっています。roleが必要だよというエラーメッセージも表示されているので、JSON Schemaによる検証が働いていそうです。

では、roleというフィールドも含めて再度やってみます。

今度はエラーにならずにちゃんと期待した結果が返ってきました。

ちなみに、今回はリクエストボディに対する検証を行いましたが、クエリ文字列やヘッダについても検証することができます。

それぞれ簡単に紹介します。

クエリ文字列に対する検証

const opts = {
    schema: {
        querystring: {
            type: 'object',
            required: ['userId'],
            properties: {
                userId: {type: 'string'}
            }
        }
    }
}
fastify.post('/users', opts, function(req, rep) {
    rep.send(req.body)
})

クエリ文字列なしで実行します。クエリ文字列というのは、URLの最後に?をつけて値を渡すものです。

クエリ文字列にuserIdがないよ!というエラーになりました。

ヘッダに対する検証

const opts = {
    schema: {
        headers: {
            type: 'object',
            required: ['x-foo'],
            properties: {
                'x-foo': {type: 'string'}
            }
        }
    }
}
fastify.post('/users', opts, function(req, rep) {
    rep.send(req.body)
})

ヘッダなしで実行します。

x-fooというヘッダがないよ!というエラーになりました。

レスポンスに対する検証

リクエストだけでなく、レスポンスボディも検証することができます。リクエストボディと同じように、どんなフィールドが含まれているかと、それぞれのフィールドの型を定義します。

const opts = {
    schema: {
        response: {
            200: {
                type: 'object',
                required: ['value', 'code'],
                properties: {
                    value: {type: 'string'},
                    code: {type: 'number'}
                }
            }
        }
    }
}
fastify.get('/users', opts, function(req, rep) {
    rep.send({
        value: 'hoge'
    })
})

上記の例は、レスポンスのステータスコードが200のとき、valueとcodeというフィールドをもつJSONを返すことを定義しています。しかし、レスポンスの中身を見るとvalueフィールドはありますが、codeフィールドが書かれていません。

この状態で実行してみます。

500エラーで、codeがないよ!と言われました。

codeも返すように修正してみます。

const opts = {
    schema: {
        response: {
            200: {
                type: 'object',
                required: ['value', 'code'],
                properties: {
                    value: {type: 'string'},
                    code: {type: 'number'}
                }
            }
        }
    }
}
fastify.get('/users', opts, function(req, rep) {
    rep.send({
        value: 'hoge',
        code: 123
    })
})

今度はちゃんとレスポンスが返ってきました。

これを利用すれば、必要なデータを渡し忘れるミスや、逆に意図しないデータを誤って渡してしまうのを防げそうです。

まとめ

Fastifyで簡単なサーバを作り、リクエストとレスポンスの検証を試してみました。

JSON Schemaを利用した検証機能がデフォルトで備わっているのがとても便利だと感じました。JSONはとても柔軟性の高いフォーマットなので、それを定義で縛っておけるのは開発者にとって嬉しいのではないかと思います。

まだまだ触れていない機能がたくさんあるので、引き続き勉強していきたいと思います。