Visit AppSignal.com

Consistent API

Robert Beekman on

How to keep your API, client tests and documentation in sync.

We’ve recently began converting our front-end from Rails views to React. There are a couple of reasons for this, and one of them is that we really wanted to use our own API.

While we use parts of it already (all graphs are generated through the API), other parts were added as an afterthought and haven’t been maintained the way they should.

We believe all data customers send us is still theirs, and that we should provide a way for customers to access their data to get their own custom intelligence out of it.

If you have an API but don’t use it yourself regularly, chances are your documentation starts lacking (it did for us). Things like pagination links, resource links, etc aren’t included in the responses, although they make the client implementation a lot easier. We tried to recreate our app’s views with just our API and it was very difficult, if not impossible to do.

We decided we needed to do better, and one way of guaranteeing that our API stays up-to-date, usable and consistent is by consuming it ourselves.

One of our main concerns was keeping our API, client fixtures and documentation in sync, in a way that’s consistent and easy to maintain. We came to the conclusion that there needs to be a single point of truth in the app. In our case it are the fixtures for the JavaScript specs.

Here’s an example of a fixture:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
{
  "incidents": [
    {
      "id": "exception-incident-id-two",
      "action_name": "BlogPostsController#show",
      "exception_name": "NoMethodError",
      "message": "No method",
      "count": 2,
      "markers": {
        "abc": {
          "count": 2
        }
      },
      "last_occured_at": "2010-10-10T10:00:00.000+02:00",
      "urls": {
        "web": "http://www.example.com/account-id-one/sites/site-id-one/web/exceptions/BlogPostsController-show/NoMethodError"
      }
    },
    {
      "id": "exception-incident-id-one",
      "action_name": "BlogPostsController#show",
      "exception_name": "NoMethodError",
      "message": "No method",
      "count": 1,
      "markers": {},
      "last_occured_at": "2010-10-10T10:00:00.000+02:00",
      "urls": {
        "web": "http://www.example.com/account-id-one/sites/site-id-one/web/exceptions/BlogPostsController-show/NoMethodError"
      }
    }
  ]
}

Rails

In our Rails API we test the API output against this fixture:

1
2
3
4
5
  it "should return incidents" do
    get path
    response.status.should == 200
    response.body.should equal_api_fixture('incidents/performance_incidents')
  end

This way we know for sure that our API outputs the right data.

React

On the front-end side we use this fixture in our JavaScript specs and make sure the right data is rendered:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
context "when loaded", ->
  beforeEach ->
    @incidents = ApiFixture('incidents/performance_incidents')
    @component = React.renderComponent(
      PerformanceTable(
        incidents:     @incidents.incidents
        namespace:     'web',
        max_mean:      1000,
        max_count:     2,
        loading:       false
        active_action: 'PerformanceIndex'
      ),
      document.body
    )
    @el = $(@component.getDOMNode())

  it "should show two rows sorted by performance mean", ->
    [...]

Documentation

And finally we use this same fixture in our documentation:

1
%pre= api_fixture('incidents/performance_incidents')

Stay sane

Whenever we make a change in the API, we always know that we have the correct output in the documentation, that our API outputs the correct data and our that JavaScript knows what to do with it. It reduces cognitive overhead, which helps keeping us and our clients sane (at least, as far as our API is concerned).

10 latest articles

Go back

Subscribe to

Ruby Magic

Magicians never share their secrets. But we do. Sign up for our Ruby Magic email series and receive deep insights about garbage collection, memory allocation, concurrency and much more.