GraphQL advanced concepts 💥💥

(Feb Blog)

Introduction 🙋

In this article, I’m going to share with you some advanced concepts that I’ve learned about GraphQL.

First ! Graphql subscriptions 💥💥:

“GraphQL subscriptions are a way to push data from the server to the clients that choose to listen to real-time messages from the server”

Example Graphql subscriptions App design
subscription Message {
Message {
mutation
node {
id
createdAt
text
author
}
}
}

Validations in GraphQL🚩:

@Non-null Validation :

The fields whose types have an exclamation mark ! are the non-null fields

@Custom Validation :

For Custom Validation like mail-validation, checking string’s length, checking if a number is within a given range we can implement custom validators.

The custom validation logic will be a part of the resolver function.

  • Solution 1 with Resolvers :
  1. Define our schema.graphql
type Query {
greeting:String
}

type Mutation {
signUp(input:SignUpInput):String
}

input SignUpInput {
email:String!,
password:String!,
firstName:String!
}

2. Create the Resolver :

const Mutation ={
signUp:(root,args,context,info) => {

const {email,firstName,password} = args.input;

const emailExpression = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;

const isValidEmail = emailExpression.test(String(email).toLowerCase())
if(!isValidEmail)
throw new Error("email not in proper format")

if(firstName.length > 15)
throw new Error("firstName should be less than 15 characters")

if(password.length < 8 )
throw new Error("password should be minimum 8 characters")

return "success";
}
}
module.exports = {Query,Mutation}

3. Run the Application:

query variable

{
"input":{
"email": "abc@abc",
"firstName": "kannan",
"password": "pass@1234"
}
}

The errors array contains the details of validation errors:

{
"data": {
"signUp": null
},

"errors": [
{
"message": "email not in proper format",
"locations": [
{
"line": 2,
"column": 4
}
],
"path": [
"signUp"
]
}
]
}

Solution 2 with schema directives:

We can use graphql-constraint-directive which allows using @constraint as a directive to validate input data.

Example of how to use it :

type SignUpInput {
email: String! @constraint(format: "email", maxLength: 255)
password: String! @constraint(maxLength: 255)
firstName: String! @constraint(pattern: "^[0-9a-zA-Z]*$", maxLength: 255)
lastName: String! @constraint(pattern: "^[0-9a-zA-Z]*$", maxLength: 255)
}

type Mutation {
signUp(input: SignUpInput!)
}

Authentication with GraphQL 🔓 🔏:

Like REST API, we should always care about who will interact with our GraphQL server, who is authorized to send queries and run mutations or subscriptions!

It’s not so different from the other approaches!

Example 1: Authentication with GraphQL using graphql-yoga

import { GraphQLServer } from 'graphql-yoga'
import * as jwt from 'jsonwebtoken'

const typeDefs = `type Query { hello(name: String): String! }`

const resolvers = {
Query: {
hello: (_, { name }) => `Hello ${name || 'World'}`,
},
}

const server = new GraphQLServer({ typeDefs, resolvers })
server.express.use((req, res, next) => {
const { authorization } = req.headers
jwt.verify(authorization, 'secret', (err, decodedToken) => {
if (err || !decodedToken) {
res.status(401).send('not authorized')
return
}
next()
})
})
server.start(() => console.log('Server is running on localhost:4000'))

Example 2: Authentication with GraphQL using apollo-server

context: ({ req }) => {
// get the user token from the headers
const token = req.headers.authorization || '';

// try to retrieve a user with the token
const user = getUser(token);

// optionally block the user
// we could also check user roles/permissions here
if (!user) throw new AuthenticationError('you must be logged in');

// add the user to the context
return { user };
},

Paginating and sorting with GraphQL 📃 📑:

Pagination :

Pagination is a tricky topic in API design. On a high-level, there are two major approaches regarding how it can be tackled

Limit-Offset: Request a specific chunk of the list by providing the indices of the items to be retrieved (in fact, you’re mostly providing the start index (offset) as well as a count of items to be retrieved (limit)).

Cursor-based: This pagination model is a bit more advanced. Every element in the list is associated with a unique ID (the cursor). Clients paginating through the list then provide the cursor of the starting element as well as a count of items to be retrieved.

Implement Pagination with Prisma :

  1. Define in our schema.graphql (add params `skip start index ` and `first the first x elements after a provided start index` to our query feed )
type Query {
info: String!
feed(filter: String, skip: Int, first: Int): [Link!]!
}

2. adjust the implementation of the feed resolver:

async function feed(parent, args, context, info) {
const where = args.filter ? {
OR: [
{ description_contains: args.filter },
{ url_contains: args.filter },
],
} : {}

const links = await context.prisma.links({
where,
skip: args.skip,
first: args.first
})
return links
}

3. Test the pagination API :

query {
feed(
first: 1
skip: 1
) {
id
description
url
}
}

sorting:

  1. Add the following enum definition to schema.graphql:
enum LinkOrderByInput {
description_ASC
description_DESC
url_ASC
url_DESC
createdAt_ASC
createdAt_DESC
}

