Send data from Elm to Flask

My background is python using pandas for data wrangling and analysis purposes. Now I want to use Elm as a frontend to enable user interaction.

My idea is to use

  • Elm as frontend (just have a nice user interface with inputs and drop downs etc.)
  • Flask / Pyhton as backend (all the data wrangling etc.)

Given I am just experimenting and I lack experience using “web apps” I am really struggling to have elm and flask communicate with each other. Unfortunately, I cannot find any good and simple examples anywhere. This may, however, also be due to my lack of understanding.

I started to build a very simplified webapp that should be able to do the following:

  1. input a value in an input field
  2. sent this input to the flask server (http.post?)
  3. have python transform the input
  4. give the transformed input back and show it in the output field (http.get?)

My below code already fails at the second step. Given my lack of experience I don’t even know if the issue is at the Elm end or in the flask end. The Elm code compiles correctly.

module SimpleTest exposing (..)

import Html exposing (..)
import Html.Attributes as Attr
import Html.Events exposing (onClick, onInput)
import Browser
import Http
import Json.Decode as Decode exposing (string, Decoder)
import Json.Encode as Encode

type alias Model =
    { input : String
    , output : String
    , errorMessage : String 
    }

initialModel : Model
initialModel = 
    { input = ""
    , output = ""
    , errorMessage = "No Error"
    }

view : Model -> Html.Html Msg
view model =
    div [][ div [][ text "Input"
                  , input [ Attr.name "InputField"
                          , onInput GetInput 
                          ][]
                  , button [Attr.name "Button", onClick PressedOk][text "OK"]
                  ]
          , div [][text ""]
          , div [][text "Output"
                  , input [Attr.name "OutputField"][]
                ]
          , h2 [][text "Debugger"]
          , (text << Debug.toString) model
          ]

type Msg = 
    PressedOk
    | GetInput String
    | SentInput (Result Http.Error String)
    | ReceiveOutput

inputEncoder : String -> Encode.Value
inputEncoder inputStr = 
    Encode.object [("input", Encode.string inputStr)]

outputDecoder : Decoder String
outputDecoder = 
    Decode.field "output" Decode.string

sentData : String -> Cmd Msg
sentData inputStr = 
    Http.post 
    { url = "http://localhost:8989/data"
    , body = Http.jsonBody (inputEncoder inputStr) 
    , expect = Http.expectString SentInput

    }
    


update : Msg -> Model -> (Model, Cmd Msg)
update msg model = 
    case msg of
        GetInput newInput-> 
            ({model | input = newInput}, Cmd.none)
        PressedOk -> 
            (model, sentData model.input)
        SentInput inputData ->
            case inputData of
                Ok newOutput ->  
                    ({model | output = newOutput }, Cmd.none)
                Err error -> 
                    ({model | errorMessage = Debug.toString error}, Cmd.none)
        ReceiveOutput -> 
            (model, Cmd.none)

init : () -> (Model, Cmd Msg)
init _ = 
    (initialModel, Cmd.none)

main : Program () Model Msg
main = 
    Browser.element
        { init = init
        , view = view
        , update = update
        , subscriptions = \_ -> Sub.none 
        }

Could someone point me in the right direction how to achieve the four steps mentioned above?

Thanks for your help and your patience with a Newbie.

How does it fails exactly? What is the error? Can you look into the developer console and see if the call goes through? Do you receive a SentInput message back?

Later Edit, it looks like you wrote a decoder for the response from the server but you are not using it.

Maybe you should have:

sentData : String -> Cmd Msg
sentData inputStr = 
    Http.post 
    { url = "http://localhost:8989/data"
    , body = Http.jsonBody (inputEncoder inputStr) 
    , expect = Http.expectJson SentInput outputDecoder

    }
    

I’ve not used Python before, but the following works with an Elixir/Phoenix backend.

I’ve used Elm-UI for the view, it’s well worth looking into.

As @pdamoc mentioned you’re not using your outputDecoder and you’re also not using your ReceiveOutput Msg. It’s in your update function but it won’t ever be used. It’s not actually necessary as you’ll be receiving the response to your post request in your SentData Msg.

Hope this can put you on the right course, it should work for you as-is (after installing Elm-UI) if you just switch the url to your flask endpoint. If it doesn’t I would suggest your problem is your backend.

module Main exposing (main)

import Browser
import Element as El
import Element.Input as Input
import Html
import Http
import Json.Decode as JD
import Json.Encode as JE



-- INIT


init : () -> ( Model, Cmd Msg )
init _ =
    ( initialModel, Cmd.none )



-- MODEL


type alias Model =
    { input : String
    , output : String
    , error : String
    }


initialModel : Model
initialModel =
    { input = ""
    , output = ""
    , error = ""
    }



-- UPDATE


type Msg
    = ChangedInput String
    | SendData
    | ReceivedData (Result Http.Error String)


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        ChangedInput input ->
            ( { model | input = input }
            , Cmd.none
            )

        SendData ->
            ( model
            , sendData model.input
            )

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

                Err error ->
                    ( { model | error = decodeError error }
                    , Cmd.none
                    )


sendData : String -> Cmd Msg
sendData input =
    Http.post
        { url = "/"
        , body = Http.jsonBody (inputEncoder input)
        , expect = Http.expectJson ReceivedData outputDecoder
        }



-- ENCODER


inputEncoder : String -> JE.Value
inputEncoder input =
    JE.object [ ( "input", JE.string input ) ]



-- DECODERS


outputDecoder : JD.Decoder String
outputDecoder =
    JD.field "output" JD.string


decodeError : Http.Error -> String
decodeError error =
    case error of
        Http.BadUrl err ->
            err

        Http.Timeout ->
            "Timeout"

        Http.NetworkError ->
            "Network Error"

        Http.BadStatus status ->
            "Error " ++ String.fromInt status

        Http.BadBody err ->
            err



-- VIEW


view : Model -> Html.Html Msg
view model =
    El.layout
        [ El.height El.fill
        , El.width El.fill
        ]
        (El.column
            [ El.centerX
            , El.centerY
            , El.spacing 20
            ]
            [ El.row
                [ El.spacing 20 ]
                [ Input.text
                    []
                    { onChange = ChangedInput
                    , text = model.input
                    , label =
                        Input.labelHidden
                            "Input"
                    , placeholder =
                        Just <|
                            Input.placeholder
                                []
                                (El.text "Input")
                    }
                , Input.button
                    []
                    { label = El.text "Click"
                    , onPress = Just SendData
                    }
                ]
            , El.text "Output"
            , El.text model.output
            , El.text "Error"
            , El.text model.error
            ]
        )



-- MAIN


main : Program () Model Msg
main =
    Browser.element
        { init = init
        , view = view
        , update = update
        , subscriptions = \_ -> Sub.none
        }

Thanks for your help! I am having a hard time debugging given I don’t know where to look for errors. With your hint (i.e. developer console) I was able to identify the issue. It was 405 and I realized the issue was in the backend.

Thanks

1 Like

Thanks for your response. It is now working. With your code I could be sure the frontend is not the problem and I found the backend issue.

Thanks!

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.