Skip to content

Instantly share code, notes, and snippets.

@pjkelly
Last active April 25, 2022 10:19
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save pjkelly/5282900 to your computer and use it in GitHub Desktop.
Save pjkelly/5282900 to your computer and use it in GitHub Desktop.
Crush + Lovely Blog Posts

How We Build JSON APIs in Rails

Over the past few years, we've seen the projects we work on go from occasionally to almost always requiring a REST API. There are several reasons for this, all of them necessitating our ability to construct clean, performant APIs that are easy to work with:

  • More than ever, we are using client-side applications to provide rich, responsive interfaces.
  • The products we build span multiple platforms (web, iOS, Android, Blackberry) that all need to be kept in sync.
  • People and organizations have accumulated large amounts of data, and they need to have that data aggregated and/or sanitized so it can be used in new ways.

As a Ruby shop, we've always had a wide array of tools and libraries to choose from when building our applications. While this is most definitely a good problem to have, it's still a problem, and finding a combination of tools that made API development feel right to us took a few tries.

Framework

Early on, we were concerned Rails would be overkill for building something as "simple" as a JSON API. We tried several other options, including Rack, Grape and Sinatra, but we found ourselves missing things that we had obviously taken for granted when developing with Rails. Features like parameter parsing, environments for development/testing/production, code reloading, and logging to name just a few. In the end, we decided that the performance gains of using a smaller framework weren't worth the added development pain and chose to stick with Rails.

Problems & Solutions

API versioning isn't just about allowing a client to specify a new version number and get some cool new features. It's about being able to introduce new features as well as extend existing features while maintaining backwards compatibility. It affects all parts of your application, from routing to business logic to object serialization. In our early JSON APIs, we were specifying the version via URL, handling object serialization by overriding serializable_hash, and also putting all our business logic in our models. We learned very quickly that this approach made versioning code other than our controllers while also keeping our codebase DRY difficult at best.

The first thing we did was investigate different options for specifying an API version. Doing this as part of a resource's URI always felt a little clunky to us, so when we found Brian Ploetz's versionist gem, we immediately switched to specifying versions via the Accept header. This library simplified the code in routes.rb and the controller, and also provided a cleaner way for clients to specify which version of the API they want to use.

For object serialization, after we realized that overriding serializable_hash only ends in tears, we looked at view-layer solutions like RABL and jBuilder, but never found a) a DSL we really loved, and b) a method that was easy enough to test. We finally found ActiveModel Serializers, and never looked back. The class-based approach made versioning representations of our object easier, and because serializers are just Ruby classes, they're simple to test.

These days, we keep our models pretty slim and handle business logic in a variety of plain Ruby objects. These objects are simple classes with a single responsibility like "create account", "update payment card", "check username availability". We've found that this approach helps to clarify the intention behind our code as well as making it easier to test and version.

Wrapping Up

It's important to note that all the tools we evaluated are all outstanding projects. A lot of these projects were very young when we evaluated them, and as a result could not satisfy the demands of what we were working on at the time without significant customization or extension. All of these projects are alive and well, and most certainly deserve your consideration when evaluating potential tools to use.

While there's nothing new or revolutionary about how we build our APIs, we have found that it works very well for us. We hope that sharing the steps we went through in finding our current preferred architecture will prove useful to someone who finds themselves faced with similar choices. Remember that, like most things, the choices you make should be based on what feels "right" for you and your team. We'd love to hear what works for you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment