DEV Community

Cover image for Mint ๐Ÿƒ: Handling HTTP Requests
Szikszai Gusztรกv
Szikszai Gusztรกv

Posted on • Updated on

Mint ๐Ÿƒ: Handling HTTP Requests

This is the fourth post in a series that showcases the features of Mint, you can find the previous posts here:

In this post I will show you how to make HTTP requests to an API :)

The Code

This is the full source code to fetch planets from the Star Wars API and display it in a table.

record Planet {
  population : String,
  gravity : String,
  climate : String,
  name : String,
}

record Data {
  results : Array(Planet),
  count : Number,
}

enum Status {
  Initial
  Loading
  Error(String)
  Ok(Data)  
}

store StarWars {
  state status : Status = Status::Initial

  fun load : Promise(Never, Void) {
    sequence {
      next { status = Status::Loading }

      response =
        "https://swapi.co/api/planets"
        |> Http.get()
        |> Http.send()

      object = 
        response.body
        |> Json.parse()
        |> Maybe.toResult("")

      decodedResults = 
        decode object as Data

      next { status = Status::Ok(decodedResults) }
    } catch Http.ErrorResponse => error {
      next { status = Status::Error("Something went wrong with the request.") }
    } catch Object.Error => error {
      next { status = Status::Error("The data is not what is expected.") }
    } catch String => error {
      next { status = Status::Error("Invalid JSON data.") }
    }
  }
}

routes {
  * {
    StarWars.load() 
  }
}

component Main {
  connect StarWars exposing { status } 

  fun render : Html {
    case (status) {
      Status::Initial => <div></div>
      Status::Loading => <div>"Loading..."</div>
      Status::Error message => <div><{ message }></div>
      Status::Ok data =>
        <table>
          <tr>
            <th>"Name"</th>
            <th>"Climate"</th>
            <th>"Gravity"</th>
            <th>"Population"</th>
          </tr>

          for (planet of data.results) {
            <tr>
              <td><{ planet.name }></td>
              <td><{ planet.climate }></td>
              <td><{ planet.gravity }></td>
              <td><{ planet.population }></td>
            </tr>
          }
      </table>
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

I will now explain it to you block by block.

Modelling the data

In any typed programming language, the structure of data must be defined somehow:

record Planet {
  population : String,
  gravity : String,
  climate : String,
  name : String,
}

record Data {
  results : Array(Planet),
  count : Number,
}

enum Status {
  Initial
  Loading
  Error(String)
  Ok(Data)  
}
Enter fullscreen mode Exit fullscreen mode

In Mint there are two constructs for defining data:

  • record - which defines an object with fixed named key / value pairs
  • enum - which defines an ADT - a type which represents a fix set of possibilities

In our example Planet and Data defines the data that comes from the API and the Status defines the possible states of the request.

Defining the state

In Mint, global state is stored in a store (insert Nicolas Cage meme here) which is globally accessible and basically works like a Component where state is concerned. (state and next keywords from the last article)

store StarWars {
  state status : Status = Status::Initial

  fun load : Promise(Never, Void) {
    ...
  }
}
Enter fullscreen mode Exit fullscreen mode

Handling the request

The handling of an HTTP request is done in a sequence block, which runs each expression in it asynchronously in sequence (Cage again) in the order they are written.

What this means that it will await all promises Promise(error, value) and unbox the value in a variable for subsequent use or raise the error which is handled in a catch block.

sequence {
  next { status = Status::Loading }

  response =
    "https://swapi.co/api/planets"
    |> Http.get()
    |> Http.send()

  object = 
    response.body
    |> Json.parse()
    |> Maybe.toResult("")

  decodedResults = 
    decode object as Data

  next { status = Status::Ok(decodedResults) }
} catch Http.ErrorResponse => error {
  next { status = Status::Error("Something went wrong with the request.") }
} catch Object.Error => error {
  next { status = Status::Error("The data is not what is expected.") }
} catch String => error {
  next { status = Status::Error("Invalid JSON data.") }
}
Enter fullscreen mode Exit fullscreen mode

The Http module contains functions to make Http.get(url : String) and send Http.send(request : Http.Request) HTTP requests.

The next part is to parse the JSON content into an Object and then decode it to the type we defined earlier, then we set the status to Status::Ok or Status::Error according to what happened.

Routing

Mint has a built in system for handling routes which will be featured in a different article.

In our case we define the * route which handles all non-handled routes, in the route we just load the data, which in practice means when the page is loaded:

routes {
  * {
    StarWars.load() 
  }
}
Enter fullscreen mode Exit fullscreen mode

Displaying the data

The last part is to display the data which we will do in the Main component:

component Main {
  connect StarWars exposing { status } 

  fun render : Html {
    case (status) {
      Status::Initial => <div></div>
      Status::Loading => <div>"Loading..."</div>
      Status::Error message => <div><{ message }></div>
      Status::Ok data =>
        <table>
          <tr>
            <th>"Name"</th>
            <th>"Climate"</th>
            <th>"Gravity"</th>
            <th>"Population"</th>
          </tr>

          for (planet of data.results) {
            <tr>
              <td><{ planet.name }></td>
              <td><{ planet.climate }></td>
              <td><{ planet.gravity }></td>
              <td><{ planet.population }></td>
            </tr>
          }
      </table>
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

To get the data from the store, first we need to connect the component to it using the connect keyword and expose the status state so it can be used in the scope of the component.

Connecting a component to a store makes it so that the component re-renders when the data in the store changes.

Then based on the status we render different content:

  • Status::Initial - we display nothing
  • Status::Loading - we display a loading message
  • Status::Error message - we display the error message
  • Status::Ok data - we display the data

And there you have it, thank you for reading ๐Ÿ™:


If you like to learn more about Mint check out the guide ๐Ÿ“–

In the next part I'm going to show how to style elements with CSS ๐Ÿ˜‰ see you there ๐Ÿ‘‹

Top comments (0)