Unlock GraphQL's full Potential for Frontend

@vilvaathibanpb

What does GraphQL solve

  • Better Client Experience
  • One API for all
  • Fix over/under fetching
  • No version hell
  • Work with exisiting systems

Auto Docs and Playground

Code generation

# schema.graphql
type Query {
  getUser(id: ID!): User
}

type User {
  id: ID!
  name: String!
}
# queries.graphql
query GetUser($id: ID!) {
  getUser(id: $id) {
    id
    name
  }
}

Code generation

overwrite: true
schema: schema.graphql
documents: "src/**/*.graphql"

  src/generated/react-hooks.tsx:
    plugins:
      - "typescript"
      - "typescript-react-apollo"

Code generation

overwrite: true
schema: schema.graphql
documents: "src/**/*.graphql"
generates:
  src/generated/types.ts:
    plugins:
      - "typescript"
  src/generated/react-hooks.tsx:
    plugins:
      - "typescript"
      - "typescript-react-apollo"

Code generation

import React from 'react';
import { useGetUserQuery } from '../generated/react-hooks';

const UserProfile = ({ userId }) => {
  const { data, loading, error } = useGetUserQuery({
    variables: { id: userId },
  });

  if (loading) {
    return <div>Loading...</div>;
  }

  if (error) {
    return <div>Error: {error.message}</div>;
  }

  const user = data?.getUser;

  return (
    <div>
      <h2>User Profile</h2>
      <p>Name: {user?.name}</p>
    </div>
  );
};

export default UserProfile;

Automatic update of data on write

// schema.graphql
type Todo {
  id: ID!
  text: String!
  completed: Boolean!
}

type Query {
  todos: [Todo]!
}

type Mutation {
  createTodo(text: String!): Todo!
}

Automatic update of data on write

const [createTodo] = useMutation(CREATE_TODO, {
  // Refetch the GET_TODOS query after the mutation
  refetchQueries: [{ query: GET_TODOS }], 
});

Cache Policy

  • Cache-first
  • Cache-only
  • Network-only
  • Cache-and-Network
  • No-cache

Cache-first

import { useQuery } from "@apollo/client";

const { loading, error, data } = useQuery(GET_TODOS, {
  fetchPolicy: "cache-first",
});

Cache-only

import { useQuery } from "@apollo/client";

const { loading, error, data } = useQuery(GET_TODOS, {
  fetchPolicy: "cache-only",
});

Network-only

import { useQuery } from "@apollo/client";

const { loading, error, data } = useQuery(GET_TODOS, {
  fetchPolicy: "network-only",
});

Cache-and-network

import { useQuery } from "@apollo/client";

const { loading, error, data } = useQuery(GET_TODOS, {
  fetchPolicy: "cache-and-network",
});

No Cache

import { useQuery } from "@apollo/client";

const { loading, error, data } = useQuery(GET_TODOS, {
  fetchPolicy: "no-cache",
});

Out of the box - Pagination

  const { loading, error, data, fetchMore } = useQuery(GET_ITEMS, {
    variables: { limit: 10, offset: 0 }, // Initial values for pagination
  });
  
  
  export const GET_ITEMS = gql`
  query GetItems($limit: Int!, $offset: Int!) {
    items(limit: $limit, offset: $offset) {
      id
      name
      description
    }
  }
`;

Optimistic UI

const optimisticTodo = {
      id: 'temp-id-' + Date.now(), // Use a temporary ID
      text,
      completed: false, // Assuming new todos are initially incomplete
    };
type Mutation {
  createTodo(input: TodoInput!): Todo!
}

input TodoInput {
  text: String!
  completed: Boolean
}

type Todo {
  id: ID!
  text: String!
  completed: Boolean!
}

Optimistic UI

createTodo({
      variables: { input: optimisticTodo },
      optimisticResponse: {
        __typename: 'Mutation',
        createTodo: optimisticTodo,
      },
      update: (cache, { data: { createTodo } }) => {
        // Update the cache with the real data when the response is received
        cache.modify({
          fields: {
            todos(existingTodos = []) {
              const newTodoRef = cache.writeFragment({
                data: createTodo,
                fragment: gql`
                  fragment NewTodo on Todo {
                    id
                    text
                    completed
                  }
                `,
              });
              return [...existingTodos, newTodoRef];
            },
          },
        });
      },
    });

Magic of CacheĀ 

Other benefits

  • Out of the box Cache persist and sharing
  • Local state management

Designing better Queries

Better purpose

type Query {
  getUser(id: ID, email: String): User
}

type User {
  id: ID!
  username: String!
  email: String!
  // Other user fields
}
type Query {
  getUserByID(id: ID!): User
  getUserByEmail(email: String!): User
}

type User {
  id: ID!
  username: String!
  email: String!
  // Other user fields
}

Better Types - Custom Scalars

input EventInput {
  name: String!
  date: String!
}
input EventInput {
  name: String!
  date: Date!
}

Better Types - Enums



type User {
  id: ID!
  username: String!
  email: String!
  role: String!
}
enum UserRole {
  USER
  ADMIN
  MODERATOR
  GUEST
}

type User {
  id: ID!
  username: String!
  email: String!
  role: UserRole!
}

Over Exposing



type User {
  id: ID!
  username: String!
  email: String!
  role: String!
  bank_acc_no: String
  personal_email_id: String
}

Other Backend Tips

  • Federation
  • Schema linting
  • Schema Design
  • Security

Thank You

@vilvaathibanpb

Made with Slides.com