A Backend with Flexibility
Tags: graphql, dgraph, backend, spanner
Published: 2021-11-12 16:04:52

When it comes to backend development, flexibility is not a common word. But why not given that everything else in the tech world is all about flexibility? Think about it, tablets are convertible to laptops and cell phones now double as wi-fi hotspots. Almost every new piece of hardware or SaaS platform is built for flexibility to serve multiple purposes and use cases.

For the most part, backends are very rigid systems. RESTful APIs always return a full dataset for a given endpoint even if you just want a specific field-set from the dataset provided.

When [GraphQL](https://graphql.org/) came on the scene, it was made as a way to give a backend flexibility, but there was only one problem, the backend of the GraphQL layer was still very rigid. GraphQL is language and database agnostic meaning that it can be implemented with a variety of programming languages and backed by any database model including rdbms and noSQL databases. Since GraphQL is not a data storage engine, but only a layer on top of an existing database, this grows the complexity of application development. Not only does a full stack developer need to define their data structure in their GraphQL schema, but they also need to design the models of their data structure in their database.

Spanner-like tools such as [Hasura](https://hasura.io/) came to the scene and attempted to make this easier to help generate much of the boilerplate, but the problem is that no matter how much these spanner tools auto generate, there is still a rigid disconnection between the GraphQL API and the database layer. GraphQL APIs allow front-end developers to easily query deep relationships in the database. What would take dozens of lines of code to join tables simply becomes asking for the nested field that links to the type.

For example let's look at how one might query for a users list of friends and then find other users who have pets of the same breed.

```SQL
SELECT
  user.name AS user_name,
  friend.name AS friend_name,
  pet.name AS pet_name,
  pet.breed,
  owner.name AS owner_name
FROM
  users user
  LEFT JOIN users_have_friends pivot_u_f
    ON user.id = pivot_u_f.user_id
  LEFT JOIN users friend
    ON pivot_u_f.friend_id = friend.id
  LEFT JOIN pets pet
    ON friend.id = pet.owner_id
  LEFT JOIN pets breed
    ON pet.breed = breed.breed
  LEFT JOIN users owner
    ON breed.owner_id = owner.id
WHERE
  user.id=1
```

Response:

| user_name | friend_name | pet_name | breed | owner_name |
|---|---|---|---|---|
| Tony | Terry | Bella | Yorky | Heather |
| Tony | Terry | Bella | Yorky | Brian |
| Tony | Amanda | Anna | Dachshund | Becky |
| Tony | Amanda | Anna | Dachshund | Becky |

Compared to a simple GraphQL query requesting the same data:

```GraphQL
{
  getUser(id:1) {
    name
    friends {
      name
      pets {
        name
        breed {
          name
          pets {
            name
            owner {
              name
            }
          }
        }
      }
    }
  }
}
```

Response:

```JSON
{
  "data": {
    "getUser": {
      "name":"Tony",
      "friends": [
        {
          "name":"Terry",
          "pets":[
            {
              "name":"Bella",
              "breed": {
                "name":"Yorky",
                "pets":[
                  {
                    "owner": {
                      "name": "Heather"
                    }
                  },
                  {
                    "owner": {
                      "name": "Brian"
                    }
                  }
                ]
              }
            }
          ]
        },
        {
          "name":"Amanda",
          "pets":[
            {
              "name":"Anna",
              "breed": {
                "name":"Dachshund",
                "pets":[
                  {
                    "owner": {
                      "name": "Becky"
                    }
                  },
                  {
                    "owner": {
                      "name": "Becky"
                    }
                  }
                ]
              }
            }
          ]
        }
      ]
    }
  }
}
```

As you can see the two queries return the same data, but with SQL the query is complex to write with all of the joins, and the response is a flattened table. With GraphQL, the query is very simple declaring the fields and relationships wanted, and the response takes the same shape as the query which saves from repeating data in every row of a table. GraphQL also allows the client to be aware of the shape of the data returned and makes it clear how many unique responses were returned on every relationship. From SQL with only the given query, it is impossible to know if there are four users named `tony` or if that is the same user with 4 pets, or is that only 2 unique pets. GraphQL solves all of this and adds flexibility into the backend.

But GraphQL is just a layer right? If GraphQL is just a layer, then the actual data still needs to be stored somewhere. Since the inception of GraphQL, implementations have been using rdbms and noSQL databases to persist the data. But this adds complexity, when what developers need is simplicity. If using GraphQL means that a developer has to maintain both a model in GraphQL and a model in the database, then there is the possibility that these models may be altered and break the whole GraphQL layer. This is not even to mention that resolving GraphQL deeply nested relationships creates a dreaded "N+1" problem which happens when multiple queries to a database are needed to resolve a single query operation. These query requests can grow by a factor of `N` given that `N` represents the count of parents in the relationship model.

So how does one create a backend that fixes these problems of rigidity, reduce complexity, and at the same time keep the flexibility of technologies like GraphQL?

Let me introduce you to [Dgraph](https://dgraph.io)! Dgraph is a spanner-like backend solution that at its core is a key-value store, but at the API level provided as an endpoint inside of the core provides a GraphQL API.

With one GraphQL schema, a user can achieve a fully generated production ready GraphQL API **WITH** persisted storage. To make this even easier for developers, Dgraph Labs launched [Dgraph Cloud](https://cloud.dgraph.io) which provides a BaaS in 2020 so users can deploy all of this in less than 5 minutes. A Shared instance in Dgraph Cloud with High Availability with up to 25 Gb of storage is only $39.99/mo. You can also try it out for free with the daily limitation of 1Mb of data transfer.

To generate a production ready GraphQL API with complete CRUD built in for the above examples, we could use the following schema:

```GraphQL
type User {
  id: ID!
  name: String! @search
  friends: [User] @hasInverse(field: "friends")
  pets: [Pet]
}
type Pet {
  id: ID!
  name: String! @search
  breed: Breed!
  owner: User! @hasInverse(field: "pets")
}
type Breed {
  name: String! @id
  pets: [Pet] @hasInverse(field: "breed")
}
```

After deploying this schema to Dgraph, we can use the generated GraphQL API to do all of these following tasks:

1. Add users with the mutation `addUser`
2. Add Pets with the mutation `addPet`
3. Add Breeds with the mutation `addBreed`
4. Add a new user with their new pets, and linking to existing breeds all in a single mutation using the `addUser` mutation and providing JSON input
5. Update a Pet and change the owner using the `updatePet` mutation
6. Delete a pet using the `deletePet` mutation
7. Update User's names using the `updateUser` mutation
8. Add relationships to friends which automatically build the inverse relationships indicated by the `@hasInverse` directives in the schema
9. Traverse the graph by querying a breed, finding all pets by that breed, and find all of those pet's owners using the `queryBreed` or `getBreed` queries
10. And so much more!

Did I mention that [Dgraph is production ready](https://dgraph.io/docs/deploy/production-checklist/) for your startup? In the next article, I will write how to add access control and use the Dgraph datastore as the source of truth for authenticating users and granting authorization based on roles [RBAC], attribute based access control [ABAC], and graph based access control [GBAC] all while using simple GraphQL query rules!

Photo Credit: Photo by [John Barkiple](https://unsplash.com/@barkiple?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) John Barkiple on [Unsplash](https://unsplash.com/s/photos/complex-network?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)