Skip to content
Home » News » 2022 News » A GraphQL API in DataFlex

A GraphQL API in DataFlex

  • by

It’s that time of year when Unicorn try to think of something that we can share with the DataFlex community. We’ve not seen any talk of GraphQL API on the Data Access forums (yet), so thought we’d investigate it. Below, are Mike’s notes on his initial investigations…

GraphQL logo

So, I decided to take a quick look at GraphQL APIs and immediately started coding something (my default response when I have no clue what I’m doing; don’t laugh, it sometimes works! 😉).

That quick look had told me that pretty much everything in GraphQL was a POST request (technically GET can be used, but it doesn’t make much sense, so I’m going to ignore that for now1).  Simples!  So I created a WebApp and dropped a cWebHttpHandler into it, set its psPath to “API” and its psVerbs to “POST”.  Then I started working on dealing with the input in Procedure OnHttpRequest, using Postman and just pasting sample requests to my URL (http://localhost/GraphQLTest/API) into it, to give me something to chew on.

It didn’t take very long for the light to dawn: this stuff wasn’t even JSON!  Sure the responses were JSON, but the requests were not.  I’d have to work out how to parse those requests as a first thing.  Here is a simple (ever a trap for the unwary, that word) sample request (taken from the spec here):

{
    user(id: 4) {
        name
    }
}

(See?  It kinda looks like JSON at a casual glance – all those curly braces – but nothing is double-quoted, so it isn’t!)

Which is supposed to produce the resulting JSON response:

{
    "user": {
        "name": "Mark Zuckerberg"
    }
}

(GraphQL was born at Facebook in 2012, before being open-sourced to the GraphQL Foundation in 2018, so a lot of the examples are a bit… Facebookish.)

How hard could it be?  There are only three request types:

  • query – the default; if the type of the request is omitted – plus some other caveats – it is a query, as in the above example
  • mutation – everything else in the CRUD lexicon, so Create, Update or Delete
  • subscription – not at all sure how to deal with this one… a problem for later.

Oh yes, and there is also a requirement to be able to return a schema definition.  GraphQL has its own Schema Definition Language (SDL) as a part of its specification.  But never mind about that right now, let’s just get something working!

So, taking a top-down approach… Yup, I could easily identify what kind the request was (even if I could only vaguely see how I might approach satisfying two out of the three request types at this point).  But then it got more complicated.  Quite a bit more complicated.  Clearly I was going to have to write something to parse those requests in a more generic fashion.  Perhaps a bottom-up approach was called for.

Several hours of Googling around and finding stuff and I was hip-deep in “How-To” articles about Lexers, Parsers, ASTs (Abstract Syntax Trees, forsooth!), Compilers, defining whitespace, comments, punctuators and what-not – all the stuff you need to know if you are going to write your own programming language (and I’ve got 18 tabs on the subject open in my browser!).

Now wait just a cotton-picking minute here!  This is the stuff they teach in Computer Science courses… my degree is in History: Medieval History, specialising in the 11th-13th centuries with the Byzantine Empire, the Crusades and the Papacy being my main topics, to be specific.  I’m way out of my depth with this! The Decretals of the Fourth Lateran Council were a walk in the park by comparison.

OK, back to top-down!  The way to eat an elephant is one byte at a time.  Maybe we’ll get there in the end!

Top-down is a less flexible and less easily extensible approach, but it has the virtue that you can just incrementally solve problems on a case-by-case basis.  After a fair bit of work I had a solution that would handle simple cases of GraphQL queries.  No mutations yet, and certainly not subscriptions, but a start.

So, working with The Sacred Texts:

query getAllCustomers {    # This is a comment - ignore
    allCustomers {    # This is a second comment
        customer_number
        name    # Yada, yada
        city
        blurp    # This is a duff field
        zip
        state
      }    # And another comment...
}    # Yet another comment

Returns the kind of JSON you’d expect:

{
   "data": [
     {
       "customer_number": 1,
       "name": "Access Miles",
       "city": "Miami",
       "zip": "331766017",
       "state": "FL"
     },
     {
       "customer_number": 2,
       "name": "American Products, Inc.",
       "city": "Redmond",
       "zip": "980522350",
       "state": "WA"
     },  ... etc.

Or trying something similar to the specification example above (and here I’ve used the WebOrder System’s “WebAppUser” table for “Users”, with “id” being interpreted as Recnum and requesting “fullname” instead of “name”, so the answer probably isn’t going to be “Mark Zuckerberg” 😉)

{
    user(id: 4) {  # I've added myself to the WebAppUsers table
        fullname
    }
}

Returns:

{
    "user": {
        "fullname": "Mike Peat"
    }
}

 And we can add to the set of requested columns:

{
    {
        user(id: 4) {  # I've added myself to the WebAppUsers table
            loginName
            fullname
            password
            rights
            lastLogin
    }
}

 Which gets us:

{
    "user": {
         "loginName": "Mike",
         "fullname": "Mike Peat",
         "password": "foo",
         "rights": 1,
         "lastLogin": "14/12/2022"
    }
}

 (Probably shouldn’t have included “password” in the query!  Oh well!  😲)

Watch this space… maybe!

1 Once I have POST working it shouldn’t be too hard to add the GET functionality – an issue for another day.

PS: Turns out I was right. Implementing GET was only a few extra lines of code. Now GET http://localhost/GraphQLTest/API?query={user(id:4){loginname fullname password rights lastLogin}} returns the same JSON as the final example above. Which has the advantage that you can access it in a web browser, rather than using Postman (queries only though – GET isn’t supposed to handle mutations or subscriptions).