LoginSignup
19
10

More than 5 years have passed since last update.

ElmでJSON APIを叩く最低限

Last updated at Posted at 2019-03-05

ElmでWebサービス的なものを作るとなった時にAPIサーバとの通信は必須になってくる。
普段JSのaxiosを使っている自分は割と手間取ったのでメモっておく。
基本的にはElm GuideHTTP JSON 項目の内容をなぞっているだけ。

想定読者

  • The Elm Architecture(Model,View,Update)の概要をなんとなく理解している
  • Cmdの使い方・挙動をなんとなく理解している

バージョン

  • Elm 0.19

ソース

Github

yarn server でjson-serverを動かした状態で yarn start でフロントエンド環境を立ち上げると動作確認できるようになっている。

デモ

mjdh6-1033x.gif

Http.get/postとHttp.requestの違い

HTTPパッケージのドキュメントを見ると get post 関数と request 関数がある。
一見、 get post の方がシンプルに使えて良さそうに見えるが、リクエストヘッダーの設定ができないっぽい。
実際には request を多用することになりそう。

APIリクエストの流れ

実際にAPIを叩くコードを見ていく。

リクエスト関数

json-serverで返ってくる値は次のように設定した。

{
  "articles": [
    {
      "id": 1,
      "title": "json-server",
      "author": "typicode"
    },
    {
      "id": 2,
      "title": "faker.js",
      "author": "Marak"
    }
  ],
  "profile": {
    "name": "typicode"
  }
}

まずはprofileのリソースを取得する。
次のようにリクエスト関数を定義。

getProfile : Cmd Msg
getProfile =
    Http.request
        { method = "GET"
        , headers =
            [ Http.header "Authorization" ("Bearer " ++ "TOKEN")
            , Http.header "Accept" "application/json"
            , Http.header "Content-Type" "application/json"
            ]
        , url = "http://localhost:3000/profile"
        , expect = Http.expectJson GotProfile profileDecoder
        , body = Http.emptyBody
        , timeout = Nothing
        , tracker = Nothing
        }

headers Authorizationは認証が必要という想定で適当な値を設定してある。
url 叩くエンドポイントを設定。
body 空なので Http.emptyBody を渡している。
expect Http.expectJson はレスポンス結果がJSONであることを期待する。引数としてレスポンスが返ってきた後のJSONデコーダー関数とデコードされた値を受け取るUpdate関数を渡す。
timeout タイムアウト。特に設定したくないのでNothingとしている。

tracker については何を設定する項目なのかわかっていない。
ドキュメントによると、

The tracker lets you cancel and track requests.
トラッカーを使用すると、リクエストをキャンセルして追跡できます。

やはりちょっとわからないが、ひとまずNothingとしている。

JSONデコーダー

/profile を叩くと

{
  "name": "typicode"
}

が返ってくる。
これに対しては次のようにデコーダーを定義する。

profileDecoder : Decoder String
profileDecoder =
    field "name" string

field 関数の第一引数にキー名、第二引数にDecoder関数を渡す。
今回は文字列型なので Json.Decode.string 関数を渡す。
特にこのあたりは使い方を知っているだけで理解が怪しい点。

Update関数

作成したリクエスト関数はUpdateの中で使う。
Msgの型は下のように定義。

type Msg
    = GetProfile
      GotProfile (Result Http.Error String)

Result型は1つ目に失敗時のエラーの型、2つ目に成功時に受け取る値の型を定義する。

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        GetProfile ->
            ( model
            , getProfile
            )

        GotProfile result ->
            case result of
                Ok name ->
                    ( { model | name = name }, Cmd.none )

                Err _ ->
                    ( model, Cmd.none )

ViewからはUpdate関数である GetProfile を呼ぶ。
GetProfile はリクエスト関数の getProfile を呼ぶ。
getProfile でAPIへのリクエストが行われ、受け取ったレスポンスをデコードして GotProfile へ渡す。
GotProfile ではデコードされた値が渡ってくるので、その値をModelに格納する。

JSONが配列で返ってくる場合

/articles を叩くと、

[
  {
    "id": 1,
    "title": "json-server",
    "author": "typicode"
  },
  {
    "id": 2,
    "title": "faker.js",
    "author": "Marak"
  }
]

このように配列が返ってくる。
Elm的にはListとして受け入れる必要がある。

ListのデコードはElm Guideでは記載されていないが、ググって出てきたスタックオーバーフローの回答がかなり参考になった。

考え方としてはまずListでない単一要素のデコーダーを定義する。
続いてそのデコーダーをベースにListにデコーダーを定義する。

getArticles : Cmd Msg
getArticles =
    Http.request
        { method = "GET"
        , headers =
            [ Http.header "Authorization" ("Bearer " ++ "TOKEN")
            , Http.header "Accept" "application/json"
            , Http.header "Content-Type" "application/json"
            ]
        , url = "http://localhost:3000/articles"
        , expect = Http.expectJson GotArticles articlesDecoder -- ③Listのデコーダーを渡す
        , body = Http.emptyBody
        , timeout = Nothing
        , tracker = Nothing
        }


articleDecoder : Decoder Article  -- ①まず単一のデコーダーを定義
articleDecoder =
    Json.Decode.map3 Article
        (Json.Decode.field "id" Json.Decode.int)
        (Json.Decode.field "title" Json.Decode.string)
        (Json.Decode.field "author" Json.Decode.string)


articlesDecoder : Decoder (List Article) -- ②上を基にListのデコーダーを定義
articlesDecoder =
    Json.Decode.list articleDecoder

デコーダーが定義できてしまえば後は同じで、あまり迷わず実装できた。

type Msg
    = GetProfile
      GotProfile (Result Http.Error String)
      GetArticles
      GotArticles (Result Http.Error (List Article))

Update関数の引数もList型で定義しておく。

参考

https://guide.elm-lang.org/effects/json.html
https://package.elm-lang.org/packages/elm/http/latest/Http
https://stackoverflow.com/questions/39320948/decode-json-array-with-objects-in-elm

19
10
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
19
10