🏫 GraphQL Crash Course

If you are not familiar with GraphQL, no worries. It's easy to learn. If you have experience working with SQL you'll feel right at home. The syntax also looks similar to a JSON document if you are used to working with responses from REST endpoints. In fact, the structure of the query is simply the format of the JSON response you'd like to receive back.

Another nice feature of GraphQL is schema introspection. That's a fancy way of saying that the fields are self documenting. This allows us to build things like the Fast-Weigh GraphQL API Explorer that provides autocompletion and field documentation that is always up to date with the latest API changes. (🍻cheers to no documentation lag!)

GraphQL Syntax & Basics

With GraphQL, you're still issuing a request to an HTTP endpoint just like you would for a REST API. The big difference is there is a single endpoint that services all requests.

For Fast-Weigh, the endpoint looks like this:

https://<your-servername-here>.fast-weigh.dev/v1/graphql

All requests to this endpoint will be POST requests with a JSON encoded query as the body of the request. (Content-Type: application/json)

Simple query

Here's a simple query that requests a few fields be returned for all records in the Customer table.

GraphQL Query
JSON Response
GraphQL Query
query customers {
Customer {
CustomerID
CustomerName
ContactName
ContactPhone
}
}
JSON Response
{
"data": {
"Customer": [
{
"CustomerID": "",
"CustomerName": "",
"ContactName": "",
"ContactPhone": ""
}
]
}
}

Let's break that syntax into it's parts

<ActionType> <QueryName> {
<ResourceName {
<ResourceField1>
<ResourceField2>
}
}
  • ActionType: GraphQL endpoints support the following "ActionTypes". Fast-Weigh only supports the "query" type at this time.

    • query: βœ… HTTP issued "get" query/response

    • subscription: ❌ a websocket based live data stream

    • mutation: ❌ HTTP issued data insert or update

  • QueryName: This is the identifier for the query. You can give it any name you'd like. In the GET CUSTOMERS example the QueryName could have been "getAllCustomers", "get-customers-list", or anything else you could dream up. I went with "customers" for brevity.

  • ResourceName: If you are familiar with SQL, you can think of this as the corresponding SQL table.

  • ResourceField: Again, if you are familiar with SQL, you can think of this as the columns within the corresponding SQL table.

The response back from this query will be a JSON response matching the format requested.

If a requested field has no value, it will be omitted from the response. This compresses the payload size and increases performance.

Joins

So that's a simple query. But you can also join other tables into the query assuming there is an association between them. The Customer entity has a relationship with the Orders entity, for example. So we could query:

GraphQL Query
JSON Response
GraphQL Query
query customers_and_orders {
Customer {
CustomerID
CustomerName
ContactName
ContactPhone
Orders {
OrderNumber
Description
PONumber
}
}
}
JSON Response
{
"data": {
"Customer": [
{
"CustomerID": "",
"CustomerName": "",
"ContactName": "",
"ContactPhone": "",
"Orders: [
{
OrderNumber: 0,
Description: "",
PONumber: ""
}
]
}
]
}
}

There is one issue with this query though. It will return a lot of data! Let's imagine the average customer has 10 orders and you've got 5,000 customers. This query would results in quite the payload to handle. More than you'd typically want to handle in a single request. So before we push run on this query lets filter it down.

Filters

Within the GraphQL syntax there are a number of query filters that can be applied:

  • _eq: equal to

  • _neq: not equal to

  • _gt: greater than

  • _gte: great than or equal to

  • _lt: less than

  • _lte: less than or equal to

  • _in: exists in array of values

  • _nin: does not exist in array of values

  • _like: matches some part of the given string

  • _nlike: does not match some part of the given string

  • _is_null: ...is null πŸ˜†

For this query, let's say we only want Customer records from the city of Knoxville. And just in case there are other Knoxville's out there, let's apply a second filter for the state of Tennessee just to be safe.

GraphQL Query
JSON Response
GraphQL Query
query customers_and_orders {
Customer(where: {City: {_eq: "Knoxville"}, State: {_eq: "TN"}}) {
CustomerID
CustomerName
ContactName
ContactPhone
Orders {
OrderNumber
Description
PONumber
}
}
}
JSON Response
{
"data": {
"Customer": [
{
"CustomerID": "",
"CustomerName": "",
"ContactName": "",
"ContactPhone": "",
"Orders: [
{
OrderNumber: 0,
Description: "",
PONumber: ""
}
]
}
]
}
}

If you compare that to our previous query, you'll see we've opened up parens on the Customer resource and within them we've defined an object called "where". Within the "where" object we can list the fields we'd like to filter on.

