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>
}
}
}
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)
}
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) {
...
}
}
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.") }
}
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()
}
}
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>
}
}
}
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)