2. adjust the feed query again to include the orderBy argument:

type Query {
info: String!
feed(filter: String, skip: Int, first: Int, orderBy: LinkOrderByInput): [Link!]!
}

3. adjust the implementation of the feed resolver:

async function feed(parent, args, context, info) {
const where = args.filter ? {
OR: [
{ description_contains: args.filter },
{ url_contains: args.filter },
],
} : {}

const links = await context.prisma.links({
where,
skip: args.skip,
first: args.first,
orderBy: args.orderBy
})
return links
}

4. Test your query :

query {
feed(orderBy: createdAt_ASC) {
id
description
url
}
}

Fragments, Interfaces, and Unions in GraphQL 📀 📼:

What are Fragments?

A fragment is basically a reusable piece of the query.

Fragments are a handy feature to help to improve the structure and reusability of your GraphQL code. A fragment is a collection of fields on a specific type.

Simple GraphQL Query without fragment :

Define Fragment:

Adding Fragment to Query code :

Interfaces in GraphQL?

Interface in programming: The interface is a structure that enforces certain properties on the object or class that implements the corresponding interface

Unions in GraphQL?

The Union type indicates that a field can return more than one object type

Error handling with GraphQL 🚫 ❌ :

Before we talk about how to handle errors in GraphQL, let’s identify what types of errors that we can get :

  1. Server problems (5xx HTTP codes, 1xxx WebSocket codes)
  2. Client problems e.g. rate-limited, unauthorized, etc. (4xx HTTP codes)
  3. The query is missing/malformed
  4. The query fails GraphQL internal validation (syntax, schema logic, etc.)
  5. The user-supplied variables or context is bad and the resolve/subscribe function intentionally throws an error (e.g. not allowed to view requested user)
  6. An uncaught developer error occurred inside the resolve/subscribe function (e.g. poorly written database query)

And the problem with Graphql gives you a result with data, even if that result contains errors, it is not an error! that’ why we should handle it

Example 1 of Implementation with Apollo-server :

Apollo Server provides a collection of predefined errors, including AuthenticationError, ForbiddenError, UserInputError, and a generic ApolloError

const {
ApolloServer,
gql,
AuthenticationError,} = require('apollo-server');

const typeDefs = gql`
type Query {
authenticationError: String
}
`;

const resolvers = {
Query: {
authenticationError: (parent, args, context) => { throw new AuthenticationError('must authenticate'); }, },
};

the response :

UserInputError Example :

const {
ApolloServer,
UserInputError,
gql,
} = require('apollo-server');

const typeDefs = gql`
type Mutation {
userInputError(input: String): String
}
`;

const resolvers = {
Mutation: {
userInputError: (parent, args, context, info) => { if (args.input !== 'expected') { throw new UserInputError('Form Arguments invalid', { invalidArgs: Object.keys(args), }); } }, },
};

and you will get a response like this :

Batching in GraphQL 📟:

“Batching is the process of taking a group of requests, combining them into one, and making a single request with the same data that all of the other queries would have made”

Caching in GraphQL 💲:

Caching is the process of storing data in a temporary storage area called cache.

In an endpoint-based API, clients can use HTTP caching to easily avoid refetching resources, and for identifying when two resources are the same.

There are three types of caching that can be involved in an endpoint-based API :

  • Client Caching: is the process of caching data in the client-side browser that helps to limit the data cost incurred by the user by keeping commonly referenced data locally. The client often requests data that may not be large but is indeed continuously needed
  • Network Caching: they intercept requests that look the same (based on various configurable criteria), returning a response early straight out of memory, instead of hitting the application serve
  • Application Caching: It can be implemented in the application with tools like Memcache, Redis, etc… one of the advantages for example if the remote server is not available due to a crash or network partitioning, the client can obtain a cached copy at the proxy. Hence, the robustness of the Web service is enhanced.

For the case of Graphql, there is no Network caching because all our interactions (queries or mutations ) with GraphQL API it will be with a single endpoint 🔧 🔨

So the caching needs to be done in the application and it’s recommended for the graphql website to be done into the client-side

Technical solution: “DataLoader” 📘 📙

a generic utility to be used as part of your application’s data fetching layer to provide a consistent API over various backends and reduce requests to those backends via batching and caching.

— — →What I have got from learning developers feedback it that “GraphQL definitely needs a better caching system to win the GraphQL vs REST war.”⚡️⚡️⚡️

GraphQL tools :

  1. GraphiQL — making GraphQL easier
  2. GraphQL Voyager : visualize your GraphQL
  3. GraphQL Docs : — auto-create your docs
  4. GraphQL IDE — the GraphQL playground
  5. Graphql-network: Chrome Devtool that provides a “network”-style tab for GraphQL requests to allow developers to debug more easily.
  6. Graphql-code-generator: GraphQL code generator with flexible support for custom plugins and templates
  7. swagger-to-graphql Swagger to GraphQL API adapter
  8. Prisma Modern DB toolkit to query, migrate and model your database

Conclusion :

Happy Learning :)

<script>alert('try your best')</script>