LoginSignup
40
9

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-12-20

概要

Node.jsのフレームワークである「fastify」が速いらしいとの噂を聞きちょっといじってみようかなと思った記録です(何をいまさらですが...)。

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

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

下準備

バージョン

node.js 18.7.0

ディレクトリ作成

$ mkdir fastify_play

(任意の名前で大丈夫です)
ここで作業していきますー

npm初期化

$ npm init -y

fastifyインストール

最新版は4系ですが、他のパッケージ使いたかったりするので、相性がいい3系を使います。
あとはnodemonも使うので一緒に入れます。

$ npm i fastify@3 nodemon

とりあえずデフォルトのscript直して起動コマンド書く

package.json
...
  "scripts": {
    "start": "node server",
    "dev": "nodemon server"
  },
...

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

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

まずは公式通りやって動作確認

fastify - Quick start
server.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です!

リクエスト
$ curl http://localhost:3000
レスポンス
{"hello":"world"}

問題なし!

CRUD作ってみる

CRUDどんな感じで作っていくのか、リファレンス見ながら書いてみる。

ちょっとリファクタ

まずは最初にちょっとリファクタ

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()

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

データを作って返す処理を作る

まずは仮データ

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にルーティングと処理を追加する。

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

$ curl http://localhost:3000/posts

ここでルーティング追加するたびにserver.jsに追加して書いていくのはさすがにね...
fastify - Routes の機能がありますのでこちらを活用します。

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

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側の方も切り出した処理を消して、新しいルーティングを登録します。
下記のようになりますね!

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

こんな感じです!

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を返す処理を追加しましょう!
こんな感じです。

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

リクエスト
$ curl http://localhost:3000/posts/1

idをいじってちゃんとデータが変わるかも確認してみてください!

JSON Schema設定

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

こちらを参考に定義していきます。
fastify - Validation and Serialization

まずは/postsのシリアライズをします。
このような感じです。

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

レスポンス
[
  {"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を定義しましょう。

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)
  }
}
...

中身のコードが被ってるのでリファクタして最終的には全体こんな感じです。

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の処理を移していきます。

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,
}
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

40
9
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
40
9