dillonkearns/elm-pages/7.0.0

NOTE: the latest version is 12.3.0

Pages.StaticHttp

StaticHttp requests are an alternative to doing Elm HTTP requests the traditional way using the elm/http package.

The key differences are:

  • StaticHttp.Requests are performed once at build time (Http.Requests are performed at runtime, at whenever point you perform them)
  • StaticHttp.Requests strip out unused JSON data from the data your decoder doesn't touch to minimize the JSON payload
  • StaticHttp.Requests can use Pages.Secrets to securely use credentials from your environment variables which are completely masked in the production assets.
  • StaticHttp.Requests have a built-in StaticHttp.andThen that allows you to perform follow-up requests without using tasks

Scenarios where StaticHttp is a good fit

If you need data that is refreshed often you may want to do a traditional HTTP request with the elm/http package. The kinds of situations that are served well by static HTTP are with data that updates moderately frequently or infrequently (or never). A common pattern is to trigger a new build when data changes. Many JAMstack services allow you to send a WebHook to your host (for example, Netlify is a good static file host that supports triggering builds with webhooks). So you may want to have your site rebuild everytime your calendar feed has an event added, or whenever a page or article is added or updated on a CMS service like Contentful.

In scenarios like this, you can serve data that is just as up-to-date as it would be using elm/http, but you get the performance gains of using StaticHttp.Requests as well as the simplicity and robustness that comes with it. Read more about these benefits in this article introducing StaticHttp requests and some concepts around it.

Scenarios where StaticHttp is not a good fit

  • Data that is specific to the logged-in user
  • Data that needs to be the very latest and changes often (for example, sports scores)
type alias Request value =
Request value

A Request that will be made a build time. Multiple StaticHttp.Requests can be combined together using the mapN functions, very similar to how you can manipulate values with Json Decoders in Elm.

type alias RequestDetails =
{ url : String
, method : String
, headers : List ( String, String )
, body : Body
}

The full details to perform a StaticHttp request.

get : Value String -> Decoder a -> Request a

A simplified helper around StaticHttp.request, which builds up a StaticHttp GET request.

import Json.Decode as Decode exposing (Decoder)
import Pages.StaticHttp as StaticHttp


getRequest : StaticHttp.Request Int
getRequest =
    StaticHttp.get
        (Secrets.succeed "https://api.github.com/repos/dillonkearns/elm-pages")
        (Decode.field "stargazers_count" Decode.int)
request : Value RequestDetails -> Decoder a -> Request a

Build a StaticHttp request (analagous to Http.request). This function takes in all the details to build a StaticHttp request, but you can build your own simplified helper functions with this as a low-level detail, or you can use functions like StaticHttp.get.

map : (a -> b) -> Request a -> Request b

Transform a request into an arbitrary value. The same underlying HTTP requests will be performed during the build step, but mapping allows you to change the resulting values by applying functions to the results.

A common use for this is to map your data into your elm-pages view:

import Json.Decode as Decode exposing (Decoder)
import Pages.StaticHttp as StaticHttp


view =
    StaticHttp.get
        (Secrets.succeed "https://api.github.com/repos/dillonkearns/elm-pages")
        (Decode.field "stargazers_count" Decode.int)
        |> StaticHttp.map
            (\stars ->
                { view =
                    \model viewForPage ->
                        { title = "Current stars: " ++ String.fromInt stars
                        , body = Html.text <| "⭐️ " ++ String.fromInt stars
                        , head = []
                        }
                }
            )
succeed : a -> Request a

This is useful for prototyping with some hardcoded data, or for having a view that doesn't have any StaticHttp data.

import Pages.StaticHttp as StaticHttp


view :
    List ( PagePath Pages.PathKey, Metadata )
    ->
        { path : PagePath Pages.PathKey
        , frontmatter : Metadata
        }
    ->
        StaticHttp.Request
            { view : Model -> View -> { title : String, body : Html Msg }
            , head : List (Head.Tag Pages.PathKey)
            }
view siteMetadata page =
    StaticHttp.succeed
        { view =
            \model viewForPage ->
                mainView model viewForPage
        , head = head page.frontmatter
        }
fail : String -> Request a

Stop the StaticHttp chain with the given error message. If you reach a fail in your request, you will get a build error. Or in the dev server, you will see the error message in an overlay in your browser (and in the terminal).

Building a StaticHttp Request Body

The way you build a body is analagous to the elm/http package. Currently, only emptyBody and stringBody are supported. If you have a use case that calls for a different body type, please open a Github issue and describe your use case!

type alias Body =
Body

A body for a StaticHttp request.

emptyBody : Body

Build an empty body for a StaticHttp request. See elm/http's Http.emptyBody.

stringBody : String -> String -> Body

Builds a string body for a StaticHttp request. See elm/http's Http.stringBody.

Note from the elm/http docs:

The first argument is a MIME type of the body. Some servers are strict about this!

jsonBody : Value -> Body

Builds a JSON body for a StaticHttp request. See elm/http's Http.jsonBody.

Chaining Requests

andThen : (a -> Request b) -> Request a -> Request b

Build off of the response from a previous StaticHttp request to build a follow-up request. You can use the data from the previous response to build up the URL, headers, etc. that you send to the subsequent request.

import Json.Decode as Decode exposing (Decoder)
import Pages.StaticHttp as StaticHttp


licenseData : StaticHttp.Request String
licenseData =
    StaticHttp.get
        (Secrets.succeed "https://api.github.com/repos/dillonkearns/elm-pages")
        (Decode.at [ "license", "url" ] Decode.string)
        |> StaticHttp.andThen
            (\licenseUrl ->
                StaticHttp.get (Secrets.succeed licenseUrl) (Decode.field "description" Decode.string)
            )
resolve : Request (List (Request value)) -> Request (List value)

Helper to remove an inner layer of Request wrapping.

combine : List (Request value) -> Request (List value)

Turn a list of StaticHttp.Requests into a single one.

import Json.Decode as Decode exposing (Decoder)
import Pages.StaticHttp as StaticHttp


type alias Pokemon =
    { name : String
    , sprite : String
    }


pokemonDetailRequest : StaticHttp.Request (List Pokemon)
pokemonDetailRequest =
    StaticHttp.get
        (Secrets.succeed "https://pokeapi.co/api/v2/pokemon/?limit=3")
        (Decode.field "results"
            (Decode.list
                (Decode.map2 Tuple.pair
                    (Decode.field "name" Decode.string)
                    (Decode.field "url" Decode.string)
                    |> Decode.map
                        (\( name, url ) ->
                            StaticHttp.get (Secrets.succeed url)
                                (Decode.at
                                    [ "sprites", "front_default" ]
                                    Decode.string
                                    |> Decode.map (Pokemon name)
                                )
                        )
                )
            )
        )
        |> StaticHttp.andThen StaticHttp.combine
map2 : (a -> b -> c) -> Request a -> Request b -> Request c

Like map, but it takes in two Requests.

view siteMetadata page =
    StaticHttp.map2
        (\elmPagesStars elmMarkdownStars ->
            { view =
                \model viewForPage ->
                    { title = "Repo Stargazers"
                    , body = starsView elmPagesStars elmMarkdownStars
                    }
            , head = head elmPagesStars elmMarkdownStars
            }
        )
        (get
            (Secrets.succeed "https://api.github.com/repos/dillonkearns/elm-pages")
            (Decode.field "stargazers_count" Decode.int)
        )
        (get
            (Secrets.succeed "https://api.github.com/repos/dillonkearns/elm-markdown")
            (Decode.field "stargazers_count" Decode.int)
        )
(value1 -> value2 -> value3 -> valueCombined)
-> Request value1
-> Request value2
-> Request value3
-> Request valueCombined
(value1 -> value2 -> value3 -> value4 -> valueCombined)
-> Request value1
-> Request value2
-> Request value3
-> Request value4
-> Request valueCombined
(value1 -> value2 -> value3 -> value4 -> value5 -> valueCombined)
-> Request value1
-> Request value2
-> Request value3
-> Request value4
-> Request value5
-> Request valueCombined
(value1 -> value2 -> value3 -> value4 -> value5 -> value6 -> valueCombined)
-> Request value1
-> Request value2
-> Request value3
-> Request value4
-> Request value5
-> Request value6
-> Request valueCombined
( value1
-> value2
-> value3
-> value4
-> value5
-> value6
-> value7
-> valueCombined
)
-> Request value1
-> Request value2
-> Request value3
-> Request value4
-> Request value5
-> Request value6
-> Request value7
-> Request valueCombined
( value1
-> value2
-> value3
-> value4
-> value5
-> value6
-> value7
-> value8
-> valueCombined
)
-> Request value1
-> Request value2
-> Request value3
-> Request value4
-> Request value5
-> Request value6
-> Request value7
-> Request value8
-> Request valueCombined
( value1
-> value2
-> value3
-> value4
-> value5
-> value6
-> value7
-> value8
-> value9
-> valueCombined
)
-> Request value1
-> Request value2
-> Request value3
-> Request value4
-> Request value5
-> Request value6
-> Request value7
-> Request value8
-> Request value9
-> Request valueCombined

Unoptimized Requests

Warning - use these at your own risk! It's highly recommended that you use the other request functions that make use of zwilias/json-decode-exploration in order to allow you to reduce down your JSON to only the values that are used by your decoders. This can significantly reduce download sizes for your StaticHttp requests.

unoptimizedRequest : Value RequestDetails -> Expect a -> Request a

This is an alternative to the other request functions in this module that doesn't perform any optimizations on the asset. Be sure to use the optimized versions, like StaticHttp.request, if you can. Using those can significantly reduce your asset sizes by removing all unused fields from your JSON.

You may want to use this function instead if you need XML data or plaintext. Or maybe you're hitting a GraphQL API, so you don't need any additional optimization as the payload is already reduced down to exactly what you requested.

Expect for unoptimized requests

type Expect value

Analgous to the Expect type in the elm/http package. This represents how you will process the data that comes back in your StaticHttp request.

You can derive ExpectUnoptimizedJson from ExpectString. Or you could build your own helper to process the String as XML, for example, or give an elm-pages build error if the response can't be parsed as XML.

expectString : (String -> Result String value) -> Expect value

Request a raw String. You can validate the String if you need to check the formatting, or try to parse it in something besides JSON. Be sure to use the StaticHttp.request function if you want an optimized request that strips out unused JSON to optimize your asset size.

If the function you pass to expectString yields an Err, then you will get at StaticHttpDecodingError that will fail your elm-pages build and print out the String from the Err.

request =
    StaticHttp.unoptimizedRequest
        (Secrets.succeed
            { url = "https://example.com/file.txt"
            , method = "GET"
            , headers = []
            , body = StaticHttp.emptyBody
            }
        )
        (StaticHttp.expectString
            (\string ->
                if String.toUpper string == string then
                    Ok string


                else
                    Err "String was not uppercased"
            )
        )
expectUnoptimizedJson : Decoder value -> Expect value

Handle the incoming response as JSON and don't optimize the asset and strip out unused values. Be sure to use the StaticHttp.request function if you want an optimized request that strips out unused JSON to optimize your asset size. This function makes sense to use for things like a GraphQL request where the JSON payload is already trimmed down to the data you explicitly requested.

If the function you pass to expectString yields an Err, then you will get at StaticHttpDecodingError that will fail your elm-pages build and print out the String from the Err.