GraphQL Apollo Client with Angular v6.x or v7.x and JWT tokens

Ashok Vishwakarma
Ashok Vishwakarma

Feb 03, 2019 • 6 min read

I have talked about the server-side implementation of GraphQL in my earlier article and I have received many requests to write about the client-side implementation, which I have used Apollo Client in Angular 6.x application.

In this article, we will talk about Apollo Client for Angular, its dependencies and what packages I have used to in my application to build my Angular 6.x Application.

Apollo Client for Angular

As per their website,

Apollo Client is the ultra-flexible, community-driven GraphQL client for Angular, JavaScript, and native platforms. It is designed from the ground up to make it easy to build UI components that fetch data with GraphQL.

Which simply means that using Apollo Client will help you set up everything you need to connect to your GraphQL server with bare minimum code without worrying too much of GraphQL implementation on the client. Apollo Client supports the most popular frameworks and libraries like Angular, React, Vue, Meteor, Ember and Polymer.

To get started with Apollo Client for Angular, you need to install below npm packages

apollo-angular

A full-fledged Apollo Client package implemented for the applications built in Angular framework.

apollo-angular

Use your GraphQL data in your Angular app, with the Apollo Client. Latest version: 3.0.1, last published: 2 months ago. Start using…

www.npmjs.com

apollo-link

A network interface for Apollo Client

apollo-link

Flexible, lightweight transport layer for GraphQL. Latest version: 1.2.14, last published: 2 years ago. Start using apollo-link in your…

www.npmjs.com

apollo-link-context

Easily set a context on your operation, which is used by other links further down the chain.

apollo-link-context

An easy way to set and cache context changes for Apollo Link. Latest version: 1.0.20, last published: 2 years ago. Start using…

www.npmjs.com

apollo-link-error

Handle and inspect errors in your GraphQL network stack.

apollo-link-error

Error Apollo Link for GraphQL Network Stack. Latest version: 1.1.13, last published: 2 years ago. Start using apollo-link-error in your…

www.npmjs.com

apollo-link-http

Get GraphQL results over a network using HTTP fetch.

apollo-link-http

HTTP transport layer for GraphQL. Latest version: 1.5.17, last published: 2 years ago. Start using apollo-link-http in your project by…

www.npmjs.com

apollo-link-ws

Send GraphQL operations over a WebSocket. Works with GraphQL Subscriptions.

apollo-link-ws

WebSocket transport layer for GraphQL. Latest version: 1.0.20, last published: 2 years ago. Start using apollo-link-ws in your project by…

www.npmjs.com

graphql-tag

Helpful utilities for parsing GraphQL queries

Setting up the client

Setting up the Apollo client in Angular application is very simple and easy, all you have to do is add Apollo Angular Client in your src/app/app.module.ts file.

Setting up HttpLink

In the app.module.ts file, you can create a GraphQL HttpLink to access GraphQL over HTTP

const http = new HttpLink({
uri: `http://your_graphql_endpoint`
});

Setting up WebSocket Link

The WebSocketLink in Apollo lets you connect with your GraphQL server using Web Sockets.

const ws = new WebSocketLink({
uri: `ws://your_graphql_endpoint`,
options: {
reconnect: true
}
});

Setting up context

The context in Apollo Client lets you manage request headers and set the authorization token in every request made by Apollo Client to the GraphQL server.

const authContext = setContext(async (request, previousContext) => {
// get the token from session or storage
const token = await this.session.getToken();
if(!token) {
return {}
}
// Set the Authorization headers
return {
headers: {Authorization: `Bearer ${token}`}
}
});

Setting up the Error Handlers

Apollo Client comes with prebuilt Error and Exception Handler which can catch all the Errors and Expectations made my GraphQL server.

