API Client Design Across Languages - Part 2 - Making Requests
Tags: sdk, python, php, node
Published: 2022-03-29 18:53:59

It's been a while since my last post ([API Client Design Across Languages - Part 1](https://dev.to/freshbooks/api-client-design-across-languages-part-1-5hmk)), but life and work have gotten in the way. Regardless, I'm am finally continuing my dive into how API clients can differ in style and usage across languages while still maintaining the same features.

The first post focused on the basic structure of different API clients. In this post I will go into how a client may make requests against the API.

## Request Libraries

Languages vary in how well supported making HTTP requests is in their core implementations or standard libraries. Almost by definition, languages used on the web generally have easy ways to make HTTP requests. However, there are often dedicated request libraries that can make this simpler or cleaner, and in general I recommend their use unless the language has very clear and clean support.

There are reasons to not want to include a request library in an SDK as every additional dependency added is one that developers using it will have to add as well. Keeping a small dependency graph also makes it easier to maintain updates. But, letting a library do the low-level work is also good for maintenance and security, and often someone looking to use your SDK will already be using a request library.

I'll show some examples of the choices FreshBooks made for different languages below.

## Sane Request Defaults

Both to make it easier for developers to use our SDKs, and to make our own lives easier in supporting them, we set some defaults for HTTP requests.

Timeouts are especially important. If a request takes too long, it may can impact both the user (slowing the response to their customer) and us (if our servers get saturated by slow requests, we stop service our customers). Most HTTP clients have easily set timeouts but often they are not enabled by default.

Setting user-agent strings is also helpful. Including things like the SDK language and version helps FreshBooks figure our SDK usage and what languages are popular with our developers. Of course we let users over-ride the user-agent if desired. It can also help an API support team track down a reported error if the client has a unique user-agent string.

## Interface

The SDK should try to be as consistent and intuitive as possible, especially if the API itself is a fairly CRUD-heavy RESTful API versus one with more unique behaviours around resources.

Try to make to keep things standardized like resorce pluralization (eg. `clients`, `invoices` vs. `client`, `invoices`).

Try to keep method signature variables in a similar order. For instance:

```python
clients.get(id, filters)
clients.create(data, filters)
clients.update(id, data, filters)
```

versus

```python
invoices.get(id, filters)
invoices.create(data)
invoices.update(data, id, filters)
```

The more standard and intuitive the SDK is, the easier it is for developers and the fewer support tickets.

## FreshBook's SDKs

Like last time, I'll show some examples of how FreshBook's SDKs are built in a few different languages.

### Python

In python we're using the [requests](https://docs.python-requests.org/en/latest/) library for simplicity. Requests is widely used (see the [Stripe](https://github.com/stripe/stripe-python/blob/master/setup.py#L34) and [Auth0](https://github.com/auth0/auth0-python/blob/master/setup.py#L30) SDKs) so it isn't too onerous of a requirement. In fact, FreshBooks' Python SDK is [very light on dependencies](https://github.com/freshbooks/freshbooks-python-sdk/blob/release/1.0.0/requirements.txt) in general.

You can see where we [instantiate a session](https://github.com/freshbooks/freshbooks-python-sdk/blob/release/1.0.0/freshbooks/api/resource.py#L30) (to allow for retries), and make the [HTTP requests](https://github.com/freshbooks/freshbooks-python-sdk/blob/release/1.0.0/freshbooks/api/resource.py#L57-L83)). In the client we can instantiate the [shared client code](https://github.com/freshbooks/freshbooks-python-sdk/blob/release/1.0.0/freshbooks/api/projects.py#L69-L83) for each [different resource](https://github.com/freshbooks/freshbooks-python-sdk/blob/release/1.0.0/freshbooks/client.py#L363-L365).

Usage looks like:

```python
invoice = freshBooksClient.invoices.get(account_id, invoice_id)
clients = freshBooksClient.clients.list(account_id)

assert clients[0].organization == "FreshBooks"
```

### Node.js

Like Python, our Node.js SDK is using a well-known library [axios](https://axios-http.com/). While it is not quite as ubiquitous as Python's requests, it is very commonly used. For instance, it is used by [Auth0](https://github.com/auth0/node-auth0/blob/v2.40.0/package.json#L37) (if you're looking for a different example, [Shopify](https://github.com/MONEI/Shopify-api-node/blob/3.8.2/package.json#L19) makes use of [Got](https://github.com/sindresorhus/got)). You can find it [configured here](https://github.com/freshbooks/freshbooks-nodejs-sdk/blob/%40freshbooks/api%403.0.0/packages/api/src/APIClient.ts#L169-L177). The [shared client code](https://github.com/freshbooks/freshbooks-nodejs-sdk/blob/%40freshbooks/api%403.0.0/packages/api/src/APIClient.ts#L264-L292) takes reqeust and response transform functions for [each resource](https://github.com/freshbooks/freshbooks-nodejs-sdk/blob/%40freshbooks/api%403.0.0/packages/api/src/APIClient.ts#L350-L360) to convert the repsonses to objects.

### PHP

Like Node.js, the PHP ecosystem has quite a number of good HTTP request libraries. [Guzzle](https://github.com/guzzle/guzzle) is perhaps one of the most well known, but there are [many](https://github.com/WordPress/Requests) [other](https://github.com/php-http/httplug) [popular](https://github.com/nategood/httpful) libraries out there. Luckily, PHP also has some interface standards around HTTP clients and messages, particularly [PSR-7](https://www.php-fig.org/psr/psr-7/), [PSR-17](https://www.php-fig.org/psr/psr-17/), and [PSR-18](https://www.php-fig.org/psr/psr-18/),

Implementing the FreshBooks SDK to these standards means that we don't force any particular library on developers. They are free to choose any library that implements those standards.

In our [README](https://github.com/amcintosh/freshbooks-php-sdk) we provide an example for those who have no particular preference:

> Requires a PSR-18 implementation client. If you do not already have a compatible client, you can install one with it.
>
> `composer require amcintosh/freshbooks php-http/guzzle7-adapter`

Again, [here is the configuration](https://github.com/amcintosh/freshbooks-php-sdk/blob/0.4.0/src/FreshBooksClient.php#L73-L90), and the [resources](https://github.com/amcintosh/freshbooks-php-sdk/blob/0.4.0/src/FreshBooksClient.php#L204-L207) using [shared client code](https://github.com/amcintosh/freshbooks-php-sdk/blob/0.4.0/src/Resource/AccountingResource.php#L119-L124).

Usage looks like:

```php
$invoice = $freshBooksClient->invoices()->get($accountId, $invoiceId);
$clients = $freshBooksClient->clients()->list($accountId);

echo $clients->clients[0]->organization; // 'FreshBooks'
```

## Up Next

So there you can see a number of different HTTP client options, and examples of how FreshBooks' SDKs utilize them.

I hope you found something interesting or useful here and I hope you can catch the next post where I plan to go into request data and response structures.