16
votes

I have a fair amount of experience building web apps with React but want to learn Elm. I've been banging my head against an HTTP request issue for a couple days now.

I'm running an Elm app at localhost:8080 and my supporting API at localhost:8081. Whenever I make an HTTP request (I've tried both GET and POST requests) I get a NetworkError. I've been looking into Elm JSON decoders and think it's possible that this is where my problem exists but I've tried sending simple strings from my server and using the Decode.string decoder in my Elm app and I still get the NetworkError.

Here's what my code looks like currently:

Commands.elm

module Commands exposing (..)

import Models exposing (..)
import Msg exposing (..)
import Http
import Json.Decode as Decode
import Json.Encode as Encode


createTempUser : Model -> Cmd Msg
createTempUser model =
  let
    tempUserBody =
      [ ( "firstname", Encode.string model.firstname )
      , ( "lastname", Encode.string model.lastname )
      , ( "phone", Encode.string model.phone )
      ]
        |> Encode.object
        |> Http.jsonBody

    url =
      myAPIUrl ++ "/endpoint"

    contentType = Http.header "Content-type" "text/plain"

    post =
      Http.request
        { method = "POST"
        , headers =  [contentType]
        , url = url
        , body = tempUserBody
        , expect = Http.expectJson decodeApiResponse
        , timeout = Nothing
        , withCredentials = False
        }

  in
    Http.send Msg.TempUserCreated post


decodeApiResponse : Decode.Decoder ApiResponse
decodeApiResponse =
  Decode.map4 ApiResponse
    (Decode.at ["status"] Decode.int)
    (Decode.at ["message"] Decode.string)
    (Decode.at ["created"] Decode.int)
    (Decode.at ["error"] Decode.string)


myAPIUrl : String
myAPIUrl = "http://localhost:8081"

Models.elm

module Models exposing (..)


type alias ApiResponse =
  { status: Int
  , message: String
  , created: Int
  , error: String
  }

Msg.elm

module Msg exposing (..)

import Navigation exposing (Location)
import Models exposing (..)
import Http


type Msg
  = ChangeLocation String
  | OnLocationChange Location
  | CreateTemporaryUser
  | TempUserCreated ( Result Http.Error ApiResponse )

Update.elm

module Update exposing (..)

import Msg exposing (Msg)
import Models exposing (..)
import Routing exposing (..)
import Commands exposing (..)
import Navigation

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
  case msg of
    Msg.ChangeLocation path ->
      ( { model | changes = model.changes + 1 }, Navigation.newUrl path )

    Msg.OnLocationChange location ->
      ( { model | error = "", route = parseLocation(location) }, Cmd.none )

    Msg.CreateTemporaryUser ->
      ( model, createTempUser model )

    Msg.TempUserCreated (Ok res) ->
       update (Msg.ChangeLocation signupCodePath) { model | httpResponse = toString(res) }

    Msg.TempUserCreated (Err err) ->
      ( { model | error = toString(err) }, Cmd.none )

Chrome's Network devtools show the response as this

{"status":200,"message":"Successfully inserted temporary 
user","created":1518739596447,"error":""}

I think this may be all the relevant code but if there's more you need to see I'll make an update including the requested code. I'll admit that I don't have a full understanding of the Elm Json.Decode library but I was under the impression that if this was where the issue was I would get an UnexpectedPayload Error that included additional context.

2
So, the Chrome Devtools show that you're actually getting a successful response, but Elm reports a NetworkError? How do you know you're getting a NetworkError? - Sidney
In my update Msg.TempUserCreated (Err err) I save the error string to my model and I'm displaying my model in the app view so I can see that the model updates with model.error = "NetworkError" when I make the request - Michael Fox
I loaded up the repo myself. Since I don't have the API server running on :8081, every request failed, as you'd expect. But I never actually saw a NetworkError in the model! The error in the model was always empty. I added a Debug.log and sure enough, I was actually getting a NetworkError. Without the API server, I probably won't be able to recreate the error. - Sidney
Isee you return 200 in some json object, but are you getting a 200 in the http headers themselves? - Simon H
@SimonH good question, it's possible the server is actually sending a different status. Although theoretically a non-success status should cause a BadStatus error rather than a NetworkError - Sidney

2 Answers

13
votes

@Sidney and @SimonH,

Thank you for looking into this for me, I feel bad because this wasn't an Elm issue after all.

The solution ended up using a CORS middleware on the server to add the 'Access-Control-Allow-Origin' header. The server was still responding with a 200 status and even included the entire successful response body but this resulted in a failure in Elm's Http Result.

Thank you again for your time and thought!

1
votes

For a quick solution while developing you can use the CORS everywhere extension for firefox and add the following to your whitelist in the extension preferences:

/^https?...localhost:8888\//i

For chrome you can probably use this extension