Building and consuming JSON API with Crystal

When I firstly heard about Crystal language, I got really exited. It fixes exactly four problems I sometimes have with Ruby; types, speed, memory consumption and compilation to machine code. After many months silently following its development I decided to try it out for a simple program I need — a simple server standing between Google Maps API and my application to catch the geocoding responses in local Redis instance not to hit the limit imposed on using the service (plus, Google suggests you to do it anyway).

Before we start with the code let’s look at the requirements. We need to query Google Maps API for geocoding of addresses. That means parsing JSON. We also need to save the latitude and longitude for the objects in question in local Redis instance. That means connecting to and using Redis from Crystal. And finally we need to expose this as a local service, so we need to run our program as a server waiting for our geocoding requests.

For this problems I decided to go with HTTP::Client and JSON libraries included in Crystal, together with crystal-redis client and Kemal for the server bits. I chose all of that after few minutes of googling, so make no conclusions out of it.

Let’s call our program geoserver. This is how the shards.yml would look like:

Shards can fetch and install the dependencies for us. To use the libraries in question, this would be the require calls at the top:

Now we can slowly start implementing our program. The core of it all is to handle responses from Google Maps API, so let’s look at the success and error responses we have to deal with:

And for the error response we might get the following:

Exceeding the quota can happen sooner than you think and that’s why we are building this! And if you are wondering what kind of place Opava is, that’s the city I was born in.

So let’s start with some Crystal!

I am splitting every part of the responce in its own class so we can nicely work with the respective objects. At the top we are getting either SuccessResponse or ErrorResponse. As you can see we can use JSON.mapping to specify our objects mapping to JSON format. This is very convenient as we can now call methods to_json or from_json with all parts of the response.

To simplify everything we also don’t need to specify all JSON fields from the API. We are gonna implement the minimal representation that gets us what we need. If we want though we can list all the other fields here and even use JSON::Any type if we don’t need to map the values to anything concrete.

With what we have we can parse the response and the latitude and longitude of our address:

This sounds promising. Let’s implement a client that can query the Google Maps API for us.

Our client can’t authenticate you, but will be sufficient if we are using only the limited number of requests without the API key.

The only two things to notice here (especially if you have some Ruby background) are types specifications in method signatures and the need to initialize instance variables in initialize method (as we do with @http_client).

Moving on the last piece of our example program is the server part. Here is a “1 minute Crystal & Redis course”:

With our new Redis skills let’s implement our API in Kemal. This is the idea:

Kemal will now automatically start the server and will listen for our requests in the format of /address-we-want-to-geocode.

The last thing is to put all the pieces together:

Again one thing to notice here is the different syntax for handling exceptions. Apart from that the whole experience felt very much like Ruby and that’s what I personally love about Crystal. It has the features of so much praised Golang with Ruby-like syntax and I am exited about its future.

Join the Conversation


Leave a comment

Your email address will not be published. Required fields are marked *