const error = onError(({graphQLErrors, networkError, response, operation}) => {
// Checking GraphQL Errors
if(graphQLErrors) {
graphQLErrors.map(({message, path, type}) => {
if(type === 'INVALID_TOKEN') {
this.session.destroy();
}
if(type === 'UNAUTHENTICATED') {
this.session.destroy();
}

console.log(`[GraphQL Error]: Type: ${type}, Message: ${message}`, path);
});
}
// Checking in Network Errors
if(networkError) {
console.log(`[Network Error]:`, networkError);
}
});

Setting up afterwareLink

Apollo Links also allows capturing all the responses from GraphQL server to one place, which can be helpful to set tokens and other required variables on the client side.

const afterwareLink = new ApolloLink((operation, forward) => {
return forward(operation).map(response => {
const {
response: { headers }
} = operation.getContext();
if(headers) {
const _token = headers.get('Authorization');

if(_token && _token !== 'null') {
const payload = getPayload(_token);
this.session.setToken(_token);
}
}
return response;
});
});

Splitting the HTTP and WebSocket

We need to split the ApolloLink for both the transport HTTP and WebSocket and using the split method from apollo-link package.

const link = split(({query}) => {
const { kind, operation } = getMainDefinition(query);
return kind === 'OperationDefinition' && operation === 'subscription';
}, ws, ApolloLink.from([authContext, error, afterwareLink, http]));

Joining all the pieces and creating final ApolloLink

We have created separate links and implemented various functions for these links such as handling all the requests being sent by ApolloClient with its headers, Error Messages thrown by the ApolloServer, Capturing all the responses received by ApolloClient with its headers and implement multichannel Link to the GraphQL sever on HTTP and WebSocket.

Not its time to join these pieces together and create the final ApolloLink using create method.

apollo.create({
link: link,
cache: new InMemoryCache(),
defaultOptions: {
query: {
fetchPolicy: 'network-only'
}
}
});

Putting everything into theapp.module.ts file.

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { Subscription } from 'rxjs';

import { ApolloModule, Apollo } from 'apollo-angular';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { setContext } from 'apollo-link-context';
import { onError } from 'apollo-link-error';
import { ApolloLink, split } from 'apollo-link';
import { HttpLink } from 'apollo-link-http';
import { WebSocketLink } from 'apollo-link-ws';
import { getMainDefinition } from 'apollo-utilities';

import { SessionService } from './common/session.service';


@NgModule({
declarations: [
...
],
imports: [
...
ApolloModule,
...
],
providers: [
...
],
bootstrap: [AppComponent]
})
export class AppModule{
constructor(
private apollo: Apollo,
private session: SessionService

) {
const protocol = environment.https ? 'https' : 'http';

// Creating new HttpLink for Apollo Client
const http = new HttpLink({
uri: `${protocol}://${environment.graphqlUrl}`,
});

// Creating new WebSocketLink for ApolloClient to support GraphQL subscriptions
const ws = new WebSocketLink({
uri: `ws:${environment.graphqlUrl}`,
options: {
reconnect: true
}
});

// authContext to set Authorization token for every request sent from client
const authContext = setContext(async (request, previousContext) => {
// Getting the token from the session service
const token = await this.session.getToken();

// return {} if token is not set yet
if(!token) {
return {}
}

// Set Authorization headers with token
return {
headers: {Authorization: `Bearer ${token}`}
}
});

// Error handling for GraphQL client
const error = onError(({graphQLErrors, networkError, response, operation}) => {
if(graphQLErrors) {
graphQLErrors.map(({message, path, type}) => {
// Destroy the session if INVALID_TOKEN receieved from the server
// Forcing user to login again
if(type === 'INVALID_TOKEN') {
this.session.destroy();
}

// Forcing user to login again if token is UNAUTHENTICATED
if(type === 'UNAUTHENTICATED') {
this.session.destroy();
}

// Loggin the GraphQL errors on the console
console.log(`[GraphQL Error]: Type: ${type}, Message: ${message}`, path);
});
}

// Login the Network errors
if(networkError) {
console.log(`[Network Error]:`, networkError);
}
});

// afterwareLink, Apollo link
const afterwareLink = new ApolloLink((operation, forward) => {
// tap to the forward reuqest operation
return forward(operation).map(response => {

// get the response headers
const {
response: {headers}
} = operation.getContext();

// check if headers recieved
if(headers) {
// get the token from the response header
const _token = headers.get('Authorization');

// check if token is not null
if(_token && _token !== 'null') {

// get playload for of the token
const payload = getPayload(_token);

// set the new token into the session
this.session.setToken(_token);
}
}

return response;
});
});

// creating the conditional link for http and ws requests
const link = split(({query}) => {
const { kind, operation } = getMainDefinition(query);
return kind === 'OperationDefinition' && operation === 'subscription';
}, ws, ApolloLink.from([authContext, error, afterwareLink, http]));

// creating the final Apollo client link with all the parameters
apollo.create({
link: link,
cache: new InMemoryCache(),
defaultOptions: {
query: {
fetchPolicy: 'network-only'
}
}
});
}
}