Our JSON response is structured the same, we've just filtered down the results to a manageable payload size.

Limits & offsets

Still more data than you'd like?

You can apply a limit to the query as well to make the payload even more manageable.

Let's see the same query, but returning a max of 10 records:

GraphQL Query
JSON Response
GraphQL Query
query customers_and_orders {
Customer(
where: {City: {_eq: "Knoxville"}, State: {_eq: "TN"}}
limit: 10
) {
CustomerID
CustomerName
ContactName
ContactPhone
Orders {
OrderNumber
Description
PONumber
}
}
}
JSON Response
{
"data": {
"Customer": [
{
"CustomerID": "",
"CustomerName": "",
"ContactName": "",
"ContactPhone": "",
"Orders: [
{
OrderNumber: 0,
Description: "",
PONumber: ""
}
]
}
]
}
}

If you want to limit each response but still cycle through to retrieve all of the filtered data, you can use an offset loop to go through the next set of 10 records.

In practice, you'd want to increment this number on each call by whatever your limit is. So the first call would be offset of 0, second call an offset of 10, then 20, 30, and so on. Once you get a payload with fewer records than your limit, you'll know there are no more records to retrieve and can stop issuing queries.

GraphQL Query
GraphQL Query
query customers_and_orders {
Customer(
where: {City: {_eq: "Knoxville"}, State: {_eq: "TN"}}
limit: 10
offset: 10
) {
CustomerID
CustomerName
ContactName
ContactPhone
Orders {
OrderNumber
Description
PONumber
}
}
}

Ordering

Building on this query even more, you can order the results returned and chain the filter, order, and limit options together.

The following sort options are accepted:

  • asc

  • asc_nulls_first

  • asc_nulls_last

  • desc

  • desc_nulls_first

  • desc_nulls_last

GraphQL Query
JSON Response
GraphQL Query
query customers_and_orders {
Customer(
where: {City: {_eq: "Knoxville"}, State: {_eq: "TN"}},
order_by: {CustomerID: asc}
limit: 10
) {
CustomerID
CustomerName
ContactName
ContactPhone
Orders {
OrderNumber
Description
PONumber
}
}
}
JSON Response
{
"data": {
"Customer": [
{
"CustomerID": "",
"CustomerName": "",
"ContactName": "",
"ContactPhone": "",
"Orders: [
{
OrderNumber: 0,
Description: "",
PONumber: ""
}
]
}
]
}
}

Again, the payload here is the same as before. But the order of the returned Customers is being sorted by the CustomerID field.

Compound queries

Completely separate requests can also be merged within a single query. This is useful if you need to bring in data from multiple tables and only want to make a single HTTP call to do so.

Typically compound queries would only be used to stitch together the responses of smaller queries. You would not want to combine multiple large payload queries in the same request/response as this can be quite taxing to fetch and parse due to query complexity and payload size.

Here's an example request for some base resources within Fast-Weigh. These lists are generally quite short and are good candidates for compound queries if applicable to your use case.

GraphQL Query
JSON Response
GraphQL Query
query sample_compound_query {
Region {
RegionName
RegionDescription
}
Salesperson {
Name
Email
}
TaxCode {
Code
MaterialPercent
FreightPercent
SurchargePercent
}
Product {
ProductID
ProductDescription
}
}
JSON Response
{
"data": {
"Region": [
{
"RegionName": "",
"RegionDescription": ""
},
],
"Salesperson": [
{
"Name": "",
"Email": ""
},
],
"TaxCode": [
{
"Code": "",
"MaterialPercent": 0.0000000,
"FreightPercent": 0.0000000,
"SurchargePercent": 0.0000000
},
],
"Product": [
{
"ProductID": "",
"ProductDescription": ""
},
]
}
}

Making HTTP Calls

Now that you've seen the basic query structure, it's time to actually send the query to the server. This is done via a typical HTTP call and can be handled in any programming language or program capable of sending HTTP requests.

All GraphQL queries are POST calls

The first thing to understand is the GraphQL query itself is sent along in the body of the request. This means every request, even simple "get" queries, are sent as a POST request.

The request body is JSON encoded

The request body itself should be JSON encoded. So the query you write will be transformed into something like this:

{
"query": "query get_customers {Customer {CustomerID CustomerName}}"
}

Unless you are programming in a very obscure language, there are probably client libraries that can abstract away a lot of this for you. These clients typically leave you with just needing to write the queries as we saw above with our GraphQL syntax examples.