今更ながらなんか速そうなfastify使ってみた

## 概要
Node.jsのフレームワークである「[fastify](https://github.com/fastify/fastify)」が速いらしいとの噂を聞きちょっといじってみようかなと思った記録です（何をいまさらですが...）。

fastifyに初めて触れる人にとってのチュートリアル的にものになればいいかなといった感じなので参考にしてみてください。

今回は一般的なCRUDのRead部分の処理を作ってみようかと思います！
他の処理はいずれ書く予定です...:bow:


## 下準備
### バージョン
node.js 18.7.0

### ディレクトリ作成
```
$ mkdir fastify_play
```
（任意の名前で大丈夫です）
ここで作業していきますー

### npm初期化
```
$ npm init -y
```

### fastifyインストール
最新版は4系ですが、他のパッケージ使いたかったりするので、相性がいい3系を使います。
あとはnodemonも使うので一緒に入れます。
```shell
$ npm i fastify@3 nodemon
```

### とりあえずデフォルトのscript直して起動コマンド書く
```json:package.json
...
  "scripts": {
    "start": "node server",
    "dev": "nodemon server"
  },
...
```

### mainファイルをserver.jsにする

```json:package.json
...
  "main": "server.js",
...
```

### まずは公式通りやって動作確認
[fastify - Quick start](https://github.com/fastify/fastify#quick-start)
server.jsを作成して
```js:server.js
const fastify = require('fastify')({
  logger: true
})

fastify.get('/', async (request, reply) => {
  reply.type('application/json').code(200)
  return { hello: 'world' }
})

fastify.listen({ port: 3000 }, (err, address) => {
  if (err) throw err
  // Server is now listening on ${address}
})
```

### リクエスト送って確認
curlコマンドではなくとも、普段お使いのHTTPクライアントツールで確認してもOKです！

```zsh:リクエスト
$ curl http://localhost:3000
```

```zsh:レスポンス
{"hello":"world"}
```

問題なし！

## CRUD作ってみる
CRUDどんな感じで作っていくのか、リファレンス見ながら書いてみる。
### ちょっとリファクタ
まずは最初にちょっとリファクタ
```js:server.js
const fastify = require('fastify')({
  logger: true
})
const PORT = 3000

fastify.get('/', (request, reply) => {
  reply.type('application/json').code(200)
  return { hello: 'world'}
})

const start = async() => {
  try {
    await fastify.listen(PORT)
  } catch (e) {
    fastify.log.error(e)
    process.exit(1)
  }
}

start()
```

動作確認だけしておきましょう。

### データを作って返す処理を作る
まずは仮データ
```js:posts.js
let posts = [
  {id: 1, title: "one", body: "uno"},
  {id: 2, title: "two", body: "dos"},
  {id: 3, title: "three", body: "tres"},
  {id: 4, title: "four", body: "quatro"},
  {id: 5, title: "five", body: "cinco"},
]
module.exports = posts
```

### ルーティング追加
server.jsにルーティングと処理を追加する。
```js:server.js
const fastify = require('fastify')({
  logger: true
})
// ここ
const posts = require('./Posts')
const PORT = 3000

// ここ
fastify.get('/posts', (request, reply) => {
  reply.send(posts)
})

fastify.get('/', (request, reply) => {
  reply.type('application/json').code(200)
  return { hello: 'world'}
})

const start = async() => {
  try {
    await fastify.listen(PORT)
  } catch (e) {
    fastify.log.error(e)
    process.exit(1)
  }
}

start()
```

下記にリクエストしてレスポンスがpostsの配列であればOK
```shell
$ curl http://localhost:3000/posts
```


ここでルーティング追加するたびにserver.jsに追加して書いていくのはさすがにね...
[fastify - Routes](https://www.fastify.io/docs/latest/Reference/Routes/) の機能がありますのでこちらを活用します。

routesディレクトリとファイル作成&コードはこんな感じです。

```js:routes/posts.js
const posts = require('../Posts')

function postRoutes(fastify, options, done) {
  fastify.get('/posts', (request, reply) => {
    reply.send(posts)
  })

  done()
}

module.exports = postRoutes
```

server.js側の方も切り出した処理を消して、新しいルーティングを登録します。
下記のようになりますね！
```js:server.js
const fastify = require('fastify')({
  logger: true
})
fastify.register(require('./routes/posts'))

const PORT = 3000

fastify.get('/', (request, reply) => {
  reply.type('application/json').code(200)
  return { hello: 'world'}
})

const start = async() => {
  try {
    await fastify.listen(PORT)
  } catch (e) {
    fastify.log.error(e)
    process.exit(1)
  }
}

start()
```
ここでリクエストしてみて、レスポンスが変わらなければOK

### handlerの切り出し
routes用にディレクトリを分けて処理を書くことができました。
リファレンスをよくみると、routesのoptionsとしてhandlerの処理を切り出せそうなので分けておきます。
[fastify - Routes  #routes-options](https://www.fastify.io/docs/latest/Reference/Routes/#routes-options)

こんな感じです！
```js:routes/posts.js
const posts = require('../Posts')

const getPostsOpts = {
  handler: function (request, reply) {
    reply.send(posts)
  }
}

function postRoutes(fastify, options, done) {
  fastify.get('/posts', getPostsOpts)

  done()
}

module.exports = postRoutes
```

### エンドポイント追加
データ一覧はいい感じにできたので、リクエストのパラメータにidを含めて該当のidのデータを取得するような処理を作ります。
ルーティングと該当のidのpostを返す処理を追加しましょう！
こんな感じです。
```js:routes/posts.js
const posts = require('../Posts')

const getPostsOpts = {
  handler: function (request, reply) {
    reply.send(posts)
  }
}

// /posts/:idのハンドラー
const getPostOpts = {
  handler: function (request, reply) {
    const { id } = request.params
    const post = posts.find((post) => post.id == id)
    reply.send(post)
  }
}

function postRoutes(fastify, options, done) {
  fastify.get('/posts', getPostsOpts)
  // ルーティング追加
  fastify.get('/posts/:id', getPostOpts)

  done()
}

module.exports = postRoutes
```

下記にリクエストしてレスポンスがpostsのidが1のデータであればOK
```shell:リクエスト
$ curl http://localhost:3000/posts/1
```
idをいじってちゃんとデータが変わるかも確認してみてください！


### JSON Schema設定
リファレンスによると、JSON Schemaを利用して出力をシリアライズしてあげると**highly performant function**にしてくれるのでおすすめですよとのこと！
要するに出力するJSON形式を定義してあげるといいよねって話！

こちらを参考に定義していきます。
[fastify - Validation and Serialization](https://www.fastify.io/docs/latest/Reference/Validation-and-Serialization/)

まずは`/posts`のシリアライズをします。
このような感じです。
```js:routes/items.js
...
const getPostsOpts = {
  schema: {
    response: {
      200: {
        type: 'array',
        items: {
          type: 'object',
          properties: {
            id: {type: 'integer'},
            title: {type: 'string'},
            body: {type: 'string'},
          }
        }
      }
    }
  },
  handler: function (request, reply) {
    reply.send(posts)
  }
}
...
```

これでリクエストしてみましょう！

定義した内容通り、Arrayで、idとtitleとbodyが返ってくればOK
```shell:レスポンス
[
  {"id":1,"title":"one","body":"uno"},
  {"id":2,"title":"two","body":"dos"},
  {"id":3,"title":"three","body":"tres"}, 
  {"id":4,"title":"four","body":"quatro"}, 
  {"id":5,"title":"five","body":"cinco"}
]
```
schemaのpropertiesの中をコメントアウトして再度リクエストをしてみると、ちゃんと定義した内容がレスポンスされることがわかるかと思うので試してみてください！

同じように`/posts/:id`の方もschemaを定義しましょう。

```js:routes/posts.js
...
const getPostOpts = {
  schema: {
    response: {
      200: {
        type: 'object',
        properties: {
          id: {type: 'integer'},
          title: {type: 'string'},
          body: {type: 'string'},
        }
      }
    }
  },
  handler: function (request, reply) {
    const { id } = request.params
    const post = posts.find((post) => post.id == id)
    reply.send(post)
  }
}
...
```

中身のコードが被ってるのでリファクタして最終的には全体こんな感じです。
```js:routes/posts.js
const posts = require('../Posts')

const Post = {
  type: 'object',
  properties: {
    id: {type: 'integer'},
    title: {type: 'string'},
    body: {type: 'string'},
  }
}

const getPostsOpts = {
  schema: {
    response: {
      200: {
        type: 'array',
        items: Post
      }
    }
  },
  handler: function (request, reply) {
    reply.send(posts)
  }
}

const getPostOpts = {
  schema: {
    response: {
      200: Post
    }
  },
  handler: function (request, reply) {
    const { id } = request.params
    const post = posts.find((post) => post.id == id)
    reply.send(post)
  }
}

function postRoutes(fastify, options, done) {
  fastify.get('/posts', getPostsOpts)
  fastify.get('/posts/:id', getPostOpts)

  done()
}

module.exports = postRoutes
```


### controller作ってみる
こんな感じでroutesファイルに各エンドポイントの処理をコーディングしていましたが、それをcontrollerに切り出してあげます(この方がしっくりきそうっすね)

controllerディレクトリを作ってその中にhandlerの処理を移していきます。
```js:controllers/posts.js
const posts = require('../Posts')

const getPosts = (request, reply) => {
  reply.send(posts)
}

const getPost = (request, reply) => {
  const { id } = request.params
  const post = posts.find((post) => post.id == id)
  reply.send(post)
}

module.exports = {
  getPosts,
  getPost,
}
```

```js:routes/posts.js
const {getPosts, getPost} = require('../controllers/posts')

const Post = {
  type: 'object',
  properties: {
    id: {type: 'integer'},
    title: {type: 'string'},
    body: {type: 'string'},
  }
}

const getPostsOpts = {
  schema: {
    response: {
      200: {
        type: 'array',
        items: Post
      }
    }
  },
  handler: getPosts
}

const getPostOpts = {
  schema: {
    response: {
      200: Post
    }
  },
  handler: getPost
}

function postRoutes(fastify, options, done) {
  fastify.get('/posts', getPostsOpts)
  fastify.get('/posts/:id', getPostOpts)

  done()
}

module.exports = postRoutes
```

動作の確認をしましょう！
`/posts`も`/posts/:id`も大丈夫なはず！


## さいごに
JSON自体とても自由度が高めなフォーマット形式なので、JSON Schemaで検証できるのはかなり便利そうだなと思います。
TS含め様々なプラグインもあり、いろいろな機能が実装できそうです。
express.jsに比べると日本語での情報量は少ないですが、高速であることやJSON Schemaの機能、またasync/awaitの処理も簡単に書けるので面白いかなと感じてます。
まだまだ発展段階かなと思うので、初学者が学ぶ選択肢としては違う感じではありますが、express.jsに比べて高速にレスポンスを返す部分はかなりのメリットかなと思うので、安定したフレームワークになるのがとても楽しみです！

今回のコードはGitHubにあげてるので興味もった方は全体像見てみてください！
[GitHub - kerochelo/fastify_play](https://github.com/kerochelo/fastify_play)