Smartly organize API/Endpoints in Swift

Daniel Hu
6 min readFeb 9, 2022
Photo by Adem AY on Unsplash

In many modern apps, we would like to link users to some external websites or services. For example, in a dating app such as Tinder, a user can see other users’ favourite Spotify music, and view their Instagram profile. This is achieved by using the third party APIs those companies provide.

In a large scale social app, your app may need to talk to several different APIs and many endpoints. It’s easy to lose track and end up with a lot of duplication if you simply use plain strings to organize the urls. A strategic way for managing endpoints can make your endpoints scalable and easy to use.

Today I will show you how I do it in Swift in Apple platform projects. If you prefer a video version of same content, check out my tutorial video, otherwise please keep reading.

Let’s start with a protocol and a namespace

protocol API
An API must have a base url
Give each API its own namespace (enum)

Simple enough, right? In this example, I will use Yelp and Spotify for the demo, as they are well known and easy to integrate, I will demonstrate how to organize some of their frequently used endpoints.

In the code snippet above, I defined a protocol API, which only requires a base url, because guess what, all APIs must have a base url!

Then I created a case-less enum APIs to use as a namespace to organize all the third party APIs I will be using. Each API also has its own namespace (enum), so later I can use dot notation to easily access any API’s any endpoint, e.g. APIs.Yelp.businessSearch

Add some simple endpoints

Each case represents an endpoint

So now our Spotify API has a raw value type of String, and 2 cases: one for getting several tracks and one for getting several artists.

Now if I want to get the url for getTracks endpoint, I can do something like this

let url = APIs.Spotify.baseUrl.appendingPathComponent(APIs.Spotify.getTracks.rawValue)

Wow, what a long and ugly line of code!

Fortunately, we can easily write an extension to simplify the usage:

Constrained extension for String type RawRepresentable

This extension gives all String raw value RawRepresentable types that conforming toAPI an out of box property: url. With this extension, our usage will be much shorter:

let url = APIs.Spotify.getTracks.url

Much better!

However, in the real world, not all endpoints are this simple, some endpoints require one or more path parameters, e.g. Spotify get an artist endpoint /artists/{id}, how can we pass a dynamic id to our endpoint case?

Swift enum has a feature that’s perfect for this case: associated values!

Use enum associated values to pass in path parameters

So let’s add a case for Spotify getting a specific artist with id endpoint:

case getArtist(id: String)

However, the line above will generate an Xcode compile error:

Enum with raw type cannot have cases with arguments

Since our Spotify enum has a raw type of String, enum Spotify: String , we cannot have cases with associated values, so let’s remove the raw type and try to make the error go away.

But now, this case will raise a Xcode compile error

case getArtists = "artists"

Enum case cannot have a raw value if the enum does not have a raw type

Bummer! Seems we couldn’t get the best out of “raw type” and “associated value” at the same time!

Or could we?

Since the extension we wrote above only works on RawRepresentable with String raw value type, we need to make the Spotify enum conform to RawRepresentable, and it turns out, we can conform to this protocol manually by meeting all its requirements:

RawRepresentable protocol in Swift documentation

That’s not a lot of requirements, in fact, our enum can easily conform to this protocol:

enum Spotify conform to RawRepresentable manually

and because now our enum conforms to String type RawRepresentable again, we can still use the extension to simplify our usage:

let url = APIs.Spotify.getArtist(id: "artistId").url

Also, since we never use the required failable initializer init?(rawValue: String), we can remove it from our API enum and into the extension, so the API enum will be shorter and less cluttered:

Add init? to extension

Everything seems great so far, so let’s use the same strategy to add some Yelp endpoint:

Not bad! And we can easily access an endpoint like this:

let url = APIs.Yelp.businessDetail(id: "businessId").url

But while I was typing all these endpoints out, I can’t help noticing I typed “businesses” and “events” repeatedly for several times. Also, our enum is having more and more cases, and these are just business and event related endpoints, imagine how many cases there will be if we add all Yelp endpoints.

So how can we optimize this?

Break a large API down into smaller ones

We can think of all business related endpoints is aYelp.Business API, all event related endpoints is Yelp.Event API, and each API has its unique base url and cases.

By doing this, we grouped all business endpoints into one enum, and we eliminated the repeating “businesses” path, now we can access business endpoints like this:

let url = APIs.Yelp.Business.detail(id: "businessId").url

And we can do the same thing to Yelp event endpoints, category endpoints, and put each of these small “API” into their own file to keep things organized and in a manageable size.

An alternative way

If you find setting up the custom raw value is a bit overkill, here’s an easier to understand way to define the endpoints that require path parameters:

Use static func to build endpoint with path parameter

We brought back the String raw type, enum Business: String, and now use static function to generate endpoint urls that require path parameters. The usage will be like this:

let url = APIs.Yelp.Business.detail(id: "businessId")

In my opinion, this approach is fine, but not very consistent, because some endpoints are enum cases, and some are static functions. The usage is also inconsistent, because for the enum cases, we use .url to get the URL, meanwhile static functions return URL directly. But it does simplifies the custom RawRepresentable conformation away, so pros and cons, it’s up to you.

That’s it, this is how I organize my endpoints in my projects, and I find it scalable and maintainable, fulfilling my needs.

Another thing worth mentioning is: if you are using a private API, or you use a private API key (like Yelp API key) that you wish to hide from public, you may not want to include those url and keys in your code, but use environment variables instead to access those values for safety purpose. I will talk about environment variables and Xcode schemes in my future articles.

If you want to watch the video version of the same content, check out my video tutorial: https://youtu.be/2fPr7r6XWoQ

This is my first time writing on Medium, and English is my second language, so I’m not very confident about how I did. If you like this article, please give a clap; if you have criticism or suggestions, please share them in the comment section, I will make sure to read all of them. Thanks for your support!

Until next time, happy coding!

--

--