What is Graphql
GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools.
Rest is used to created the APIs, while Graphql sits in front of the API. Graphql can sit in front of an existing API as well.
You can still have a API underneath Graphql.
Graphql is just a spec, what it can, it should, would do.
Facebook created GraphQL-JS, which is an JS implementation of graphql spec in JS.
It gives power to client, to request the data that they want. Not more, not less.
It's strongly types, like typescript.
Graphql is kind of SQL for your API. Since it's just a query language.
Rest vs graphql
Rest is an interface for building api, graphql is a query language.
They do similar things but differently.
Graphql has only one URL, mostly POST request. For query you can use GET.
In rest, shape and size of data is determined by server, while in graphql it depends on request/query.
In Rest, you make multiple API calls to get relational data, in graphql, it's just a single query. Example: To get all the posts by an author.
In Rest, single request will execute on single controller, but in Graphql, single request can execute on multiple resolver.
Graphql Schema
We already have a DB schema. Why do we need another schema.
Graphql Schema strictly defines what a resource is, how they relate and how can a client consume them.
DB schema is to keep our data consistent while entering to the DB, Graphql Schema is for what all resources are available, how they relate and how to query them.
Both schema can be same. DB schema is a good starting point for your Graphql schema.
Some graphql tools are out there, to create Graphql API from DB schema, and the other way around.
SDL
Creating schema with Schema Definition Language.
Instead of creating schema by using functions, use a verbose, string based syntax for your schemas. Later you can transform your schema into many other representations if needed.
Easier to read.
Scaler and Object types
Scaler are built in primitives.
String, Int, Float, Boolean, ID
Object types are custom shapes with fields that are either schaler types or other scaler types.
All the object types, has scaler types in leaf nodes.
Object type can also define any argument or validation.
Graphql Cheatsheet:
https://wehavefaces.net/graphql-shorthand-notation-cheatsheet-17cd715861b6
Query and Mutation type
type Cat {
name: String
age: Int!
bestFriend: Cat!
}
input CatInput {
name: String
age: Int!
bestFriend: Cat!
}
schema {
query: Query
mutation: Mutation
}
In Query type, we define what type of query we can make:
type Query {
myCat: Cat
cats: [Cat]
}
A mutation is just the same thing, just that it's intent is to mutate DB rather than just reading.
Mutation is most likely to have argument[s].
input CatInput {
}
type Mutation{
newCat(input: CatInput!): [Cat]
}
Resolvers
They resolve everything that can be a value. Enum, queries, mutations, union, interfaces.
Resolvers are like controllers, but instead they resolve all the way down.
This is where you talk to DB, data source.
Resolvers are responsible for retrieving data, like querying DB, querying third party service, querying another graphql schema.
Every query and mutation must have a resolver that returns the specified type.
Types and fields on types can also have resolvers of their own. Like to resolve id from _id, or to resolve relational data.
Creating Resolvers
Return the same shape as described in the schema, or delegate to another resolver.
type Query{
getData: Product!
}
//resolvers.js
export default {
Query: {
getData(_, argsFromQuery, context, info){
//context: share bw all the resolvers, passed from servers
//like cache, storage, db model, user object
//ast of the query, converting into a js representation of this
//abstract syntax tree
},
...
},
Mutations:{
},
Product:{
},
ID: {
}
}
Throwing errors:
throw new Error in a resolver will not stop the server, it will send the error to client in the error array. Since Graphql has try catch layer over the resolver
srcs:
- https://graphql.org/
- https://frontendmasters.com/courses/graphql/what-is-graphql/
Interfaces:
interface Animal{
species: String!
}
type Tiger implements Animal {
species: String!
stripeCount: Int
}
type Lion implements Animal {
species: String!
MainColor: String
}
type Query {
animals: [Animal]!
}
schema{
query : Query
}
Resolvers:
{
Query: {
animals(){
return [
{species: 'Tiger', stripeCount: 2},
{species: 'Lion', mainColor: 'Yellow'}
]
}
},
Animal:{
__resolveType(animal){
return animal.species
}
}
}
Client Query:
{
animals{
species
... on Tiger{
stripeCount
}
... on Lion{
mainColor
}
}
}
Union:
When you want a query to return possibly more than just one type. Union allows you to create a type that composed of many types where any of them can be returned.
union SearchItem = Person | Place | Restaurant | Review
Note: Use it the same way we used interface. Write resolve type, have fragments defined in client query.
Fragments:
Authrisation:
1. Outside of graphql, in the express server only. Before "new ApolloServer({...})". This is done when the server serves multiple applications and not just apollo server. If signin and signup are part of graphql, then this is a bad choice, since user won't be able to get to the graphql even for signin/signup
2.A When graphql is the only application. Then graphql context can do this.
new ApollorServer({context({req}){console.log('req is', req)}})
2.B. In the resolver. We can pass the context to resolver and resolver can do that. It's painfull if you have lots of resolvers and have to do the same thing again and again. If the resolvers are upto n level and all have to check for authentication, it's a waste of time.
3. By using custom directive.
type Game {
id: String @auth(role:'admin')
}
Even here, the actual work has to be done by resolver only(not sure, please check).
4. Using resolver middleware. Some of the graphql implementation support middleware. For eg. https://github.com/prisma-labs/graphql-yoga.
Testing with Jest:
1. Resolvers: As a regular function. Send the input and expect the output.
2. Schema:
2A: Expect the schema object(type, validations, etc) from schema STL string.
2B: Mock server to expect the query response
Note: MongoDB test setup was also done along with this.
Caching and Performance
1. Cache data from DB. Data loader does. More here: https://github.com/graphql/dataloader
2. Network cache. Graphql hits the same url all the time. All requests are POST most of the time. So headers and url are same, so how to cache? Something we have to do in the CDN layer. That too won't work if just a single field in the query changes, which results in low cache-hit ratio.
3. Apollo engine: https://www.apollographql.com/platform/
Custom scalers:
Define custom scaler types.
Alias
{
animals{
species
}
otherAnimals: animals{
species
}
}
Other Tools:
1. Prisma (open source): Creating graphql server from DB schema. https://www.prisma.io/
2. Hasura (open source): similar as above. https://hasura.io/
3. Graphql Anywhere, runs graphql anywhere, literally anywhere: https://www.npmjs.com/package/graphql-anywhere
4. https://www.npmjs.com/package/graphql-cli
5. Tipe: creating CMS from the schema. Creates schema, dashboard, etc. https://tipe.io/.
Doubts:
1.
//resolvers
export default {
Query: {
getData(_, argsFromQuery, context, info){
}
}
OR
export default {
Query: {
function(){
return {
getData: function(_, argsFromQuery, context, info){
}
}
}
2.
{Query: {
cat(){return {}},
},
Cat: {return {name: 'nimie', age: 5}}
}
What is we return the Cat type object from the query itself?
Like this? would the cat resolve be invoked in that case? If so? why, since the cat type object has been returned already by the query 'Query' resolver.
{Query: {
cat(){return {name: 'nimie', age: 5}},
},
Cat: {return {name: 'nimie', age: 5}}
}
3. Diff bw these resolvers
Query: {
cat: function(){
case1: adding Cat schema here
}
Query:{
cat: function(){},
case2: Adding Cat schema here, direct in the Query, not inside the parent resolver. Does the Cat resolver still get Parent data in this case? Does the field get resolved correctly in this case?
Cat: function(){}
}
4. What is __resolveType