Must-Have Features For Your Client API Library
The hard part is done. You built an API for both you and your customers to use. You have started to see some great partnerships from customers and other companies who are using it. Now you want to get the word out about how your API can help people run their businesses more efficiently and help them make more money. Is marketing the answer? It’s a piece of it, but is also comes down to how easy is it for developers to integrate with your API.
The answer: Your client API libraries.
Designing a client API library for several programming languages can be a great opportunity to encourage developers to use your API. Since your API is part of your product, and your product makes you money, I see a well designed client API library as a money maker. It can allow you to keep current customers happy and find new customers in business sectors that might surprise you. Yet, this is a largely under-appreciated aspect of many businesses that provide APIs.
As a part of my startup Affiliate Linkr, I currently integrate with APIs from 5 different affiliate networks, and none of them have a client API library. That’s right, none of them! Talk about a missed opportunity! I wonder how many cool apps could be developed if only they would make it easier for a developer to pull some code off of Github and start innovating.
With my experience developing Affiliate Linkr and several other projects, I’ve had a chance to use client API libraries and create some of my own, so I wanted to share my opinions so we can all do a better job creating them.
If you are excited about creating a client API library for your API, but would like some help, please contact me on LinkedIn and let’s work together!
Here is a list of 13 must-have features when designing your client API library.
- Object Oriented Interface
- Throw Named Exceptions
- Allow Automatic Retries
- Should Handle Multiple API Versions Within One Code Base
- Base API URL Can Be Changed
- Unit Tested and Functionally Tested
- Use an Existing Library for Doing HTTP Communication
- Ability to Get Raw API Responses and API URL Calls for Debug
- Example Code
- Live Demo of Example Code
- Library Should be Installable Using a Package Manager
- Make Releases
- Quality Documentation
For a detailed look at each of the must-have features listed above, please read on.
Object Oriented Interface
One of the main reasons you are creating a client API library in the first place is to give people an easier way to work with your API. You want to make it easy for people to create apps using your API, so you gain new customers or make your existing ones even more satisfied. An object oriented interface to your API allows you to fit into the most designs, as almost all developers are familiar with OO and most likely their end application can easily integrate object oriented code.
One important detail when designing your object oriented interface is the naming of your classes, class accessor functions and other methods. Your class names should correspond to the different service endpoints. Like if you have a /users/:uid endpoint, you should have a UsersService that returns the user data as a User object. If one of the resources you can access for a user is projects by an endpoint like /users/:uid/projects, you should have a User method of getProjects() that corresponds to this endpoint. Internally, that function could use the ProjectsService class to fetch the data.
As with any class, module, or library, make sure you use a namespace so that you do not run into any conflicts with other libraries or existing code.
Great naming can help give your API users intuition about how to use your API without knowing all the little details. It is also important to document how this naming will be done. Let’s say that you have an endpoint of /users/:uid/equipment-owned, how would that look? I suggest having a clear naming conversion guide, something that states for example:
- dashes and underscores are removed and considered as word separators
- camel case will be used (capital letters used for the beginning of words)
- get and set will be used for accessors
A naming convention like this would result in the OO method being User->getEquipmentOwned() for the /users/:uid/equipment-owned endpoint. Having a clear naming policy makes your job easier, and also helps future developers implement changes to the client API library easier when the API changes. The specifics don’t have to match what I have said above, but having a clear naming convention is where the value is at.
Throw Named Exceptions
Your client API library should throw named exceptions for as many error cases as you can. The names of the Exception classes should be self-descriptive, so inspecting an object in the debugger will allow you to have an idea of what is going on.
Your exceptions will break down into a few groups:
- Authentication related
- Authorization related
- Request related
- Response related
Make sure to cover them all. They are especially important when a developer just starts to learn to use the library, where onboarding someone smoothly to your client API library can really improve how that developer feels about your company. Yeah, I think of a client API library as a recruiting tool.
An example of this would be if the user sets an invalid API key, the API might respond with a HTTP status code or an error data structure. You would present the user with this within your client API library as an exception thrown that would be descriptive and identify the error uniquely, like InvalidAPIKeyException.
Having named exceptions are also very important to allow multiple catch blocks to look for specific exceptions by name, so the user of the client API library can integrate error handling into their application. Maybe some Exceptions thrown are fatal to the application, but other Exceptions just require a retry of a request.
Having errors that can be recovered by just retrying a request is something that happens to me with a couple of the APIs that I use within Affiliate Linkr. The API errors are random, really have no reason for them, and the same request will return fine with a retry, so knowing what particular error I got can be the difference between a fatal app failure and a smoothly running app.
Allow Automatic Retries
Not every API is perfect. There could be situations where the API gives a bad response, or is too busy and cannot service a request. Some of the reasons may be out of your control too, like a spotty internet connection that maybe broke down along the way. Having a backup plan for these cases is smart.
I suggest to build in a way for the user of your client API library to enable automatic retries. The reason I like it as an option for the user to enable is that maybe the user’s application has a different way they want to handle it. Maybe they want to treat it as a fatal error, and possibly put some analytics on it so they can feed back to the API support team.
I feel like adding automatic retries might be better handled in your user’s application in most cases, so whether you choose to have automatic retries as a feature might depend on what your API functionality is. Some APIs might be more prone to errors, or high traffic, where an uncaught error might be common and the consequences could be pretty bad.
The essential factors in a retry system is to allow configuration for:
- Retries Enabled
- Number of retries
- Time between retries
- Named Exceptions that will allow retries
- Named Exception thrown when all retries fail
Depending on how much control you want as the designer of the client API library, you may want the Exceptions that will allow retries to be controlled by you and not exposed to the user. However, I think that the goal of being developer-friendly should result in us exposing that as a setting, but providing a solid set of defaults so that it likely never has to be changed.
Having a different Named Exception thrown when all retries fail is important so that the application can operate with both retries enabled and disabled, but use the same code. They could handle the non-retried Exception one way, maybe with manual retries, but the retry failed Exception might cause a fatal app error.
Should Handle Multiple API Versions Within One Code Base
When a new API version is released and some of the changes are not backward-compatible, developers using your client API library should not have their applications break. Forgive me for stating the obvious.
I also feel that as a developer using the client API library, my existing code that uses the library should not break if I update to the latest library release. This means that if I want to keep using my current version of the API, updating to the latest library release should not automatically cause my code to start using the newest API release. I should always have a way to make my existing code continue to work.
To enable this, the client API library should have a setting as to what version of the library to use. I don’t suggest that you make people define a number, like setVersion($newVersion), too much chance for error. It is better to have a specifically named version method, or at the very least, a constant. So setVersion3() or setVersion(API_VERSION_3). If you allow setting it via setVersion(), just make sure you make sure it is a valid API version number and throw an exception if it isn’t.
Also, I suggest defaulting the client library to the use the current version of the API, but only using something similar to a constant API_LATEST that you can default to, and possibly a setVersionLatest() method. Require this line to be in code! This makes the developer make a specific decision to be working with the latest API version the library supports. So then when they update to a newer release of your client API library, it will automatically point to the latest API version, and use any new code associated with it, and possibly cause breaking changes to their app but it was their explicit choice. The key is that it is obvious that they are choosing to live on the edge by having an intentional choice they have to make.
To allow different API versions to be handled by the same code base, I like to design using Data Mappers to map between the API response and the client API library classes. Data Mappers help with decoupling, because they are the only class that needs to know both about the API response data, and the client API library object. As an example, I’d use a UserResponseMapper to map a UserResponse object into a User object.
Different class names for your service objects that correspond to different versions are the way to go, but a smart idea would be to inherit from a base service name. The service probably doesn’t change a ton over time, but just small changes, so it’s likely that a base service class could have most of the logic you need.
An example of this would be if you are currently on version 1, but you come out with version 2, then your user class name for the latest version of the library would be User, but your old user class could be named UserVersion1. Always keep the version number out of the class names of your current code, that allows you to potentially remove legacy code someday. While the class names returned might be different than they were before, the interface is the same. To handle this gracefully, it is useful to have a factory method to return an object that corresponds to your current API version to avoid having class names hardcoded.
Base API URL Can Be Changed
To allow for development work and testing, having the base API URL be a setting of the client API library will enable this to be easy.
This is also a key for unit testing. You may want your live HTTP testing to go to a test-only URL that has some fake data in it.
Note that this base API URL should NOT include a version number in the path. Most likely, the API version will be included in the URL, but the API version can contain any number of significant changes both in the API and the client library operation. We are setting the API version separately also, as mentioned previously. We will use the base API URL in combination with the library version plus the service endpoint to craft the full URL.
Normally, this base API URL would default to your production URL, but since being able to override the URL is a feature that you will want anyways to allow future development and testing of your client API library, it’s a no-brainer that it needs to be a feature.
Unit Tested and Functionally Tested
Like any piece of code, a client API library should have tests on several different levels, both unit and functional. This can get a bit tricky since the client API library is meant to interact with the live API over HTTP. Yet, it is important to have some tests for your client API library without needing the live API. This is why I see a couple different levels to testing a client API library.
The first level is without any live API available. The strategy with this is to mock the HTTP responses that the API is supposed to return, which then allows us to test all of our code independent of the API being available. What this will look like is that we will have a bunch of files that contain sample XML or JSON data that is exactly like what is returned from the real API call, then we process it as though it was real.
The second level is with a live API available. This part can be hard as you need some real user credentials. You could consider having a dummy user that has some fake data within your live API. One alternative is like Amazon Web Services PHP SDK, they just have a place within their client library that you can put your actual credentials in a file. If your actual credentials are defined, then it enables running through the live API tests. I really like that technique, because it avoids having to always maintain a dummy user account.
I would advise verifying the raw data back from the API as it’s own suite of tests, then also test the whole system together that is raw API data all the way through your client API library and converted into usable objects.
For completeness, you should also test for proper exceptions thrown when you pass in invalid API keys or client key/secret during authentication.
I think it goes without saying that we all know what we should test, but that ends up being a TON of work. It’s easy for me to say in a blog post that you should test all this stuff, but of course it depends on your project and factors such as longevity, how many users you will have, and number of different environments the client API library could be used in.
Use an Existing Library for Doing HTTP Communication
When writing a PHP client API library, I highly recommend using Guzzle. Using that makes it very easy to mock HTTP responses, and knows how to talk OAuth to make communicating with any API require a lot less code. Always strive for less code.
Other languages might not have a frontrunner in the area of HTTP communication libraries, if not, no big deal. You will just need to implement the HTTP mocking yourself through smart design, and handle your specific HTTP authentication method, which for some APIs is probably not too hard at all.
Ability to Get Raw API Responses and API URL Calls for Debug
When I develop a client API library, as with other software, unpredictable things can go wrong. When things go wrong, I start debugging and look to verify what is happening at the most basic level, which is the HTTP request and response. If the client API library does not allow a developer visibility into this low level of HTTP requests and responses, it makes it so hard to tell what is happening.
The client API library should make it easy to get out raw API responses and API URL calls along with headers and body to help provide this low level debug capability.
Another reason to provide these raw HTTP request and response details is when dealing with support requests to the API provider. When communicating with customer support, they always ask for the HTTP request and HTTP response, they don’t want to deal with your code and they shouldn’t have to. If you give them the HTTP request headers, URL, and body, then they can re-create the request and see if what they see matches what you are seeing in the raw HTTP response.
This is not only for problems caused by your client API library. No API is perfect, there is always the possibility for real errors in data to be caught by users of the client API library. Our job as creators of that library is to enable the developers using it to identify these problems and easily have the information at their fingertips to debug it.
This example code can really inspire someone to create a cool app with the API. Also, when things go wrong, the first thing I look for in a client API library is some code that I know that works, then see what I am doing differently.
I also recommend having a simple example, and some more advanced examples. The more examples you can provide, the better off your users will be. Look at what you are testing within your unit and functional tests for ideas on what kind of examples to create.
Live Demo of Example Code
We want to ship our example code with our client API code base, but it also is so nice when we can create live working examples of our client API library in the form of an application of some sort.
This is not only for seeing how your client API library works, but also is a great tool for testing out API calls. You could include this sample web app in the client API library repository, but a better idea is to include it in a separate repository that you make sure to link to within your client API library documentation. The reason to have it separate is that it should also be easy for someone else to spin it up and use. The sample app would have a dependency on your own client API library, and if you are using a package manager, it’s a great way to show how that integration works too.
Library Should be Installable Using a Package Manager
There are a lot of other aspects of your library that this implies, such as maintaining releases and versioning of your API to be compatible with the package manager. You will probably have some extra config files that are a part of your repository.
Using a package manager will dictate most of this process for you, but define your release procedure and naming convention, don’t just make everybody pull trunk.
If I look at a new project and see no releases, I automatically think less of the project, consider it to be in a beta state, and will start to look for alternatives. So make releases so you give your users confidence in using your library.
This also helps when it comes to customer support. Knowing clearly what release a customer is using will make it a lot easier to reproduce any errors.
We all know that documentation is important, but quality is preferred over quantity. Great documentation is made when you step away and can put yourself into the shoes of your users. What questions are they going to ask? What is going to be hardest for them to do? Do you have any uncommon features?
We should include the obvious instructions on how to install both with the package manager and manually.
We should have special instructions on how to test it, how to get at the example code, and where the demo pages are located at.
I highly suggest using Github for your repository so that you get Issues and a Wiki with your project. You can put the most basic documentation in your README.md and more details about architecture, types of named exceptions, API version handling, and everything else in the Wiki.
Optional Features (not exactly must-haves but are a good idea):
- Continuous integration capable through online sources like Travis CI directly integrated into your Github README page. It can give a great deal of confidence, but as long as you have clear instructions for a developer to run tests, this is just icing on the cake.
There are a lot of under-appreciated features that go into a useful client API library, but it has to be designed with as much care and attention to detail as the API itself if you want it to be a business driver for your company.
If you are excited about creating a client API library for your API, but would like some help designing a client API library using these principles, please contact me on LinkedIn and let’s work together!