In this blog we will try to understand the basic concepts of GraphQL. Make sure to read my previous blog as this is in continuation of that.
Endpoint
First basic thing to understand is that, GraphQL run on a single endpoint, as opposed to rest api’s have multiple rest api’s url. So all graphql queries, mutations etc all run on a single api end point only.
Queries
Queries are used to fetch data in graphql. All queries are structured in json format e.g
user {
username,
password
}
// or
{
test
}
// or
query {
user {
id,
profile {
firstname
},
address{
street
}
}
}
//query keyword is optional
So as such query are just json structure. What a query returns or what a query “resolves” to is defined by a resolver on the backend. And what are possible queries we can execute is defined by “schema” in the backend.
So as such queries are just simply json data structure’s we can call on the frontend but they need to be defined by “schema” and “resolvers” in the backend to make them valid.
To make a query valid, it should be define in the schema.
Schema
Schema is the full graphql definition done on server side where we define queries, types, resolves. GraphQL is strongly typed language similar to typescript, where we define our schema using scalar types such a Int, String, Boolean, ID, Float called scalar types
type Query {
user : ID
}
Above we defined a simple query in the backend schema called “user” which returns a type “ID”
This can now be called via our frontend client as
query {
user
}
Now at this stage we just define our query structure, we haven’t yet define how this will return data. That will come through resolvers.
We can also define complex query using “types”
TYPES
If we want our previous query to return an object i.e json object we need to define a “Type”
type User {
username: String,
password: String
}
type Query {
user: User
}
Cool! Now we defined a query to that returns a json object. Next what actual data this will return will be setup via “resolver”
RESOLVERS
How we define resolvers, the syntax depends on the language you choose for backend, but lets take example of NodeJS with apollo-graphql it would look like this
{
Query: {
user : async () => {
//do any db operations here which you want
return {
username : ...,
password: ...
}
}
}
}
Complex Queries
Till now what we saw was a simple example of an query, the real power of GraphQL comes on how we are able to define complex types, queries. Let’s see how
type User {
id: ID!, //! means is required field
username: String,
password: String,
profile: Profile,
address: [Address]
}
type Profile {
firstname: String,
lastname: String,
email: String
}
type Address {
street: String,
pincode: String
}
Cool! We defined our entities almost exactly same as our nosql db!
Also, we need to define resolver for every non scalar type!
{
Query : {
user: async (_ , { id }) => {
return await dataStore.getUserByID(id)
}
},
User : {
profile: async ( _ ) => {
return await dataStore.getProfileForUser(_.id)
},
address: async ( _ ) => {
return await dataStore.getAddressForUser(_.id)
}
}
}
Looks, good. We need to define resolver for every non scalar type. Resolver means how it resolves to data.
Next let’s define our query
type Query {
user(id: ID): User //we passed argument to our query!
}
Now our frontend looks like this
query {
user (id: 123) {
id,
username,
profile {
firstname
},
address {
street
}
}
}
Now we discussed initially that graphql allows frontend developers to choose what they want! Let’s see how
query {
user {
id
}
}
// above query just return user id thats all
query {
user {
id,
profile {
firstname
}
}
}
Now above query will return id, and profile name only. it's upto the frontend developer what he wants. also the address resolver i.e db operation won't be called at all in this case as it was not requested
Frontend Devloper can decide want he wants based on the schema
Schema AGAIN
After we understood above concepts, schema basically connects “queries”, “types”, “mutations”, “resolvers” all together and is available to the frontend developer as well. So the dev can look at the schema and see what all (apis) queries, mutation he has at his disposal and structure his code accordingly!
Mutiple Queries
In our same schema definition lets add another type Role
type Role {
id: ID!,
name : String!
}
type User {
id: ID!, //! means is required field
username: String,
password: String,
role: Role!
}
type Query {
user (id: ID!) : User,
role (id: ID!): Role,
roles : [Role]
}
Now we have define 3 queries, (i am not showing how to define resolvers but they need to be). The frontend dev can club queries as required
query {
user (id: 12) {
id,
profile {
name
},
role {
id,
name
}
},
role (id: 1){
name
},
roles {
name
}
}
// we are able to call queries together in single api call!
Mutations
Till now we understood queries quite well. We saw what are types, queries, schema, resolvers. There is one small bit left i.e mutation. When we want to update data we need to define mutation. All other concepts are same, just add the keyword mutation e.g
type mutation {
addUser(username: String!, password: String!) : ID,
addRole(name:String!): ID,
deleteRole(id: ID!): [Roles]
//etc..
}
Everything else is same need to define resolvers as well for this.
Functions
Most of the times when we call queries, mutations from frontend we would need to pass variables e.g
query {
user (id: 12){
id
}
}
//here 12 is hard coded but in real life this would be a variable
query getUser($id: ID!){
user ( id: $id) {
id
}
}
// this is how a query function looks and we can pass variables
//simarly for mutation
mutation addUser($username: String!, $password: String!){
addUser(username: $username, password: $password)
}
Fragment
Many time we need to reused fields in our queries, mutation on the frontend. e.g we have profile fields which we might want to reuse in multiple components. We can define reusable fragment (P.S. This is a advance concept and its use might not be clear at this stage, and you can skip it. Also this is frontend only)
fragment userFields on Profile {
firstname,
lastname,
}
query {
user (id: 12) {
profile {
...userFields
}
}
}
This basically allows us to reuse fields and our queries/mutations looks shorter
Now we have seen and understood all the basic concepts of graphql!
Why GraphQL
Now to answer our original question in previous blog on why to use graphql
- Queries, Mutation are in line with our data structures (nosql cloud db’s)
- Frontend dev’s can request data as needed and design there components as per the schema
- It’s strongly typed and dev’s can look at schema to understand all queries, mutation they have access
- Frontend, Backend, Database design all follow the same pattern of nested tree structure and fit in together
Why Not Use GraphQL
- For smaller apps, project its faster to go with rest api to get things up and running faster
- Not ideally suited for older db designs like mysql and relational database
GraphQL vs Redux
Generally speaking graphql and redux are not related at all! Graphql is a concept for api and redux is state management. But in the end, graphql makes redux redundant in practice.
We have seen all basic concepts related to graphQL which you can read in official docs as well to understand further https://graphql.org/learn/