GraphQL and the end of shared client-side model objects

· 11 minute read

In traditional client-server development, client-side models don’t often differ too dramatically from server-side models.

Should they?

A standard RESTful API might serialize a server-side user model as such:

{
  "id": 12345,
  "firstName": "Bryan",
  "lastName": "Irace",
  "avatar": {
    "thumbnail": "https://some/s3/url/a39d39fk",
    "large": "https://some/s3/url/39fka39d"
  },
  "profession": "Software developer",
  "location": {
    "city": "Brooklyn",
    "state": "NY"
  },
  "friendCount": 40
}

Instances of this same user model type can be vended by multiple routes, e.g.:

  1. /me – A route that returns the currently authenticated user
  2. /friends - A route that returns the current user’s friends

RESTful APIs aren’t inherently type-safe, so a frontend developer will generally learn that these routes both return objects of the same User type by looking at the API documentation (and hoping that it’s accurate1), or by eyeballing the HTTP traffic itself.

After realizing this, a type definition like the following can be manually added to your client-side application, instances of which you can populate when parsing response bodies from either of these two routes:

interface Avatar {
  thumbnail: string;
  large: string;
}

interface User {
  id: number;
  firstName: string;
  lastName: string;
  avatar: Avatar;
  profession: string;
}

This shared, canonical User model can be used by any part of the frontend application that needs any subset of a user’s attributes. You can easily cache these User instances in your client-side key-value store or relational database.

Suppose that your application includes the following capabilities (and is continuing to grow in complexity):

  1. Rendering user profiles (requires all user properties)
  2. Viewing a list of friends (requires user names and avatars only)
  3. Showing the current user’s avatar in the navigation (requires avatar only)

Your server will initially return the same user payload from all of these features’ routes, but this won’t scale particularly well. A model with a large number of properties2 will be necessary to render a full profile, but problematic when rendering a long list of users’ names and avatars. It’s unnecessary at best and a performance bottleneck at worst3 to serialize a full user when most of its properties are simply going to be ignored.

Perhaps your API developer changes your server to return only a subset of user properties from the /friends route. This is followed by a change to the API documentation and a hope that your frontend engineer notices, at which point they’ll add a new type to the client-side codebase. Perhaps this new type looks something like:

interface SimpleUser {
  id: number;
  firstName: string;
  lastName: string;
  avatar: Avatar;
}

At this point, your frontend will need to:

  1. Keep track of which routes vend User instances vs. SimpleUser instances, when processing HTTP responses
  2. Have its caching logic updated to support both of these different types

User vs. SimpleUser is admittedly a coarse and superficial distinction. If we add a third flavor to the mix, what would we reasonably name it?

Instead of SimpleUser, we could instead call this new type FriendListUser, named after the feature that it powers. Having separate user models for each use case is a more scalable approach – we could end up with quite a few different versions, whose names all accurately convey intention better than “simple” does:

  • FriendListUser
  • EditAccountUser
  • ProfileUser
  • LoggedOutProfileUser

The risk here is that we’re likely to incur a lot of overhead in terms of keeping track of which routes vend which models, and how to make sense of all of these different variants when modeling our frontend persistence layer.

Reducing this overhead by more tightly coupling our client-side type definitions to our API specification would be a big step in the right direction. GraphQL is one tool for facilitating exactly this.

GraphQL

There’s a lot to like about GraphQL – if you’re looking for a comprehensive overview, I’d recommend checking out the official documentation.

One advantage over traditional RESTful interfaces is that GraphQL servers vend strongly-typed schemas. These schemas can be programatically introspected, making your APIs self-documenting by default4. But this is an essay about client-side models, not avoiding stale documentation.

With higher model specificity comes higher clarity and efficiency, the primary downside being the additional work involved to maintain a larger number of models. Let’s dig deeper into how code generation can mitigate this downside.

By introspecting both:

  1. Our backend API’s strongly-typed schema
  2. Our frontend app’s data needs

We can easily generate bespoke client-side models for each individual use case.

First, we must understand how GraphQL queries work. In a traditional RESTful API server, the same routes always vend the same models. Let’s say that our GraphQL server exposes the following two queries:

  1. me: User
  2. friends: [User]

While both queries expose the same server-side User model, the client specifies the subset of properties that it’s interested in, and only these properties are returned. Our frontend might make the following query:

