Falcorで実現する効率的なfetch サーバー編 第1回 サーバーサイド実装の概要

Falcorを使うにあたって必要となるサーバーサイド側の実装を解説します。まずは、サーバーサイド実装のあらましを、単純なPathのリクエスト応える実装を行いながら確認しましょう。

発行

著者 山田 順久 フロントエンド・エンジニア
Falcorで実現する効率的なfetch サーバー編 シリーズの記事一覧

この記事の内容は古くなっています。また、Falcorは3年前から開発が止まっており、現在ではおすすめできるライブラリではなくなりました。現在はGraphQLで実現できることも多いので「Hygraph(GraphCMS)で学ぶGraphQL」などを参照してください。(2024.10.15)

はじめに

このシリーズではFalcorを使うにあたって必要となるサーバーサイド側の実装を取り上げていきます。

Falcorの基本的な考え方やクライアント側の実装については以前のシリーズ*でも扱っているので、そちらも目を通しておくことをおすすめします。

*注:Falcorのクライアントサイドの実装がわかるシリーズ

サーバーサイドでやること

Falcorを使った場合、クライアント側ではPathやPathSet*と呼ばれるFalcor用のクエリを書いてデータの取得や更新、追加、その他の操作を行います。

*注:PathやPathSet

Path、PathSetについては過去の記事でも紹介しています。

Falcorを使ってデータを取得する例

const model = new falcor.Model({
  source: new falcor.HttpDataSource('/model.json')
});
model.get('hello').then(res => {
  console.log(res);
});

このとき、ブラウザのコンソール上では次のような結果になります。

ブラウザのコンソール上で確認できる結果

{
  "json": {
    "hello": "hello!"
  }
}

このコードが実行されているとき、HTTP上では/model.jsonというURLのパスに対してpathsmethodというパラメータを持ったHTTPのGETリクエストが送られています。

実際に送られるリクエスト

GET /model.json?paths=%5B%5B%22hello%22%5D%5D&method=get HTTP/1.1

それに対して返ってくるHTTPレスポンスは次のようになっています。サーバー側では上記のGETリクエストをハンドリングして、下記のようなレスポンスを返せるようにしなくてはなりません。

実際に返ってくるレスポンス

{"jsonGraph":{"hello":"hello!"}}

クライアント側のFalcorライブラリが生成するHTTPリクエストはとても複雑で、これをハンドリングして正しいJSONレスポンスを返せと言われても大変です。ただ、その実装については、サーバーサイド側でリクエストのパラメータをパースするライブラリなどもnpmで公開されているので、いくらかは仕事を楽にしてくれます。

そうしたライブラリを利用したサーバーサイド側の実装例をこのシリーズでは紹介していきたいと思っていますが、まずはサーバーサイド側の概要を見ていきましょう。

サーバーサイドに必要なもの

Netflixが公開している、サーバーサイドで利用するためのFalcor関連ライブラリは2種類あります。

  • falcor-router:サーバーサイド側でのData Source*実装を支援するライブラリ

Netflix/falcor-router

  • falcor-express:WebフレームワークであるExpressにFalcorのData Sourceを載せられるようにするミドルウェア

Netflix/falcor-express

*注:Data Source

Data SourceはFalcorクライアントからのリクエストに対して実際にデータ(JSON Graph)を返す役割を持ちます。過去の記事でも紹介をしています。

厳密なイメージではないが、役割としてはfalcor-routerはデータを返すData Sourceの実装部分で、それとExpressを繋いでくれるのがfalcor-express。

この2つのライブラリは両方とも必須となるでしょう。falcor-routerは後述するfalcor-expressから受け取ったリクエストを元にして構築したデータを、最終的にExpressのレスポンスとして正しく送り返すため、まずはリクエストを繋いでくれたfalcor-expressへ手渡すためのインターフェイスを実装するために使います。

falcor-expressは、クライアント側で生成されるFalcor用のパラメータが付いたHTTPリクエストをパースしてfalcor-router側に渡した後、その処理結果を受け取り次第、Expressに戻してあげる役割を持っています。

このfalcor-expressと同じ役割で他のWebフレームワークに対応したものとして、hapi用のfalcor-hapiや、restify用のfalcor-restifyもあります。このシリーズではExpressを使うことを前提として進めていきます。

コラム:Node.jsは必須か?

いまのところ、こうしたライブラリはnpmで公開されており、Falcorのサーバーサイド側実装を行うためにはNode.jsを選択したほうがよいということになります。

これは、必ずしもサーバーサイドのすべてをNode.jsで実装すべきということを意味するものではありません。

たとえば、すでにRubyなどの他の言語でREST APIが実装済みであるのなら、これをそのまま利用して、既存のサーバーサイドとクライアントサイドの間にFalcorサーバーを配置するという方法もあります。

この場合、Falcorサーバーはフロントエンドのためのバックエンドとして働きます。クライアント側では、Falcorサーバーへのリクエストを行い、Falcorサーバーはまた別のAPIへリクエストを行ってデータを返します。

Expressのセットアップ

それでは、Express*のセットアップを進めていきましょう。本記事はFalcorのシリーズなので、Express自体については深くは触れません。Expressを動かすためのコードは必要最低限のものしか登場しませんが、考え方、仕組みなど、Expressについてある程度知っておくこともおすすめします。

