Minimal Swift API client

I’ve always enjoyed code refactoring and optimization — whether striving to complete a task in as few statements as possible or to speed things up. By now I probably wrote nearly a dozen different API clients in Swift but this latest one I’m rather proud of so I wanted to share it with you.


  • URLSession based with no external dependencies
  • JSON Codable for clean and simple interfacing
  • Uses generics to evaluate responses
  • Simple to add new API calls
  • Easy to understand so your team isn’t afraid to touch it in the future.

Why URLSession? Because it works, it’s super clean and I much prefer to carry 100 lines of code in my own maintained project rather than depend on thousands of lines worth of imported frameworks with most of it dead weight.

Let’s get started with our JSON request:

And the response expected from the server on successful login:

Now let’s complete the only code required for a single API call using our API client library (introduced a bit later):

That looks rather clean, but how do we use it and… where is the URLSession code? Let’s cover the first part, how to use this API call:

Pretty neat! Now that I’ve got you curious about how so little code can actually do anything.. let’s introduce the APIRequest base class:

Near the top, we define a protocol that our supported APIs will have to implement, namely provide the address of the endpoint to our API library when needed.

Next, we declare the base class and what should be considered by our API to be a standard error response.

Finally we declare the various error conditions that our API will be generating at one point or another.

Now, since our API will need to perform a POST to our server in order to login let’s see the post extension:

Ah finally! That’s where the magic happens. Let’s review this one tidbit at a time.

The function declaration uses Generics. To learn more about them, visit

Essentially, we declare that our function will be using 3 different “unknown” object types that implement one or more known behaviours.

  • The “R” object will need to support the Codable and APIEndpoint protocols and will be used to represent the JSON request we are encoding to send to our server.
  • The “T” object is the expected server response type for this request when successful — any object that supports the Codable protocol.
  • The “E” object is the expected server response type on error condition — again any object that supports the Codable protocol.

Next to defining the generics and protocols they must implement, we declare that the function requires three parameters:

  • “request” of type R, which again was any object implementing the Codable and APIEndpoint protocols
  • “onSuccess” which is callback function to possibly be called at a later time if we are able to properly decode the success response of type T.
  • “onError” which is a callback function to possibly be called at a later time if there was any error condition. This callback will receive the E object type if it can be decoded and will also receive an Error object type.

Now for the code within the Post function. At line 7 we forward the R object type to a helper function (see below) and get a URLRequest object back. We then modify the URLRequest object so it performs a HTTP POST method and we specify that the content we are including will be in JSON.

Depending on your API requirements, you may need to define other parameters such as a CSRF token, X-Api-version or any number of custom header fields.

The block of code at line 14 attempts to encode the request object to JSON and since this can generate an exception it is enclosed in a do/catch. Failure to encode the request object will simply abort the request and forward the error object to the “onError” handler.

URLSession gets called at line 21 to create a background dataTask which will foward the encoded request and obtain a response (or error), then forward this information to a processResponse function:

The function above once again declares two generics, “T” and “E” which are both expected to support the Codable protocols. As the names suggest, they are the same “T” and “E” objects received by the post() function we reviewed earlier.

It then defines 5 parameters:

  • “dataOrNil” which contains the data object received by the dataTask of URLSession, or nil if no data was received
  • “urlResponseOrNil” which contains the urlResponse from dataTask
  • “errorOrNil” once again received by dataTask
  • “onSuccess” and “onError” the two callback function handlers initially received by the post function

At line 9 we check if we have a usable data object. If not we jump to line 24 and proceed the onError callback function and forward any error we have available.

Starting at line 10, we attempt to decode the response object into the expected success response. Note that because we are using generics, the actual type of the object is not known just as long as it implements the Codable protocol. Provided decoding the response succeeded, we proceed to the onSuccess callback function and voilà!

Lines 14 through 21 are used in case the data received by the dataTask cannot be decoded into a success response — in that case we first attempt to decode the expected error response, and failing that we assume there was an expected issue and forward the data we have to the onError callback function.

As you can see above, there’s very little code and most of it is re-usable between all API calls. For the avid reader, you may have noticed there’s no function to perform a HTTP GET — consider it your homework!

Earlier during the post function implementation, we used a urlRequest helper function, here it is for completeness:

What I like is how relatively simple the entire API code is and how each API endpoint takes so little code yet allow full customization.

I hope the above will be useful to you, if for no other purpose than seeing how its possible to use Generics and Codable to really simplify your code and decrease code duplication.

If you need to learn more about Codable, how to do custom coding keys or do manual initialization, check out this tutorial:

Liked this article? Clap a few times! Loved this article? Clap even more!