GraphQL

GraphQL works as such:

  1. server defines a schema (shared contract) in a SDL (Schema Definition Language) including the DTO (types) and queries that use these DTO (types) as parameter and return types.
  2. server implements the queries using resolvers. GraphQL throws error if resolver doesn’t return data with type defined in the query.
  3. the client writes a query requesting specific fields it needs. if it’s query signature doesn’t match exactly to type defined in the server, GraphQL engine throws an error. It’s easier to enforce this type safety if you use code generation tools for producing interfaces from the server type definitions.

One of the biggest advantages of GraphQL is client-specified queries. The client can request for only the data it needs from the data it can return from the query. It breaks down to 3 simple rules:

  1. The capability (server) The server publishes a strict menu (the Schema). It says, “Here is everything I am capable of giving you, and these are the exact data types they will be.”
  2. The Demand (Client) The client looks at that menu and writes an order (the Query) asking for the exact subset of items it needs for the current screen. Nothing more, nothing less.
  3. The Rejection (Enforcement) If the client tries to order something not on the menu (a field that doesn’t exist, a typo, or a sensitive field the server deliberately left out), the GraphQL engine intercepts it immediately.

Examples: Schema in the server:

type Book {
  id: ID!
  title: String
  author: String
  published: Int
}

type Query {
  books: [Book]
  book(id: ID!): Book
}

type Mutation {
  addBook(title: String!, author: String!, published: Int): Book
}

client request

query {
  books {
    id
    title
    author
  }
}

mutation AddBook($title: String!, $author: String!, $published: Int) {
  addBook(title: $title, author: $author, published: $published) {
    id
    title
    author
    published
  }
}

using these queries and mutations

// query
const res = await fetch(url, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ query }),
});

// mutation
// 
// you could also directly use values intead of variables
// mutation AddBook {
//   addBook(title: "harry potter", author: "jk", published: "2010-10-10") {
//     id
//     title
//     author
//     published
//   }
// }
// variables are resolved by graphql in the backend.
const variables = { title: "Dune", author: "Frank Herbert", published: 1965 };
const res = await fetch(url, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ query: mutation, variables }),
});

every mutation and query is a POST request. graphql doesn’t support other http methods.

Since graphql is a separate language, web servers and clients can’t userstand it. you need a graphql client, and a graphql engine in your standard server / graphql server itself (that has graphql engine) to use graphql.

When to use GraphQL over REST

As you can see, graphql is more complex than rest. It only exports 1 api endpoint. Everything is done by GraphQL middleware in the server.

Only use GraphQL when you have multiple frontends that require different shapes of data. And only when bandwidth is a real issue.

Use REST when you have to expose your API to anyone (you don’t know who is going to use it, or how).

also, since graphql only exposes one endpoint (/graphql) it prevents caching.


Further reading: