DEV Community

Cover image for Intro to GraphQL ❤️
Henry Arbolaez
Henry Arbolaez

Posted on • Updated on

Intro to GraphQL ❤️

It's been around 5 years since GraphQL was released by the Facebook Engineering team back in 2015. Since then, GraphQL has risen in popularity. Besides Facebook, companies like Airbnb, Netflix, Coursera, and many more have adopted GraphQL and it seems that have played very well from them not just in productivity but scalability.

What is GrapQL?

  • GraphQL is just a query language for fetching data
  • It serves as a specification, not an implementation. That being said, GraphQL does not tell you how to implement your solution that you can adapt to your needs.
  • It only exposes a single endpoint to the client that consists of queries, mutations and subscriptions.
  • The client only queries the data that it needs and the data is returned in the shape that it was requested.

Schema

We start by defining the schema in the server.

type User {
   id: ID!
   firstName: String!
   lastName: String!
   """
   company that the user is associated with
   """
   company: Company
}

type Company {
   id: ID!
   name: String
   """
   where is this company located
   """
   location: String
}

input UserInput {
   firstName: String!
   lastName: String!
   company: CompanyInput
}

input CompanyInput {
   name: String!
   location: String!
   userID: ID!
}

type Query {
   """
   fetch the current user
   """
   currentUser: User
}

type Mutation {
   userCreate(userInput: UserInput!): 
}
Enter fullscreen mode Exit fullscreen mode

Schemas are the core of the GraphQL server implementation. It describes everything that your API can do. All the queries the client can execute will be run against the scheme definition.

  • Query is the type where all your queries will be encapsulated.
  • Mutation is the type where your update-create-delete actions will happen.
  • input is normally used when mutating any data to defined the argument passed

As you noticed, this is how we comment in GraphQL :

"""
Comment here
"""
Enter fullscreen mode Exit fullscreen mode

Resolvers for the Schema

After defining the schema, we need to define the “Resolvers”. A Resolver is basically a function that knows how to resolve the type that the client is asking for.

const user = { id: 1, firstName: "Henry", lastName: "Arbolaez" };
const Query = {
  currentUser: (parent, args, context) => {
    /**
     in the real world you would connect to some database.
     return context.db.User.findById(context.userId);
    */
    return user;
  }
}
Enter fullscreen mode Exit fullscreen mode

That's how we defined a resolver for the currentUser.

Querying for the current user

query {
  currentUser {
    id
    firstName
    lastName
  }
}
Enter fullscreen mode Exit fullscreen mode

When we query for the current user using the above query, we will return the data in the exact shape that it was requested.

 {
   "currentUser": {
       "id": 1,
       "firstName": "Henry",
       "lastName": "Arbolaez",
    }
 }
Enter fullscreen mode Exit fullscreen mode

But, let’s say, we want to query for the company of the current user.

query {
  currentUser {
    id
    firstNmae
    lastName
    company {
      id
      name
      location
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

When we execute the company query, null will be returned because there are no resolvers that know how to resolve the company type inside the user type

We can get around this by resolving the company in the currentUser resolver extending the currentUser resolver above

const user = { id: 1, firstName: "Henry", lastName: "Arbolaez" };
const companies = { 1: { id: 1, userID: 1, name: "Course Hero", location: "Redwood City" } };

const Query = {
  currentUser: (parent, args, context) => {
    // const company = context.db.Company.findById(parent.id);
    const company = companies[user.id];
    return {
      ...user,
      company,
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Note There is a problem with this approach. We cannot guarantee that whenever the client asks for currentUser, it will always ask for the company that the user is associated with. A better approach is to have a resolver for the company type, and only resolve if the client asks for it.

const companies = { 1: { id: 1, userID: 1, name: "Course Hero", location: "Redwood City" } };

const Query = {
  currentUser: ....,
  User: {
    company: (parent, args, context) => {
      /**
        parent: is the user object in this case. Think as the parent, as the Type that is wrapping this resolver. In this case the User type.
      */
      // return context.db.Company.findById(parent.id)
      return companies[parent.id]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

We added the company resolver under the User type to match our schema definition. If we were to put the company in the Query type, it wouldn’t know what it’s going to resolve for, since the schema explicitly said that the company belongs to the User type.

By doing this - it’s quite an optimization since the client doesn’t have to ask for the company type when asking for the current user. The extra request to fetch the company is eliminated and our server is happy!!

Why Graphql?

  • GraphQL limits the number of requests being made to the server, enabling multiple queries simultaneously in one expansive query.
    • Usually, in REST, you will have different RESTful endpoints to do X operation.
-- users
GET https://example.com/users
CREATE https://example.com/users
GET https://example.com/users/1
DELETE https://example.com/users/1
PUT https://example.com/users/1

-- companies
GET https://example.com/companies
CREATE https://example.com/companies
GET https://example.com/companies/1
DELETE https://example.com/companies/1
PUT https://example.com/companies/1

-- companies associated with the user
GET https://example.com/user/1/companies
GET https://example.com/user/1/companies/1
Enter fullscreen mode Exit fullscreen mode
  • i.e. if we want to fetch all the companies for a given user, we will need to:
    1. Fetch the user endpoint to get the userId
    2. Fetch the companies for the given userId
  • Also, in REST, we aren’t aware of the shape of the data is coming back and the type of it.
  • in GraphQL this could be simply be sent to the server as one query while reusing code that is already there, and we would know beforehand what is the shape and type of that data.
query currentUser {
  companies {
    id
    name
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Improve developer experience and productivity
    • GraphQL is a strong type and has validation
    • There are a lot of tools around GraphQL, that increase productivity. One of them is the GraphQL Playground, which lets you introspect the query before having to write any code.
    • GraphQL will help standardized and simplify complexity in our API's. In the example above for the REST API, we wouldn't need to worry about creating those many resources.

Wrapping

I hope this intro to GraphQL has helped you to understand the basics of what a schema, resolvers, and the client request are. There are quite a few other topics that we can cover, but with this brief introduction of GraphQL, you can build a simple graph and dive deeper into more advanced topics. Here are some resources that I find useful.

Here are some resources that I find that are going to be useful:

Top comments (2)

Collapse
 
clavinjune profile image
Clavin June

Great Post! but I think everyone should know that it's quite hard for handling entity population and N+1 problem in graphql :(

Collapse
 
harbolaez profile image
Henry Arbolaez • Edited

Thank you Calvin. Absolutely, one of the hardest things to archive in GraphQL. But there are libraries that can help on that, like dataloader and if you can use prisma or hasura you don't need to worry about it.

But the N+1 issue in GraphQL without a doubt could be the trickiest and hardest thing to handle.

I'll try and make a post explaining the N+1 issue :)