GraphQL Alternative: GraphQL vs. AskQL

How does AskQL differ from GraphQL?

Efi Shtain’s question was repeated in various forums

After writing about AskQL and how to use AskQL with nodejs, the obvious question that repeat itself was: How does it differ from GraphQL?

In this article I will try to show the main benefits of AskQL by converting a simple Covid-19 GraphQL project into AskQL.

The Project

The project is a small Covid-19 api server powered by GraphQL. Here’s the original repository:

https://github.com/vinitshahdeo/covid19api by Vinit Shahdeo

This repository sends some queries to an open covid-19 api and returns them to the client.

I’ve forked the repository and added a simple client to it. Let’s look at its code.

The GraphQL Server

const express = require('express');
const graphqlHTTP = require('express-graphql');
const schema = require('./schema');
const app = express();
app.use(express.static('public'));
app.use(
'/graphql',
graphqlHTTP({
schema,
graphiql: true,
}),
);
app.listen(8081);
view raw server.js hosted with ❤ by GitHub
Code snippet #1: the GraphQL powered server main file

Code snippet #1 shows the GraphQL server setup. It sets up the client (line 7). It then sets up a GraphQL endpoint via the GraphQL express middleware (line 9).

By going to http://localhost:8081, you can actually get to the UI that is connected to the GraphQL server (Figure 1).

Figure 1: the GraphQL query UI. Just enter a valid query and get the results back.