me {
  firstName
  lastName
  location {
    city
    state
  }
  avatar {
    large
  }
}

The server will only return the properties specified above, even though the server-side user model contains far more properties than were actually requested.

Similarly, this query will return a different subset:

friends {
  firstName
  lastName
  avatar {
    thumbnail
  }
}

Code generation tools can introspect these client-side queries, plus the API schema definition, in order to:

  1. Ensure that only valid properties are being queried for (even directly within your IDE, validating your API calls at compile-time)
  2. Generate client-side models specific to each distinct query

In this case, the generated models would look as follows:

// Generated to support our `me` query

interface User_me {
  firstName: string;
  lastName: string;
  location: User_me_location;
  avatar: User_me_avatar;
}

interface User_me_location {
  city: string;
  state: string;
}

interface User_me_avatar {
  large: string;
}

// Generated to support our `friends` query

interface User_friends {
  firstName: string;
  lastName: string;
  avatar: User_friends_avatar;
}

interface User_friends_avatar {
  thumbnail: string;
}

Each of our app’s components can now be supplied with a model perfectly suited to their needs, without the overhead of maintaining all of these type variations ourselves.

Trees of components, trees of queries

User interface libraries like React and UIKit allow encapsulated components to be composed together into a complex hierarchy. Each component has its own state requirements that the other components ideally needn’t concern themselves with.

This is at odds with traditional RESTful API development, where a single route will often return a large swath of data used to populate whole branch of the component tree, rather than just an individual node.

GraphQL query fragments better facilite the colocation of components and their data requirements:

const FriendListItem = gql`
  fragment FriendListItem on User {
    firstName
    lastName
    avatar {
      thumbnail
    }
  }
`;

const FriendListRow = props => {
  return (
    <Container>
      <Avatar source={props.user.thumbnail} />

      <NameLabel firstName={props.user.firstName}
        lastName={props.user.lastName} />
    </Container>
  );
};

This results in a “query hierarchy” that much better aligns with our component hierarchy.

Just as a UI rendering layer will walk the component tree in order to lay out our full interface hierarchy, a GraphQL networking layer will aggregate queries and fragments into a single, consolidated payload to be requested from our server.

Heterogenous caching made simple

GraphQL is a high-level query language; while you can use it to query a GraphQL server, client-side libraries such as Apollo and Relay can act as abstraction layers on top of both the network as well as an optional local cache5.

(Additionally, Apollo and Relay both also handle the code generation and query fragment unification outlined in the sections above 💫)

Traditional client-server applications often end up with logic that looks as follows:

// Check if we have a certain user in our cache
if let user = db.query("SELECT FROM users WHERE id = \(id)").first {
  callback(user)
}
else {
  // Fetch from the network instead
  API.request("/users?id=\(id)").onSuccess { response in
    callback(response.user)
  }
}

In this case, we’re querying for the same user via two different mechanisms: SQL against our local database and a URL-encoded query string against our remote server.

Apollo and similar libraries allow us to more declaratively specify the data that we need in one unified way. This level of abstraction lets us delegate the heavy-lifting – checking whether our request can be fulfilled purely from cache, and augmenting with additional remote data if not.

To continue our example: if you first made a friends query, your cached users would only contain firstName, lastName, and avatar.thumbnail properties. A subsequent me query for one of those same users would hit the server in order to “fill in” the additional properties – location and avatar.large. From this point forward, subsequent friends or me queries could avoid the network roundtrip altogether5.

As long as two user models have the same unique identifier, it doesn’t matter which subset of their properties were fetched in which order. Apollo will take care of normalizing them for us.

Sound magical? It certainly can be, for better or for worse. Like all high levels of abstraction, it’s amazing when it’s working and infuriating when it isn’t.

But such is the promise of GraphQL; the sky is the limit for tooling when a typed schema definition is the foundation being built upon. Tooling of this nature can make a premise that would’ve otherwise seemed prohibitively unwieldy – having a distinct client-side model type for every slight use case variation – not only achievable, but ideal.

  1. More likely if it’s generated using something like Swagger, less likely if your API engineer is manually doing their best to keep it up to date. 

  2. Just imagine how many properties a Facebook user is comprised of, for example. 

  3. Not to mention, disrespectful of your users’ time and cellular data plans. 

  4. Tools like GraphiQL allow you to see exactly which server-side models are vended by each of your GraphQL queries. 

  5. Depending on your caching policy, of course.  2

How I learned to read (with regularity, as an adult)

· 8 minute read

Yours truly, in January 2013:

Last year I only read two books. I’m disappointed by that, and my goal this year is to read somewhere closer to ten, alternating between fiction and non-fiction. To hold myself accountable, I will try to write a little about each, starting with this one.

I only ended up reading five books in 2013, but read seven in 2017 and seventeen in 20181. At risk of jinxing it, I think it’s safe to say that – many years later – I’ve finally adopted a real reading habit for the first time as an adult.

Habits are obviously quite personal, and there’s no shortage of advice out there on how to make one out of reading in particular2. Here are the few keys to my own personal success on this front that I hope may prove helpful to you as well.

Committing to a platform

I spent far too long being really indecisive as to whether I should invest in Apple or Amazon:

I eventually committed to Amazon due to Goodreads, local library rentals, and e-ink Kindle devices (more on all of these later). Most important was to simply make a decision, however; I think I’d be doing just fine had I chose Apple instead.

I still prefer Apple for programming books because I find the Apple Books software for macOS to be far superior to Kindle’s desktop web experience, but these are a small percentage of the books that I read overall. I’ve been very happy with Kindle as my default for everything else.

Keeping a streak

If you’re the type of person who finds streaks to be motivational, keeping track of a streak is an easy way to make sure that you’re spending at least a few minutes reading each day. Just five or ten minutes each day is a great place to start; those minutes really do add up, but perhaps more importantly, a streak helps ingrain the habit of dipping into your book during your commute or while waiting in line at the grocery store. Or really, anytime when you might otherwise default to scrolling through Twitter or Instagram.

My 2018 reading streak
I highly recommend Streaks for iOS. It even has built-in timers for letting you know when you’ve finished reading for the desired increment.

Allowing myself to take breaks from books

There are some days where I simply don’t want to read a book, but that doesn’t mean I can’t read something else of value. The goal here was to instill a reading habit, not necessarily a book habit.

Maybe I just finished my last book and haven’t decided which my next one will be, or maybe there’s a lot going on in the news that I’d like to catch up on. I use Instapaper to save articles of all different kinds to read at a later date, and on days where I choose to spend at least ten minutes reading these instead of a book, I’m happy to count that against the streak as well.

Using an e-ink Kindle

I resisted getting a dedicated Kindle device until December 2016, primarily because I really don’t mind reading on iOS. I’ve read entire books on iPhones without issue.

I eventually moved to a Kindle as my primary reading device not because I prefer e-ink screens to LCD or OLED, but because I am very bad at avoiding distractions. My Kindle doesn’t have notifications, a web browser, or a Twitter client. I can do nothing but read on it, so read without interruption I do.

Modern Kindles also have the benefit of being quite light and small. I expected to only bring mine along when carrying a bag, but it fits pretty easily in many of my jacket pockets. As such, I’ve carried it far more often than I would’ve originally guessed.

Reading wherever, whenever

Despite having really grown to like my Kindle, it’s only with me a fraction of the time compared to my iPhone. Like with cameras, the best book is the one that’s with you. Kindle sync means that your book is always with you as long as your phone is, even if your primary reading device is not. And as mentioned, I’m definitely not above reading books on my phone.

There are plenty of times where I most certainly do not want to carry anything more than my phone – specifically in the summer – but this doesn’t mean that there can’t still be any number of planned or unplanned reading opportunities along the way.

Drowning out the noise

In the spirit of reading wherever and whenever, it’s often helpful to have something to help you drown out the background noise of your neighborhood coffee shop, or the New York City subway. I’ve been using Brain.fm almost exclusively for the past year but have also used Noizio a lot in the past. They’re both really good at what they do.

Putting a book cover on my lock screen

Whenever I start a new book, I put the book’s cover on my iPhone lock screen3. It’s not exactly subtle, but I benefit from the constant reminder that I could be reading my book whenever I’m tempted to use my phone for something less beneficial.

“Breakfast of Champions” adorning my lockscreen
“Bonnie made a joke now as she served him his martini. She made the same joke every time she served anybody a martini. “Breakfast of Champions,” she said.”

If you’d like to make your own lock screen wallpaper, here’s the Sketch template that I use.

Using Goodreads

Goodreads is a social network for keeping track of what you and your friends are reading and want to read in the future. It’s owned by Amazon, and as such is able to automatically track any books that you’ve read using Kindle apps or devices.

The Goodreads app, showing a feed of updates from friends
Having Goodreads friends like Katie is one of the best ways to keep inspired and motivated.

While the website and iOS app could both use a bit of user interface love, I’ve found it to be a huge help in keeping a reading habit by providing:

  1. A steady influx of new book suggestions, specifically from those who share your tastes
  2. Encouragement in the form of annual challenges
  3. Guilt, if you’ve lapsed a bit yet see all of the books that your friends keep finishing

Renting from the library

Did you know that not only can you borrow physical books for free from your local library, but ebooks as well? More surprising, perhaps: Libby, the iOS app used to do so, is actually quite nice.

The Libby app, used to rent e-books from local libraries
Libby even supports multiple accounts, if you’re lucky enough to have more than one in your area.

The selection isn’t perfect, but I’ve been able to find a lot of books on my reading lists at either the New York or Brooklyn Public Libraries4. You may need to wait a few weeks for popular books to become available, but you can put them on hold such that they’re automatically rented once they are. Once rented, you can keep the book for up to 21 days (which I’ve found can serve as a helpful forcing function to make sure you’re moving through it with haste).


These are just a few tactics, but they’ve worked well for me over the past year and I expect will continue in years to come. I still think there’s room for improvement5, but the first step to improving a habit is to have said habit in the first place. As of 2018, I can finally say that I do.

  1. I didn’t really keep track from 2014 through 2016. Day One and Goodreads indicate that I read at least three in 2014, at least three in 2015, and at least two in 2016. Nothing to brag about. 

  2. A couple of favorites: this one from Rick Webb (my former Tumblr colleague and a far more prolific reader than I am ever likely to be), and this one from Paul Stamatiou. 

  3. I wish I could take credit for this, but I got the idea from Twitter at some point and can’t find the source for the life of me. Apologies! 

  4. Your mileage may vary depending on your local library, of course. 

  5. Specifically, retaining and utilizing information gleaned from non-fiction books

The Series Zero still ticks

· 2 minute read

On Friday, September 14 at 2:58 AM, my alarm woke me up so that I could pre-order an Apple Watch Series 4 the moment that it went on sale. If you’re reading this blog and/or follow me elsewhere on the Internet, this might not surprise you very much. But it might surprise you to learn that prior to this month, I was still wearing an original Apple Watch – henceforth referred to as the Series Zero – almost every day since its release in 2015.

It feels safe to say that most meditations on consumer technology in 2018 have a tinge of negativity to them, and as such a soliloquy in praise of the Series Zero feels in order. Are there times over the past three-plus years that I wished mine were faster and smaller, or that it had GPS? Absolutely. But in an age when shortened attention spans and heightened expectations always have us ready for what’s newer and shinier, the duration over which my Series Zero served me well feels commendable to say the very least.

Apple’s AirPods were released in December 2016 – over a year and seven months after the Series Zero. With both the Series 1 and Series 2 watches already being available for sale at this point, it wouldn’t have been too surprising or indefensible if the AirPods weren’t compatible with the Series Zero. But they were, and this combination was instrumental in my training for and eventually completing my first half-marathon in Brooklyn in May of 2018.

Running the 2018 Brooklyn Half-Marathon

Despite my surname, this was the first race that I had run since high school.

As M.G. Siegler notes, the Series Zero’s longetivity can in part be explained by the fact that watch apps still aren’t particularly useful even on the newer, faster models:

While Apple did eventually fix my not-even-year-old third-generation Apple Watch which spontaneously broke, I had to send it in twice, which means I was left wearing my older Apple Watch as a backup quite a bit. This worried me since it is far slower than the third-gen. But in reality, I realized that barely mattered. Again, because I don’t use any apps. So really, the Apple Watch just pushes notifications to me, which work just as well on previous models. And the (first-party) apps I do use, like timers, and the workout app, all run basically the same.

Perhaps a better watch app narrative would’ve been strong enough incentive for me to have upgraded sooner, and maybe I’m unintentionally highlighting the flaws of the the Apple Watch as an app platform moreso than the Series Zero’s durability and Apple’s commitment to keeping it in good working order.

Regardless, Apple produced a wearable device in 2015 that I still personally deemed worthy of putting on each morning in 2018. If that’s not worth taking a break from our keyboard or USB-C grievances to accredit, I’m not sure what is.