The above code is having all the required implementation for your application using Apollo Angular Client for a RESTful based web application which sends and received JWT tokens in every request.

Define GraphQL Fragments

A GraphQL Fragment is a shared piece of Query logic and has two main principles

  • Sharing fields between multiple queries, mutations or subscriptions.
  • Breaking your queries up to allow you to co-locate field access with the places they are used.

To define GraphQL Fragments I have created a new file src/app/fragments.ts

// src/app/fragments.ts
import gql from 'graphql-tag';

// A GraphQL Fragment on User type
export const userFields = gql`
fragment userFields on User {
first_name
last_name
email
password
bio
}
`
;

export const postFields = gql`
fragment postFields on Post {
author {
..userFeilds
}
title
description
banner
}
${userFields}
`
;

Define GraphhQL Queries, Mutations and Subscriptions

I have created a new file src/app/graphql.ts to define all the GraphQL Queries, Mutations and Subscriptions

// src/app/graphql.ts
import gql from 'graphql-tag';
import { userFields, postFields } from './fragments';
export const QUERY_USERS = gql`
query users {
users {
...userFields
}
}
${userFields}
`
;
export const MUTATION_LOGIN = gql`
mutation login($email: String!, $password: String!){
login(email: $email, password: $password){
...userFields
}
}
${userFields}
`
;
export const SUBSCRIPTION_POST = gql`
subscription post {
post {
...postFields
}
}
${postFields}
`
;

Queries, Mutations and Subscription

In Apollo client making a query to GraphQL endpoint is easy and simple, I have used Apollo from apollo-angular as a dependency in my services and used the same to make query, mutation and subscription requests

import { Injectable } from '@angular/core';
import { Apollo } from 'apollo-angular';

import {
QUERY_USERS,
MUTATION_LOGIN,
SUBSCRIPTION_POST
} from './graphql';

@Injectable({
providedIn: 'root'
})
export class AppService {
constructor(
private apollo: Apollo,
) { }

login(email: string, password: string) {
return this.apollo.mutate({
mutation: MUTATION_LOGIN,
variables: {
email,
password
}
}).toPromise(); // returning as promise
}

users() {
return this.apollo.query({
query: QUERY_USERS
}).toPromise();
}

posts() {
return this.apollo.subscribe({
query: SUBSCRIPTION_POST
}); // return subscription
}
}

Conclusion

Apollo Makes using GraphQL simple and easy and its backed by a huge community which helping people like us to adapt and start using GraphQL for our applications.

Let me know your implementation of GraphQL on client-side, I would love to learn more about the same.

Share with your developer friends and help the community.

Ashok Vishwakarma

Ashok Vishwakarma

Google Develover Expert — WebTechnologies and Angular | Principal Architect at Naukri.com | Entrepreneur | TechEnthusiast | Speaker