GraphQL as your Service Layer

Speaker

@emaSuriano

http://emanuelsuriano.design/

#100DaysOfCode

My App

FTP

HTTP

REST

HTTP
SOAP

Common Implementation

const myService = await params => {
 const requestParams = adaptParamsForRequest(params);

 const response = fetch(MY_SERVICE_URL, {
   headers: SERVICE_HEADERS,
   body: requestParams,
   ...more
 });

 return parseResponse(response);
}

GraphQ what? 🤔

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.

A query language for APIs

Query language refers to any computer programming language that requests and retrieves data from database and information systems by sending queries.

Schema 🧠

Specifies the capabilities of the API and defines how clients interact with the data

 

 

The contract between the client and the server.

SDL

The syntax for writing schemas.

It has 2 components:

  • Type
  • Field
type Car {
  patent: String!
  color: String!
}

Query Type 🔍

Used by the client to request the data it needs from the server.

 

The Client decides what it really wants! 💪

{
 allCars {
   patent
 }
}
{
 "data": {
   "allCars": [
     {
       "patent": "ABC 123"
     },
     {
       "patent": "BQK 893"
     },
     {
       "patent": "POI 098"
     }
   ]
 }
}
{
 allCars {
   patent,
   owner {
    name,
   }
 }
}
{
 "data": {
   "allCars": [
     {
       "patent": "ABC 123",
       "owner": {
           "name": "Pepe",
       }
     },
     {
       "patent": "BQK 893",
       "owner": {
           "name": "Juan",
       }
     },
   ]
 }
}

Mutation Type ⛓

Allows the client to make changes to data stored on the server.

 

These changes could be:

  • Create new data
  • Update existing data
  • Delete existing data
mutation {
 createCar(
    patent: “QWE 112”,
    color: “red”,
 ) {
   patent
   color
 }
}
{
 "data": {
   "createCar": {
       "id": 12312301923    
   }
 }
}

Subscription Type 📩

Clients get notified when an event happened inside the server.

subscription {
  createCar {
    patent
  }
}
{
 "data": {
   "createCar": {
       "id": 12312301923,
       "patent": "ABC123",
   }
 }
}

GraphQL is a runtime for Fulfilling those Queries with your existing Data

{
 allCars {
   patent,
   owner {
    name,
   }
 }
}
{
 "data": {
   "allCars": [
     {
       "patent": "ABC 123",
       "owner": {
           "name": "Pepe",
       }
     },
   ]
 }
}

Legacy System

3rd Party API

getCars()
[...cars]
getOwner(car)
owner
storeRequest()

Resolvers 📡 

Each of the fields inside my schema corresponds to a function called resolver.

const CarResolver = {
  patent: async ({ id }) => {
    const patent = await getPatentFromDb(id);
    return patent;
  },
  owner: async ({ id }) => {
    const owner = await fetch(getOwnerInformatioById(id));
    return owner;
  }
};

The sole purpose of a resolver function is to fetch the data for its field 👌

Use Cases 💪

  • Connection with multiple services
  • Wrap a response from a server
  • Different client platforms

GraphQL Tools 🔧

GraphiQL

  • Allow its users to test and present GraphQL APIs.
  • Uses schema to provide information about data structure and types.
  • Supports autocompletion.

❤️

GraphQL is not a Framework ⛔️

Framework Specific

Depending on the server you want to run GraphQL, you will have to install a dependency for your specific technology.

express → express-graphql
hapi → hapi-graphql
micro → micro-graphql

...

My experience 💡

{
  "Brastlewark": [
    {
      "id": 0,
      "name": "Tobus Quickwhistle",
      "thumbnail": "http://www.publicdomainpictures.net/pictures/10000/nahled/thinking-monkey-11282237747K8xB.jpg",
      "age": 306,
      "weight": 39.065952,
      "height": 107.75835,
      "hair_color": "Pink",
      "professions": [
        "Metalworker",
        "Woodcarver",
        "Stonecarver",
        " Tinker",
        "Tailor",
        "Potter"
      ],
      "friends": ["Cogwitz Chillwidget", "Tinadette Chillbuster"]
    },
    {
      "id": 1
    }
  ]
}

