Essential steps

Travel API client in Elixir

In this post I would like to show you how to start a travel project. In travel it is all about integration. You integrate various flights, hotels, and payments providers and build your service on top of this. So, to start I created a simple API client that you can use as a template in your future work.

Without going into much details about implementation, we will cover only main parts. You should be able to find the whole project in Github – travel_client. Try to download and run the client before further reading.

Consumed API provided by Skypicker.com and you can freely use it to get flights information. You will have to become a partner to proceed with booking, though.

Application workflow

The application downloads information about flights from A to B within required interval of time. That is, we will be asking, for example, how to get from Berlin to Paris with departure date in between June 21 through 28.

Then the tool converts received data to internal representation. And it could finally display or save this to a local database. That’s our high level steps:

  • Fetch data from API
  • Convert to internal format
  • Output (or save to DB)

It’s probably the most common workflow for API clients like this. I skipped the use of database, though, for sake of simplicity. And it will be displaying converted response right in the console. You can check on some wrappers like ecto, for example, to make it talk to the database.

In Elixir you can outline the workflow in very clear manner (lib/travel_client.ex):

def process({from, to, from_date, to_date}) do
  TravelClient.API.fetch(from, to, from_date, to_date)
  |> decode_response
  |> convert
  |> output
end

Fetch flights

Interaction with the API is done in lib/travel_client/api.ex. We get the request data prepared and then pass it down to HTTPoison library.

def fetch(from, to, from_date, to_date) do
  format_dates(from_date, to_date)
  |> flights_url(from, to)
  |> HTTPoison.get(@user_agent)
  |> handle_response
end

To process a response there is handle_response function that checks the status code and parses the body.

We limit a number of possible options providing constant duration for the trip.

defmodule TravelClient.API do
  @default_duration 7 # week in heaven
...

Convert data

Response from the flight API is basically, surprise, a list of flights. Every entity of the list represents a flight and can be handled separately. So it would be logical to create a struct in which we can keep flight related data. You can find such a struct inside lib/flight.ex:

defmodule Flight do
  defstruct [:departue_time, :arrival_time, :duration, :price]
end

defimpl String.Chars, for: Flight do
  def to_string(f) do
    "#{f.departue_time} | #{f.arrival_time} | #{f.duration} | #{f.price}"
  end
end

For now we only interested in a small scope of fields, but you can explore the response and the API documentation to extend the struct. In the file you’ll also find implementation of String.Chars protocol. This lets us control the Flight representation when used in string interpolation.

Then conversion is mostly based on extract_flights function that goes recursively through the list of flights and creates another list containing instances of the Flight structure.

def extract_flights([]), do: []
def extract_flights([head | tail]) do
  flight = %Flight{
    departue_time: DateTime.from_unix!(head["dTimeUTC"], :second),
    arrival_time: DateTime.from_unix!(head["aTimeUTC"], :second),
    duration: head["fly_duration"],
    price: head["price"]
  }

  [ flight | extract_flights(tail)]
end

Output

Now we can simply iterate over this list and display flights in the console.

$ ./travel_client berlin paris 2017-06-21 2017-06-28

14:01:46.948 [info]  Successful request
Departue | Arrival | Duration | Price (EUR)
2017-06-23 14:10:00Z | 2017-06-23 16:05:00Z | 1h 55m | 92
2017-06-23 14:10:00Z | 2017-06-23 16:05:00Z | 1h 55m | 95
2017-06-22 14:10:00Z | 2017-06-22 16:05:00Z | 1h 55m | 97
...

Summary

Obviously, there is much more room for functionality in this application. And it is just a basic implementation to show you the idea. So, feel free to fork the project and start building your very own API client.