Article
Gevorg Arutiunian · Sep 4, 2018

I already talked about [GraphQL](https://community.intersystems.com/post/graphql-intersystems-data-platforms) and the ways of using it in this article. Now I am going to tell you about the tasks I was facing and the results that I managed to achieve in the process of implementing GraphQL for InterSystems platforms.
## What this article is about
- Generation of an [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree) for a GraphQL request and its validation
- Generation of documentation
- Generation of a response in the JSON format
Let’s take a look at the entire process from sending a request to receiving a response using this simple flow as an example:

The client can send requests of two types to the server:
- A schema request.
The server generates a schema and returns it to the client. We’ll cover this process later in this article.
- A request to fetch/modify a particular data set.
In this case, the server generates an AST, validates and returns a response.
## AST generation
The first task that we had to solve was to parse the received GraphQL request. Initially, I wanted to find an external library and use it to parse the response and generate an AST. However, I discarded the idea for a number of reasons. This is yet another black box, and you should keep the issue with long callbacks in mind.
That’s how I ended up with a decision to write my own parser, but where do I get its description? Things got better when I realized that [GraphQL](http://facebook.github.io/graphql/October2016/) was an open-source project with a pretty good description by Facebook. I also found multiple examples of parsers written in other languages.
You can find a description of an AST [here](http://facebook.github.io/graphql/October2016/#Document).
Let’s take a look at a sample request and tree:
```
{
Sample_Company(id: 15) {
Name
}
}
```
**AST**
```
{
"Kind": "Document",
"Location": {
"Start": 1,
"End": 45
},
"Definitions": [
{
"Kind": "OperationDefinition",
"Location": {
"Start": 1,
"End": 45
},
"Directives": [],
"VariableDefinitions": [],
"Name": null,
"Operation": "Query",
"SelectionSet": {
"Kind": "SelectionSet",
"Location": {
"Start": 1,
"End": 45
},
"Selections": [
{
"Kind": "FieldSelection",
"Location": {
"Start": 5,
"End": 44
},
"Name": {
"Kind": "Name",
"Location": {
"Start": 5,
"End": 20
},
"Value": "Sample_Company"
},
"Alias": null,
"Arguments": [
{
"Kind": "Argument",
"Location": {
"Start": 26,
"End": 27
},
"Name": {
"Kind": "Name",
"Location": {
"Start": 20,
"End": 23
},
"Value": "id"
},
"Value": {
"Kind": "ScalarValue",
"Location": {
"Start": 24,
"End": 27
},
"KindField": 11,
"Value": 15
}
}
],
"Directives": [],
"SelectionSet": {
"Kind": "SelectionSet",
"Location": {
"Start": 28,
"End": 44
},
"Selections": [
{
"Kind": "FieldSelection",
"Location": {
"Start": 34,
"End": 42
},
"Name": {
"Kind": "Name",
"Location": {
"Start": 34,
"End": 42
},
"Value": "Name"
},
"Alias": null,
"Arguments": [],
"Directives": [],
"SelectionSet": null
}
]
}
}
]
}
}
]
}
```
## Validation
Once we receive a tree, we’ll need to check if it has classes, properties, arguments and their types on the server – that is, we’ll need to validate it. Let’s traverse the tree recursively and check whether its elements match the ones on the server. [Here’s](https://github.com/intersystems-ru/GraphQL/blob/master/cls/GraphQL/Query/Validation.cls) how a class looks.
## Schema generation
A **schema** is a type of documentation for available classes and properties, as well as a description of property types in these classes.
GraphQL implementations in other languages or technologies use resolvers to generate schemas. A resolver is a description of the types of data available on the server.
**Examples or resolvers, requests, and responses**
```
type Query {
human(id: ID!): Human
}
type Human {
name: String
appearsIn: [Episode]
starships: [Starship]
}
enum Episode {
NEWHOPE
EMPIRE
JEDI
}
type Starship {
name: String
}
```
```
{
human(id: 1002) {
name
appearsIn
starships {
name
}
}
}
```
```json
{
"data": {
"human": {
"name": "Han Solo",
"appearsIn": [
"NEWHOPE",
"EMPIRE",
"JEDI"
],
"starships": [
{
"name": "Millenium Falcon"
},
{
"name": "Imperial shuttle"
}
]
}
}
}
```
However, before we generate a schema, we need to understand its structure, find a description or, even better, examples. The first thing I tried was attempting to find one that would help me understand the structure of a schema. Since GitHub has its own [GraphQL API](https://developer.github.com/v4/explorer/), it was easy to get one from there. But the problem was that its server-side was so huge that the schema itself occupied 64 thousand lines. I really hated the idea of delving into all that and started looking for other methods of obtaining a schema.
Since our platforms are based on a DBMS, my plan for the next step was to build and start GraphQL for PostgreSQL and SQLite. With PostgreSQL, I managed to fit the schema into just 22 thousand lines, and SQLite gave me an even better result with 18 thousand lines. It was better than the starting point, but still not enough, so I kept on looking.
I ended up choosing a [NodeJS](https://graphql.org/graphql-js/) implementation, made a build, wrote a simple resolver, and got a solution with just 1800 lines, which was way better!
Once I had wrapped my head around the schema, I decided to generate it automatically without creating resolvers on the server in advance, since getting meta information about classes and their relationships is really easy.
To generate your own schema, you need to understand a few simple things:
- You don’t need to generate it from scratch – take one from NodeJS, remove the unnecessary stuff and add the things that you do need.
- The root of the schema has a queryType type. You need to initialize its “name” field with some value. We are not interested in the other two types since they are still being implemented at this point.
- You need to add all the available classes and their properties to the **types** array.
```
{
"data": {
"__schema": {
"queryType": {
"name": "Query"
},
"mutationType": null,
"subscriptionType": null,
"types":[...
],
"directives":[...
]
}
}
}
```
- First of all, you need to describe the **Query** root element and add all the classes, their arguments, and class types to the **fields** array. This way, they will be accessible from the root element.
**Let’s take a look at two sample classes, Example_City and Example_Country**
```
{
"kind": "OBJECT",
"name": "Query",
"description": "The query root of InterSystems GraphQL interface.",
"fields": [
{
"name": "Example_City",
"description": null,
"args": [
{
"name": "id",
"description": "ID of the object",
"type": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
},
"defaultValue": null
},
{
"name": "Name",
"description": "",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
}
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "Example_City",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "Example_Country",
"description": null,
"args": [
{
"name": "id",
"description": "ID of the object",
"type": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
},
"defaultValue": null
},
{
"name": "Name",
"description": "",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
}
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "Example_Country",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
}
```
- Our second step is to go one level higher and extend the **types** array with the classes that have already been described in the **Query** object with all of the properties, types, and relationships with other classes.
**Descriptions of classes**
```
{
"kind": "OBJECT",
"name": "Example_City",
"description": "",
"fields": [
{
"name": "id",
"description": "ID of the object",
"args": [],
"type": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "Country",
"description": "",
"args": [],
"type": {
"kind": "OBJECT",
"name": "Example_Country",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "Name",
"description": "",
"args": [],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "Example_Country",
"description": "",
"fields": [
{
"name": "id",
"description": "ID of the object",
"args": [],
"type": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "City",
"description": "",
"args": [],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "Example_City",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "Name",
"description": "",
"args": [],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
}
```
- The third point is that the “types” array contains descriptions of all popular scalar types, such as int, string, etc. We’ll add our own scalar types there, too.
## Response generation
Here is the most complex and exciting part. A request should generate a response. At the same time, the response should be in the JSON format and match the request structure.
For each new GraphQL request, the server has to generate a class containing the logic for obtaining the requested data. The class is not considered new if the values of arguments changed – that is, if we get a particular dataset for Moscow and then the same set for London, no new class will be generated, it’s just going to be new values. In the end, this class will contain an SQL query and the resulting dataset will be saved in the JSON format with its structure matching the GraphQL request.
**An example of a request and a generated class**
```
{
Sample_Company(id: 15) {
Name
}
}
```
```
Class gqlcq.qsmytrXzYZmD4dvgwVIIA [ Not ProcedureBlock ]
{
ClassMethod Execute(arg1) As %DynamicObject
{
set result = {"data":{}}
set query1 = []
#SQLCOMPILE SELECT=ODBC
&sql(DECLARE C1 CURSOR FOR
SELECT Name
INTO :f1
FROM Sample.Company
WHERE id= :arg1
) &sql(OPEN C1)
&sql(FETCH C1)
While (SQLCODE = 0) {
do query1.%Push({"Name":(f1)})
&sql(FETCH C1)
}
&sql(CLOSE C1)
set result.data."Sample_Company" = query1
quit result
}
ClassMethod IsUpToDate() As %Boolean
{
quit:$$$comClassKeyGet("Sample.Company",$$$cCLASShash)'="3B5DBWmwgoE" $$$NO
quit $$$YES
}
}
```
How this process looks in a scheme:

For now, the response is generated for the following requests:
- Basic
- Embedded objects
- Only the many-to-one relationship
- List of simple types
- List of objects
Below is a scheme containing the types of relationships that are yet to be implemented:

Summary
- **Response** — at the moment, you can get a set of data for relatively simple requests.
- **Automatically generated schema** — the schema is generated for stored classes accessible to the client, but not for pre-defined resolvers.
- **A fully functional parser** – the parser is fully implemented, you can get a tree by making a request of any complexity.
→ [Link to the project repository](https://github.com/intersystems-community/GraphQL)
→ [Link to the demo server](http://37.139.6.217:57773/graphiql/index.html)
Demo server doesn't work Thank you, I fixed this issue!
Announcement
Anastasia Dyubaylo · Jul 3, 2023
Hi Community,
It's voting time! Cast your votes for the best applications in our InterSystems Grand Prix Contest 2023:
🔥 VOTE FOR THE BEST APPS 🔥
How to vote? Details below.
Experts nomination:
InterSystems experienced jury will choose the best apps to nominate the prizes in the Experts Nomination. Please welcome our experts:
⭐️ @Alexander.Koblov, Support Specialist⭐️ @Guillaume.Rongier7183, Sales Engineer⭐️ @Eduard.Lebedyuk, Senior Cloud Engineer⭐️ @Steve.Pisani, Senior Solution Architect⭐️ @Timothy.Leavitt, Development Manager⭐️ @Evgeny.Shvarov, Developer Ecosystem Manager⭐️ @Dean.Andrews2971, Head of Developer Relations⭐️ @Alexander.Woodhead, Senior Systems Developer⭐️ @Andreas.Dieckow , Principal Product Manager⭐️ @Aya.Heshmat, Product Specialist⭐️ @Benjamin.DeBoe, Product Manager⭐️ @Robert.Kuszewski, Product Manager⭐️ @Carmen.Logue , Product Manager⭐️ @Jeffrey.Fried, Director of Product Management⭐️ @Luca.Ravazzolo, Product Manager⭐️ @Raj.Singh5479, Product Manager⭐️ @Patrick.Jamieson3621, Product Manager⭐️ @Stefan.Wittmann, Product Manager⭐️ @Steven.LeBlanc, Product Specialist⭐️ @Thomas.Dyar, Product Specialist⭐️ @Daniel.Franco, Interoperability Product Management
Community nomination:
For each user, a higher score is selected from two categories below:
Conditions
Place
1st
2nd
3rd
If you have an article posted on DC and an app uploaded to Open Exchange (OEX)
9
6
3
If you have at least 1 article posted on DC or 1 app uploaded to OEX
6
4
2
If you make any valid contribution to DC (posted a comment/question, etc.)
3
2
1
Level
Place
1st
2nd
3rd
VIP Global Masters level or ISC Product Managers
15
10
5
Ambassador GM level
12
8
4
Expert GM level or DC Moderators
9
6
3
Specialist GM level
6
4
2
Advocate GM level or ISC Employees
3
2
1
Blind vote!
The number of votes for each app will be hidden from everyone. Once a day we will publish the leaderboard in the comments to this post.
The order of projects on the contest page will be as follows: the earlier an application was submitted to the competition, the higher it will be on the list.
P.S. Don't forget to subscribe to this post (click on the bell icon) to be notified of new comments.
To take part in the voting, you need:
Sign in to Open Exchange – DC credentials will work.
Make any valid contribution to the Developer Community – answer or ask questions, write an article, contribute applications on Open Exchange – and you'll be able to vote. Check this post on the options to make helpful contributions to the Developer Community.
If you changed your mind, cancel the choice and give your vote to another application!
Support the application you like!
Note: contest participants are allowed to fix the bugs and make improvements to their applications during the voting week, so don't miss and subscribe to application releases! So! After the first day of the voting we have:
Community Nomination, Top 5
oex-vscode-snippets-template by @John.Murray
irisChatGPT by @Muhammad.Waseem
oex-mapping by @Robert.Cemper1003
irisapitester by @Daniel.Aguilar
DevBox by @Sean.Connelly
➡️ Voting is here.
Experts, we are waiting for your votes! 🔥
Participants, improve & promote your solutions! Devs!
Here are the results after two days of voting:
Community Nomination, Top 5
oex-vscode-snippets-template by @John Murray
oex-mapping by @Robert Cemper
irisChatGPT by @Muhammad Waseem
irisapitester by @Daniel Aguilar
iris-user-manager by @Oliver.Wilms
➡️ Voting is here.
So, the voting continues.
Please support the application you like! Hi Developers!
Here are the results at the moment:
Community Nomination, Top 5
iris-fhir-generative-ai by @José.Pereira
irisChatGPT by @Muhammad Waseem
oex-mapping by @Robert Cemper
IntegratedMLandDashboardSample by @珊珊.喻
oex-vscode-snippets-template by @John Murray
➡️ Voting is here.
Expert Nomination, Top 5
irisChatGPT by @Muhammad Waseem
iris-fhir-generative-ai by @José.Pereira
oex-vscode-snippets-template by @John Murray
ZProfile by @Dmitry.Maslennikov
DevBox by @Sean Connelly
➡️ Voting is here.
Don't forget to vote for your favorite app! Hi, Dev's!
And here're the results at the moment:
Community Nomination, Top 5
iris-fhir-generative-ai by @José Roberto Pereira
IntegratedMLandDashboardSample by @Shanshan Yu
IntegratedML-IRIS-PlatformEntryPrediction by @Zhang.Fatong
irisChatGPT by @Muhammad Waseem
oex-mapping by @Robert.Cemper1003
➡️ Voting is here.
Expert Nomination, Top 5
iris-fhir-generative-ai by @José Roberto Pereira
irisChatGPT by @Muhammad Waseem
IRIS FHIR Transcribe Summarize Export by @Ikram.Shah3431
oex-mapping by @Robert.Cemper1003
FHIR - AI and OpenAPI Chain by @Ikram.Shah3431
➡️ Voting is here. Was there a problem with the voting earlier this week? I know of two people who voted on Monday but when they re-checked yesterday or today found that their votes were no longer registered, so they had to vote again.
I think something similar happened during a previous contest. It happened to me too. My votes were deleted I had to vote again and I don't know who voted for me I have the same issue. I voted on the 03rd of July. I've read your comment and discovered that my votes have disappeared. So I voted again now.
Dear all,
We encountered an issue with the voting and are currently working to fix it. Please don't worry - all your votes will be restored and counted in the voting.
We hope for your understanding. Thank you! Developers!
Last call!Only one day left to the end of voting!
Cast your votes for applications you like! @Anastasia.Dyubaylo @Irina.Podmazko I'm unable to vote because of an issue with open exchange login. I'm able to login to community but not open exchange. I think the issue is related to my email id which is too long. I see an error like below.
length longer than MAXLEN allowed of 50\r\n > ERROR #5802: Datatype validation failed on property 'MP.Data.User:Referrer', with value equal to
Do you if there is a workaround? Is there any alternate way in which I can share my votes with you? Hi! Could you please try again? I've fixed this issue. Hi Semion, Thanks for fixing! I'm able to login now, but when I try to vote it says, I should be an active contributor to be able to vote. I've posted a few articles and comments already - https://community.intersystems.com/user/sowmiya-nagarajan. Do you know if these issues are related? What should I do to fix this? The activity status is updated every few days so that new users cannot cheating with votes during the contest. You are participant, so I can fix it for you. Done. Could you please try to vote again? Got it, Thanks Semion! I'm able to vote now.