Skip to content

Instantly share code, notes, and snippets.

@nateklaiber
Last active April 24, 2024 18:11
Show Gist options
  • Star 16 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save nateklaiber/2d446d63239b6e1aa91e to your computer and use it in GitHub Desktop.
Save nateklaiber/2d446d63239b6e1aa91e to your computer and use it in GitHub Desktop.
API Client Design

API Client Design

When working with API clients there are several distinct areas of concern. Each of these happen in sequence and are used with the goal of receiving a Domain Model at the end.

Routing

This answers the question: what can I do with this API?

When embedded links or hypermedia are not available, routing should be confined to a single responsibility and include:

By using HTTP and available methods we can see more details as to how we can interact with a specific link. For instance, we may have a route like: /products. This tells us the route, but doesn't tell us how to interact with it. By making a call to OPTIONS /products, we can learn that the Allow header specifies GET,POST,OPTIONS,HEAD. This means we can then issue a GET request to retrieve all products, or we can issue a POST request to create a new product.

By providing extended meta information in the OPTIONS request, we can also describe the requests in greater detail. We can answer questions like:

  • What are my available filters on a GET request? (their names, data types, formats, etc)
  • What are the Content-Types available to me? Can I GET this as JSON and CSV?
  • What are the parameters required when POSTing to a new record? (their names, data types, formats, validation rules, etc)

The biggest advantage to a centralized routing library: You are not manually building URIs throughout your application code. You can see this in many applications, where the routes are scattered about the JavaScript or server side code with mixed templating and interpolation strategies. Keep it simple.

Configuration

This answers the question: how should I communicate with the API?

The configuration object sets up the different variables you will use throughout your interactions. It can be used to:

  • Set Defaults
  • Allow User Provided values

A set of configuration options may include

  • API Host
  • API Version
  • Proxy Information
  • Basic Auth Information
  • OAuth Bearer Token Information
  • Default HTTP Headers
    • User Agent
    • Media Type
    • Content Type
  • Pagination Handling

Logging

This answers the question: what is happening behind the scenes?

Logging can be used in several areas:

  • Raw request logging. These are the raw HTTP requests.
  • Cache logging. When utilizing HTTP Caching, this provides information into your hits and misses.
  • Application logging. This provides feedback in the client itself. This is useful to provide feedback and information such as:
    • Deprecation warnings
    • Debugging information when calling certain methods

Connection Handling

This answers the question: can we communicate with the API server?

This involves things like:

  • Establishing a connection (is the server responding at all?)
  • Specifying a timeout for retries
  • Ensuring the response code is not in the 500 range

Error Handling

This answers the question: something is wrong, but what is it and can I fix it?

Whether we are dealing with connection errors or request errors, there should be a consistent way to handle when something goes wrong.

  • When there are connection errors, this is an Exception. We can't do anything else with the API until we can connect.
  • When there are request errors, it could be either an Exception or silent failure.

Request Building

This phase allows you to prepare your request that you want to make. This includes things like:

  • HTTP Headers
  • HTTP Query String Params
  • HTTP Body Params

This phase should provide a DSL to build requests in a consistent manner. The specific client could then have very specific implementations of the details (IE: Using Query String params like Google API)

Request Handling

This answers the question: Can you provide me the resource with this representation?

This takes the Request Builder from the last phase, combines in with the Connection Handling, and issues a request to the server.

Response Handling

One a request has been issued, there is a response that includes things like:

  • HTTP Response body (according to content negotiation - CSV, JSON, etc)
  • HTTP Status Code
  • HTTP Headers
  • Full Raw Response

The response Status Code itself allows us to immediately handle the request accordingly:

The response Header itself carries some important client information as well, such as:

  • Scope listing
  • Content Negotiation
  • Allow information
  • Caching information
  • OAuth Information
  • Link information

The goal here is to ensure we get a 200 range response. Not all responses will include a body. We also need to figure out if we want to follow redirects and for how many hops (avoid infinite redirect loops).

Domain Modeling

We've configured, connected, built our request, issued the request to the server, and received a response. Now it's time to take that response and turn it into the Core Domain Model.

In most cases this is translating the JSON response into meaningful objects. The body itself may include other pieces of helpful information:

  • Metadata
  • Pagination Information
  • Links
  • Data/Results - the body itself

Each of these models will translate accordingly and can be used to then issue subsequent requests.

Examples and Inspiration

  • PAW: Mac Client, allows you to build requests and see responses.
    • Build the request itself with headers and params
    • View the request as a URL and as cURL and other tools.
    • View the response as raw output, or separated by headers and body
    • Allows you to extract environment variables for use in requests (configuration)
  • Github Octokit
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment