DEV Community 👩‍💻👨‍💻

Cover image for Designing GraphQL APIs - Best Practices & Learnings from REST API Design
Lucia Cerchie for StepZen

Posted on • Originally published at stepzen.com

Designing GraphQL APIs - Best Practices & Learnings from REST API Design

An API Layer As A Contract

Paraphrasing Eve Porcello: "a GraphQL API is an agreement between backend and frontend devs."

An API represents the data backend engineers need to provide and the data that frontend developers need to consume. Therefore, in a REST API, the endpoints represent the intersection of these needs.

This can get messy, say, if a backend engineer who is writing the ORM or API provides all the data on a customer to an endpoint and the frontend dev only needs a customer's name for a certain page. This can lead to either data pollution, in which case the frontend developer needs to write unnecessary business logic, or endpoint pollution, in which case the number of endpoints multiplies to the extent that both engineers lose track of the purpose of each endpoint.

GraphQL entered the scene as a way to avoid this type of pollution: ask for what you need, and get it.

And yet, there remains a factor that can multiply complexity: GraphQL queries. Used correctly, they minimize pain, but used incorrectly, they can create confusion and stifle an API layer.

What makes for a messy GraphQL API layer?

For one, naming convention should remain consistent within the API. If your API returns information on 3 different pet breeds but your naming is inconsistent, it's just not easy to read:

catBreedQuery
dog_breed_query
getInfoOnHamsterBreeds
Enter fullscreen mode Exit fullscreen mode

Consistency is key for easy reading:

cat_breed_query
dog_breed_query
hamster_breed_query
Enter fullscreen mode Exit fullscreen mode

Next, meaningless nesting can make for an inconvenient developer experience as well.

For example, wrapping your users data unnecessarily in a data element:

"data" : {
    "users" { :
        [
            {
            "username": "Jane Doe"
            "id": 1
            "joined_at": 12-12-2002
            },
            {... etc}
       ]
Enter fullscreen mode Exit fullscreen mode

Why not just have the following?

    "users" : {
        [
            {
            "username": "Jane Doe"
            "id": 1
            "joined_at": 12-12-2002
            },
            {...etc}
       ]
Enter fullscreen mode Exit fullscreen mode

These are implementation details, but when we 'zoom out' to the level of the architecture of the API itself, we see another way of creating a cluttered API by using GraphQL queries incorrectly: following the structure of the REST API directly. If you do that, then why have a GraphQL API at all?

To see what I mean, let's pretend we're GraphQL-izing a REST API with three endpoints:

  1. https://themovieapi.com/getmoviebyid
  2. https://themovieapi.com/getactorsbymovieid
  3. https://themovieapi.com/getactorspreviousrolesbyid

The developer wants to enter a movie id, and then get a list of actors with their previous roles.

So, to layer your GraphQL API, you create these queries:

  1. getmoviebyid
  2. getactorsbymovieid
  3. getactorspreviousrolesbyid

This means that the developer must write a lot of frontend business logic. They must first retrieve the results of getmoviebyid, then they must use that result in getactorsbymovieid, and lastly they must use that result in getactorspreviousrolesbyid. Why even use GraphQL in the first place, from the frontend developer's perspective?

This is where a GraphQL API designer can leverage StepZen to create an API that is easily consumed and understood by other developers.

StepZen provides many custom directives to users of its endpoints. One of them, @sequence, solves this problem within a few lines of code:

getActorsPreviousRolesByMovieID(movie_id: String!): MovieType
  @sequence(
  steps:
    [
      {query: "getmoviebyid"}
      {query: "getactorsbymovieid"}
      {query: "getactorspreviousrolesbyid"}
    ]
)
Enter fullscreen mode Exit fullscreen mode

This condenses 3 queries to one-- each query in the steps argument automagically picks up its parameters from the previous step (while the first uses the parameters entered into the query).

Using these and other solutions like @materializer make connecting APIs with StepZen a matter of a few lines of code in the GraphQL layer, rather than hundreds of lines of business logic in the client.

An API Layer as Communication

Insofar as an API layer is a contract between developers, it represents a type of static communication between developers.

Much like in the case of software documentation, there are many principles of communication that then apply to API design.

  1. Communicate only what is needed.
    • Documentation for an npm package needn't explain why a recursive function was needed to build it-- it only needs to explain how to use the package. Similarly, a GraphQL API does not need to make a call for REST endpoint it's layered over, if the API designer makes use of directives like @sequence.
  2. Create a smooth entry point.
    • Documentation should take the reader clearly from one concept to the next. Similarly, GraphQL queries should create predictable patterns for the developer to follow: -- allStates -- statesByName -- statesByCapital -- allStateCapitals -- stateCapitalByName
  3. Consider the readability of your API.
    • avoid meaningless nesting and inconsistent queries

Conclusion

GraphQL APIs partially evolved to help clean up messy REST APIs.

But how to redesign a confusing GraphQL API?

Developers can implement a StepZen GraphQL layer using @sequence or a similar custom StepZen directive, to craft an API that will smooth out the developer experience.

It might also mean careful renaming of your queries.

Prevention is the best medicine-- patience and forethought in the process of adopting GraphQL, especially since incremental adoption is used at many organizations, can reduce your technical debt in the future.

If you've got questions about anything GraphQL API design, hit us up on Discord.

Top comments (0)

🌱 DEV runs on 100% open source code that we started called Forem.

You can contribute to the codebase or host your own.