A Crystal Mint Lemonade🍹🍃
Tags: mint, crystal, webdev, tutorial
Published: 2020-03-10 19:33:56

Hi everyone!👋 
It's been a while since my last post, but I have a good reason: I was on vacations! Yeah! 😁☀️🏖 

And now it is time for a new Mint/Crystal recipe: we are going to build a Client/Server application using [Mint](https://www.mint-lang.com/) for the frontend (this post) and [Crystal](https://crystal-lang.org/) for the backend (the next post)!

So let's start ... but first: 
may I have a Crystal Mint Lemonade? ☝️🍹🍃

# The Application

We are going to build an application that will list four (not 3, nor 5 but 4!) non-alcoholic summer drinks (as shown in the post [4 Refreshing Summer Drinks](https://www.fifteenspatulas.com/4-refreshing-summer-drinks/))

# The Frontend

In this post we are going to build the frontend of our application ... uh oh! wait! We don't have a name for our application ... mmm let me think ... it will be called: **Summer drinks! 🍹**

And as we already mention, we are going to build the frontend using [Mint](https://www.mint-lang.com/)!

We won't be doing a _step-by-step_ tutorial but instead we are going to show the highlights of the source code.

## The Structure

The project's structure is inspired by [Mint Realworld](https://github.com/mint-lang/mint-realworld) and is the following:

```
mint-front
  |
  |- public
  |- source
  |   |- components
  |   |- entities
  |   |- pages
  |   |- stores
  |   |- Main.mint
  |   |- Routes.mint
  |
  |- mint.json
```

## Routing

[Routing](https://www.mint-lang.com/guide/reference/routing) in Mint is really simple. For our application we need 3 routes:

`mint-front/source/Routes.mint`
```javascript
routes {
  / {
    Application.navigateTo(Page::Home)
  }

  /drinks {
    parallel {
      Application.navigateTo(Page::Drinks)
      Stores.Drinks.load()
    }
  }

  * {
    Application.navigateTo(Page::NotFound)
  }
}
```

What's important here is that when navigating to `/drinks`:
- we start loading the drinks 
- and **in parallel** we start rendering the view.

In the Application `store` we are going to save the current page:

`mint-front/source/stores/Application.mint`
```crystal
store Application {
  state page : Page = Page::Home

  fun navigateTo (page : Page) : Promise(Never, Void) {
    sequence {
      next { page = page}
      Http.abortAll()
    }
  }
}
```

And [the Main component](https://www.mint-lang.com/guide/getting-started/the-main-component) will be responsible for rendering the correct view given the current page:

`mint-front/source/Main.mint`
```crystal
component Main {
  connect Application exposing { page }

  fun render : Html {
    <Layout>
      case (page) {
        Page::Home =>
          <Pages.Home/>

        Page::Drinks =>
          <Pages.Drinks/>

        Page::NotFound =>
          <div>"Where am I?!"</div>
      }
    </Layout>
  }
}
```

## Entities

We will be working with just one entity: the `Drink` itself! Here's the definition and the way to create an empty one:

`mint-front/source/entities/Drink.mint`
```crystal
record Drink {
  id : Number,
  icon : String,
  name : String,
  url : String
}

module Drink {
  fun empty : Drink {
    {
      id = 0,
      icon = "",
      name = "",
      url = ""
    }
  }
}
```

## Requesting the drinks

Here's an excerpt of the function `#Stores.Drinks.load()` showing the `request` we send to the server:

`mint-front/source/stores/Drinks.mint`
```crystal
  fun load() : Promise(Never, Void) {
    sequence {
      next { status = Stores.Status::Loading }

      response = "https://demo5780178.mockable.io/drinks"
                |> Http.get()
                |> Http.header("Content-Type", "application/json")
                |> Http.send()

      newStatus = case (response.status) {
                    404 => Stores.Status::Error("Not Found")
                        =>  try {
                              /* parse JSON */
                              object = Json.parse(response.body)
                                      |> Maybe.toResult("")

                              /* JSON to Drinks */
                              drinks = decode object as Stores.Status.Drinks

                              Stores.Status::Ok(drinks)

                            } catch Object.Error => error {
                              Stores.Status::Error("Could not decode the response.")
                            } catch String => error {
                              Stores.Status::Error("Could not parse the response.")
                            }
                  }

      next { status = newStatus }

...
```

In `sequence`, we will:
- update the `status` to `loading`.
- send the `request` (**waiting** for the response).
- define the `new status` given the response. If the response was successful then we try to parse the `drinks` in the response.
- and finally,  we change the `status`.

Another important element here is how we implement the different `status`. We use [enums](https://www.mint-lang.com/guide/reference/enums) like this:

```crystal
enum Stores.Status(a) {
  Initial
  Loading
  Error(String)
  Ok(a)
}
```

Notice how easy is to send the `request` and handle the response (`parse` and `decode` the `JSON` data)! 🤓

## Listing the drinks (the `Drinks` component)

This component will be responsible of showing the list of drinks. So first it needs to connect to the `store`:

`mint-front/source/components/Drinks.mint`

```crystal
component Drinks {
  connect Stores.Drinks exposing { status }

  ...
}
```

Then the rendering depends on the current status (here we only show the cases `Loading` and `Ok`):

```crystal
component Drinks {
  connect Stores.Drinks exposing { status }

  ...

  fun render : Html {
    case (status) {
      ...

      Stores.Status::Loading =>
        <div::base>
          <div::message>
            "Loading drinks..."
          </div>
        </div>

      ...

      Stores.Status::Ok =>
        <div>
          <{ drinksItems }>
        </div>
    }
  }
}
```

`drinkItems` and `drinks` are [computed properties](https://www.mint-lang.com/guide/reference/components/computed-properties) that _extract_ the data from the status:

```crystal
  get drinks : Array(Drink) {
    case (status) {
      Stores.Status::Ok data => data.drinks
      => []
    }
  }

  get drinksItems : Array(Html) {
    drinks
    |> Array.map((drink : Drink) : Html { <Drinks.Item drink={drink}/> })
    |> intersperse(<div::divider/>)
  }
```

Notice that each `drink` is rendered by the component `Drinks.Item`.

# The Full Client Application 🤓🍹

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/fqurrlcrmerwe3ibaw07.png)

Here is [the source code of the recipe](https://github.com/ftarulla/crystal-mint-drinks)! And remember that we run the application using: 🚀

```terminal-session
$ mint-lang start
Mint - Running the development server
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
⚙ Ensuring dependencies... 279μs
⚙ Parsing files... 19.301ms
⚙ Development server started on http://127.0.0.1:3000/
```

# Mocking the Backend

We still have not implemented the backend but we may use [Mockable.io](https://www.mockable.io/) to mock it. Here is the response we need:

```json
{
 "drinks": [{
    "id": 1,
    "icon": "🍓",
    "name": "Strawberry Limeade",
    "url": "https://www.youtube.com/watch?v=SqSZ8po1tmU"
 }, {
    "id": 2,
    "icon": "⛱",
    "name": "Melon Sorbet Float",
    "url": "https://www.youtube.com/watch?v=hcqMtASkn8U"
 }, {
    "id": 3,
    "icon": "🍨",
    "name": "Raspberry Vanilla Soda",
    "url": "https://www.youtube.com/watch?v=DkARNOFDnwA"
 }, {
    "id": 4,
    "icon": "🌴",
    "name": "Cantaloupe Mint Agua Fresca",
    "url": "https://www.youtube.com/watch?v=Zxz-DYSKcIk"
 }]
}
```

Also notice that the request URL is hardcoded in `mint-front/source/stores/Drinks.mint` 🙈 

# Farewell and see you later. Summing up.

We've reached the end of the recipe!👨‍🍳 We have implemented our second application in Mint🍃:
- using `stores` for saving the state of our application (`current page` and `drinks`)
- using `enums` to implement the different `status`.
- using `components` with conditional rendering (given the `current status`)

And remember that, in the next recipe, we will implement the server in Crystal! 💪🤓

Hope you enjoyed it! Until next recipe!👨‍🍳🍹

_Photo by Jamie Street on Unsplash_