Server Response

{
  "Brastlewark": [
    {
      "id": 0,
      "name": "Tobus Quickwhistle",
      "thumbnail": "http://www.publicdomainpictures.net/pictures/10000/nahled/thinking-monkey-11282237747K8xB.jpg",
      "age": 306,
      "weight": 39.065952,
      "height": 107.75835,
      "hair_color": "Pink",
      "professions": [
        "Metalworker",
        "Woodcarver",
        "Stonecarver",
        " Tinker",
        "Tailor",
        "Potter"
      ],
      "friends": ["Cogwitz Chillwidget", "Tinadette Chillbuster"]
    },
    {
      "id": 1
    }
  ]
}

Server Response

const getGnomes = () => fetch('gnomeURL'); //will return the whole list of gnomes

const getGnomeById = (id, loadFriends = true) => {
  const gnome = getGnomes().then(gnomes => {
    const result = gnomes.filter(gnome => gnome.id === id);
    if (loadFriends) {
      const friendsId = gnomes
        .filter(({ name }) => result.friends.includes(name))
        .map(gnome => gnome.id);
      result.friends = Promise.all(
        friendsId.map(id => getGnomeById(id, false))
      );
    }
    return result;
  });
};

Old School Approach

export const GET_GNOME_BY_ID = gql`
  query getGnomeById($id: ID!) {
    gnome(id: $id) {
      name
      thumbnail
      age
      weight
      height
      hair_color
      professions
      friends {
        id
        name
        thumbnail
        professions
      }
    }
  }
`;

GraphQL Approach

Implementation 👷‍♂️

Micro by Zeit

Apollo Server by Apollo


const typeDefs = `
 type Query { 
    allGnomes(name: String, professions: [String]): [Gnome],
    gnome(id: ID!): Gnome,
 }
 type Gnome {
   id: ID!,
   name: String,
   thumbnail: String,
   age: Int,
   weight: Float,
   height: Float,
   hair_color: String,
   professions: [String],
   friends: [Gnome],
   createdAt: Int,
 }
`;

Type Definition

import { makeExecutableSchema } from 'graphql-tools';
import { getGnomes, getGnomeById } from './query';

const resolvers = {
  Query: { allGnomes: getGnomes, gnome: getGnomeById },
  Gnome: {
    friends: async ({ friends }) => {
      const gnomes = await getGnomes();
      return gnomes.filter(({ name }) => friends.includes(name));
    }
  }
};

export default makeExecutableSchema({
  typeDefs,
  resolvers
});

GraphQL Schema

import fetch from 'node-fetch';
import memoize from 'fast-memoize';
import BASE_URL from './constants';

const fetchGnomes = memoize(async () => {
  const rawData = await fetch(BASE_URL);
  const jsonData = await rawData.json();
  return jsonData.Brastlewark;
});

const getGnomes = async (_, args) => {
  const gnomes = await fetchGnomes();
  if (!args) return gnomes;

  const { name = '', professions = [] } = args;
  return gnomes.filter(
    gnome =>
      (!name || new RegExp(name, 'i').test(gnome.name)) &&
      (!professions.length ||
        professions.every(prof => gnome.professions.includes(prof)))
  );
};

const getGnomeById = async (_, { id }) => {
  const gnomes = await fetchGnomes();
  return gnomes.find(gnome => gnome.id == id);
};

export { getGnomes, getGnomeById };

Query

Demo Time ✨

Last Words

REST API is dead. Long live GraphQL

Questions?

Related Links

Thanks!

GraphQL as your Service Layer

By Ema Suriano

GraphQL as your Service Layer

Talk about the advantages of using GraphQL as your service layers.

  • 1,300