Developing an API in Rails
At work it's finally time to open our "API" to some real use. The reason I use quotes around API is that our current implementation does almost nothing and is use by no one. But now we need a real API for our mobile apps and some future third parties.
I've written a few APIs in the past but always exclusively for internal use, and while my next one is primarily for us, I know that in the near future we will need to let some intruders into our home.
So I spent all last week planning the API and sifting through existing ones that I admire. I even talked to some API authors about the problems they had faced.
Here's some references I used:
- Versioned APIs
- API Design.org These guys need to learn about web design(.org) but they have good resources.
The main problems I heard from existing API authors:
- Regret integrating the API into existing controllers, via respond_to
- Inability to change API without breaking third party apps
- Lack of knowledge/control of third party developer actions
- At this time it doesn't make sense for me to split an API out to its own app, using Sinatra or node for speed, because we don't have the traffic. However, I took the regrets of others to heart and decided to completely namespace the api, avoiding mixing logic. Yes, some of my controller logic will be duplicated, but I don't care. If I need to split it out, it's easy; very easy.
- I considered using versioned APIs. A simple approach in Rails once you are namespaced is simply to namespace versions in your api namespace. But I didn't want to copy code and add namespaces. So I decided to stick to RESTful APIs for public consumption and save anything different for our internal use. That way, I can change the api however I deem fit, without breaking the RESTful portions. Third parties will just have to do more work than us.
- This last problem doesn't seem important to me now, but could in the future once there's a pesky consumer. So I developed a tiered access level of API keys. To the typical public consumer, nothing will seem different, but internally they will have an access level of 1, be restricted to certain requests, and have suspicious activity logged. I decided to create a model that feeds to MongoDB where I can just dump suspicious activity and calculate use rates separately.
At this point I've finished the foundation of the API.
Here are the key features:
- All controllers are namespaced into api.
- Every request requires API_KEY in header and authenticates its access level. This is handled in the api namespace base controller.
- Receives and authenticates optional HTTP Basic User Authentication.
- Controllers inherit from API::BaseController and can optionally call require_api_login (for HTTP Auth) and require_access_level(num) to restrict access.
Now that all that is finished, with 100% test coverage, I can easily add RESTful controllers to expose JSON and XML per model. It's going swimmingly.