*注:Express

Expressは過去のCodeGrid記事でも紹介をしていますので、参考にしてください。

サンプルリポジトリ

今回紹介する最終的なコードは以下より入手できます。

Falcorサーバーサイド サンプルリポジトリ

サンプルコードはNode.jsのバージョン4.4.5で動作を確認しています。また、ブラウザとそのバージョンはChrome 51、Firefox 47での動作を確認しています。

最低限のExpressサーバー

Falcor用のコードは何も入っていない、Expressのサーバーを立ち上げるだけのコードです。これをベースにしてfalcor-express、falcor-routerを繋いでいきます。

Expressの他にbody-parserも入っていますが、これはFalcorクライアントのリクエストパラメータを読み取るために必要となります。

server.js

'use strict';
const express = require('express');
const bodyParser = require('body-parser');

const app = express();

app.use(bodyParser.urlencoded({extended: true}));
app.use(express.static('./'));

const server = app.listen((process.env.PORT || 3000), (err) => {
  if (err) {
    console.error(err);
    return;
  }
  console.log(`app start, on port ${server.address().port} in ${app.get('env')} mode ${Date().toString()}`);
});

falcor-express

falcor-expressはExpressのmiddleware*として機能します。

*注:middleware

ここでいうmiddlewareとは、リクエストに対する処理をプラグインのように追加できるExpressの機能です。npmでいろいろなmiddlewareが公開されており、falcor-expressもそうしたmiddlewareのうちのひとつです。

次のようにmiddlewareをマウントするapp.useメソッドを使ってfalcor-expressをExpressにマウントします。このときに、falcor-expressの初期化も行う必要があります。そのためにはfalcorExpress.dataSourceRouteメソッドを呼び出します。

falcorExpress.dataSourceRouteメソッドは関数を引数として受け取ります。

その関数内でやらないといけないのは、クライアントからのリクエストに応えるインターフェースを持ったData Sourceをreturnすることです。こうすることで、ExpressとData Sourceはfalcor-express middlewareの助けによって繋がることができます。

その結果として、ExpressがFalcorクライアントに応答できるようになります(以下のサンプルコードではこの部分はまだTODOの扱いになっています)。

falcor-expressのマウント

const falcorExpress = require('falcor-express');

// 中略

app.use(bodyParser.urlencoded({extended: true}));
app.use('/model.json', falcorExpress.dataSourceRoute((req, res) => {
  // TODO: FalcorRouterインスタンスを返す
}));
app.use(express.static('./'));

// 後略

falcor-router

次はfalcor-routerの準備です。一緒に簡単な読み取り用の値も返せるような実装もしてみましょう。

falcor-routerの準備

const FalcorRouter = require('falcor-router');

// 中略

app.use(bodyParser.urlencoded({extended: true}));
app.use('/model.json', falcorExpress.dataSourceRoute((req, res) => {
  return new FalcorRouter([
    {
      route: 'hello',
      get() {
        return {
          path: ['hello'],
          value: 'hello!'
        };
      }
    }
  ]);
}));
app.use(express.static('./'));

// 後略

ひとつ前のコード例で// TODOにしていた部分を書き換えました。return new FalcorRouter以下です。ここではFalcorRouterインスタンスを初期化してreturnしています。渡している引数はデータの在り処であるPathを示すrouteとそれに対するgetsetcallいずれかのアクションを定義したオブジェクトの配列です。

上記のコードではhelloというキーの取得にのみ対応した処理をひとつだけ定義しています。これでクライアント側のmodel.get('hello')というリクエストに対応できるようになるわけです。

getメソッドの中では、最終的にpathvalueを持ったオブジェクトを返す必要があります。

pathには返す値がJSON Graph内のどこに存在しているかを示したPathを渡します。helloというキーのリクエストに対応するので、ここはhelloになります。また、配列書式でのPathを渡すことが必須となっているので、**['entries', 0, 'title']のような階層指定がない場合でも必ず['hello']のような配列書式での指定をします**。

そしてvalueにはそのPathに存在する値を指定します。

完成したコード全体は次のようになっています。

このrouteとアクションの定義をたくさん書いていくことによって、豊かなデータを提供するData Sourceを実装することができます。

Expressの動作確認

これで最低限の実装が終わりました。クライアント側から動作確認してみましょう。

先ほどhelloというPathのデータ取得に対応できるようにしたので、それをリクエストしてみます。

client.js

const model = new falcor.Model({
  source: new falcor.HttpDataSource('/model.json')
});

model.get('hello').then(res => {
  console.log(JSON.stringify(res, null, 2));
});

次のようなデータが返ってきたら成功です。

コンソールの結果

{
  "json": {
    "hello": "hello!"
  }
}

まとめ

今回はFalcorのサーバーサイド側実装の概要と、単純なPathのリクエストに対応する例を紹介しました。

Falcorのクライアントサイド側を扱っていたシリーズでも解説したように、Pathは実際にはmodel.get(['entries', 0, 'title'])のように任意のインデックス値を指定できたりもします。

そうしたPathへの対応も、次回紹介していきたいと思います。