The actual GraphQL magic happens in the schema.js file (Code snippet #2).

const axios = require('axios').default;
const {
GraphQLSchema,
GraphQLString,
GraphQLObjectType,
GraphQLList,
GraphQLInt
} = require('graphql');
const CovidDataType = new GraphQLObjectType({
name: 'CovidStats',
fields: () => ({
active: {
type: GraphQLInt
},
confirmed: {
type: GraphQLString
},
deaths: {
type: GraphQLString
},
recovered: {
type: GraphQLString
}
})
});
const StateCovidDataType = new GraphQLObjectType({
name: 'statewise',
fields: {
state: {
type: GraphQLString
},
active: {
type: GraphQLString
},
confirmed: {
type: GraphQLString
},
deaths: {
type: GraphQLString
},
recovered: {
type: GraphQLString
}
}
});
const DailyCovidDataType = new GraphQLObjectType({
name: 'daily',
fields: {
date: {
type: GraphQLString
},
dailyconfirmed: {
type: GraphQLInt
},
dailydeceased: {
type: GraphQLInt
},
dailyrecovered: {
type: GraphQLInt
}
}
});
/**
* Total data
*/
const RootQuery = new GraphQLObjectType({
name: 'RootQueryType',
fields: {
total: {
type: CovidDataType,
async resolve() {
const data = await axios.get('https://api.covid19india.org/data.json')
.then(res => res.data.statewise[0]);
return data;
}
},
statewise: {
type: new GraphQLList(StateCovidDataType),
async resolve() {
const data = await axios.get('https://api.covid19india.org/data.json')
.then(res => res.data.statewise.splice(1));
return data;
}
},
datewise: {
type: new GraphQLList(DailyCovidDataType),
async resolve() {
const data = await axios.get('https://api.covid19india.org/data.json')
.then(res => res.data.cases_time_series);
return data;
}
}
}
});
module.exports = new GraphQLSchema({
query: RootQuery
});
view raw schema.js hosted with ❤ by GitHub
Code snippet #2: The GraphQL schema used for the covid-19 api.

Lines 12-67 in Code snippet #2 are the definition of 3 GraphQL object types (CovidDataType, StateCovidDataType and DailyCovidDataType).

Lines 72-100 in Code snippet #2 define another object type that also includes resolvers for 3 properties: total, statewise and datewise. Each property is of the Object types defined above respectively.

The resolvers for each property fetch data from the covid api, and return a certain portion of it.

Lines 102-104 just export a GraphQL schema that is passed to the GraphQL express middleware.

If you try to enter the following queries, you will get the wanted results:

GET overall COVID-19 stats

{ 
  total {
    active
    confirmed
    deaths
    recovered
  }
}

GET statewise COVID-19 stats

{
  statewise {
    state
    active
    confirmed
    deaths
    recovered
  }
}

GET datewise COVID-19 stats

{
  datewise {
    date
    dailyconfirmed
    dailydeceased
    dailyrecovered
  }
}

The AskQL Server

For this purpose, I’ve just used the AskQL server from the AskQL Nodejs Quickstart article. By cloning it and using the main branch, you will receive the exact same query UI – only this time the backend is powered by AskQL.

Now – part of the power of AskQL is that we don’t really need to do anything to make the same queries work. For instance, if we’d like to get the datewise data set, our AskQL query will look like this:

ask {
  fetch('https://api.covid19india.org/data.json')['cases_time_series']
  :map(fun(dataSet) {
                         return {
                            data: dataSet['date'],
                            dailyconfirmed: dataSet['dailyconfirmed'],
                            dailydeceased: dataSet['dailydeceased'],
                            dailyrecovered: dataSet['dailyrecovered']
                          }
                        })
}

Code snippet #3: An AskScript that can be pasted into the AskQL demo website.

Code snippet #3 is written all client side, but the same as the GraphQL datewise query.

The difference here – we did not need to make any change serverside.

AskQL Resolvers

As shown in Code snippet #2, you can create resolvers in GraphQL. For parity sake, I’m going to show how AskQL resolvers work.

In AskQL, the resolvers are called resources. Let’s create the statewise resource in AskQL.

import askql from "askql";
import {values} from '../resources/index.js';
import middlewareFactory from "askql/askExpressMiddleware/askExpressMiddleware.js";
import { customResources } from "./statewise.js";
const { askExpressMiddleware } = middlewareFactory;
const { resources } = askql;
export const askMiddleware = askExpressMiddleware(
{ resources: {…resources, …customResources}, values },
{ callNext: true, passError: true }
);
view raw askql.js hosted with ❤ by GitHub
import askql from 'askql';
import fetch from 'node-fetch';
const { askvm } = askql;
export const customResources = {
statewise: askvm.resource({
resolver: async () => {
const res = await fetch('https://api.covid19india.org/data.json');
const fullData = await res.json();
const dataSet = fullData.statewise.splice(1);
return dataSet.map(({ state, active, confirmed, deaths, recovered }) => ({
state,
active,
confirmed,
deaths,
recovered,
}));
},
}),
};
view raw statewise.js hosted with ❤ by GitHub
Code snippet #4: The statewise resource (statewise.js) and the AskQL express middleware setup with the custom resource (askql.js).

In Code snippet #4, in the statewise file (bottom) we have a simple code that does the same as the GraphQL statewise resolver.

Line 8 fetches the data, line 9 returns the JSON, line 10 returns the part of the data we want (all except the first array member) and line 11 does the mapping into the schema we want.

In the askql.js file (top) you can see the custom resource is added to the list of resources sent to the AskQL Express Middleware in line 9.

The query that calls for the statewise resource is shown in Code Snippet #5.

 ask {
    {
      data: {
        statewise: statewise()
      }
    }
 }

Code Snippet #5: The statewise query in AskQL.

This returns exactly the same data as the GET statewise COVID-19 stats GraphQL query mentioned above. You can see the two queries side by side in Figure 2.

Figure 2: The two queries return the same statewise data.

The Big Deal

We saw above that while we can create resolvers in AskQL, they are not always needed.

While in GraphQL you’d need to create resolvers for new types of data, in AskQL you are not limited to what the backend team created for you as a client.

Why is this so valuable?

There are many use cases for this.

No need to deploy a new server version

There might come a time when the client needs some new data from the existing dataset. For instance – statewise also includes the lastupdatedtime property.

The GraphQL server does not expose this to the client. In order to expose this to the client, the GraphQL server needs to be redeployed with an updated schema.

Not so for AskQL. As we saw in Code snippet #3, you can send the mapping instruction via the AskScript query, directly from the client.

This means, the client team can move ahead without waiting for a server redeployment.

Access to new resources

The last use case was accessing data that was hidden by the resource/resolver.

Another use case is a situation in which the remote API was upgraded, or a new microservice was raised with information you need in order to farther enrich your data.

In the GraphQL server, this would again require the backend team to redeploy a new version of the server.

In AskQL, it is as easy as writing a new script.

In our example, the JSON fetched was showing data by states in India. A new microservice was created that handles the data by districts (https://api.covid19india.org/state_district_wise.json).

Instead of creating a new resolver in the backend, the client side can just do the following:

ask {
  const statewiseData = statewise();
  const districtData = fetch('https://api.covid19india.org/state_district_wise.json');
  statewiseData:map(fun(stateData) {
    {
      stateData,
      districtsData: districtData[stateData['state']]
    }
  });
}

Code Snippet #6: The statewise query enriched with district data in AskQL.

In Code snippet #6 we get the states data array, and enrich it with the district data fetched from the new server. As easy as that.

You can check the covid-19 branch in the askql-demo repository for the full example.

Why no just query from the client?

That’s a good question, and there are various reasons for that. One reason that comes into mind – especially in commercial applications – is security.

The AskQL server will be an internet facing server. It might be that it has access to non-internet facing servers inside an AWS VPC (or other cloud vendors equivalents).

So the AskQL server is mainly a security gateway for the client to be able to fetch data that is not internet facing and thus is not available directly.

Performance-wise, servers might have an optimised access to some resources. The fetch request might actually be much faster via the AskQL proxy rather than directly from the client.

Another performance use case can be caching – the AskQL server might handle requests caching and serve them in order to reduce the load on more expansive resources like DB’s.

Summary

In the past, the DB was the “King” and the clients had to code accordingly. DB normalisation came into effect in the client’s code.

GraphQL came to solve the “DB is king” problem. It’s now possible to allow the frontends to query as they know – simple JSONs that fit them.

In this article we saw that AskQL solves this issue, just like GraphQL does, but goes one step farther.

AskQL allows more flexibility since the client is not dependent on schemas set in the server. This way, it allows the client(s) to advance without waiting for the server to redeploy.

This reduces the coupling between client and server.

Besides flexibility in a single client, this allows us to build multiple clients that have different needs with only one (scalable) endpoint.

The fact that one endpoint can serve multiple clients, and indeed, multiple types of clients, we can use optimisation techniques like caching in order to improve performance and save valuable money for our growing company.

You can try out AskQL in the official repository, and also meet the AskQL team over at the AskQL discord channel.

Thanks a lot for Omer Koren from webiks and Piotr from xFAANG for the kind and thorough review!

Sign up to my newsletter to enjoy more content:

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments