Here is a quick overview of GraphQL API essentials that will help you get going with the QuickBooks Online GraphQL API.
Let’s start with an over-simplified example of a GraphQL request and response:
1 2 3 4 5 6 | POST https://api.intuit.com/graphql { company { id } } |
1 2 3 4 5 6 7 8 | HTTP/1.1 200 OK { "data": { "company": { "id": "djQuMToxMjMxNDY3MTI2OTQ4OTk6Mjk2MDU3NjVkNw:123146712694899" } } } |
As shown in this simple example, all GraphQL requests are served from a single endpoint. No matter what is requested, from transactions to contacts, the endpoint will be the same.
The request body contains the list of operations to execute, which in this case is using the default query operation to request fields from the company object, specifically the id field. Everything powerful about GraphQL, from variables to fragments to aliases, are ultimately expressed in the request body for the server to execute. We will go over operations later.
The response body contains a JSON object with the exact fields that were requested. This gives clients the predictability to know the structure of the response ahead of time, and parse the data accordingly. This is also helpful for concepts like versioning, since new fields can be introduced without breaking existing clients.
It’s important to note that GraphQL expresses errors in the response body, rather than just the HTTP STATUS. As such, clients need to parse the response object to check for error objects, especially since the request can be partially fulfilled. Certain errors, such as authorization or server errors, may be returned by the server in the HTTP STATUS, so it is vital to check both in your application’s code.
Learn more at Serving over HTTP.
Since we know GraphQL is all about the way data is requested by the client, let’s take a closer look at the structure of a generic GraphQL operation:
1 2 3 | <operation> <name> (<variables>) { fields } |
The operation specifies the action to be performed against the server, which is a query, mutation or subscription. The default operation is query, which allows us to omit it from in the company id example above. In a general sense, queries retrieve information from the server without altering it, and mutations make changes to server data by altering it, such as creating or updating data.
While the operation name is optional, clients may find it useful to specify a name for each operation, as it can help code readability and logging. For example, the company query above could be named as ReadCompanyId, while also specifying the query operation explicitly:
1 2 3 4 5 | query ReadCompanyId { company { id } } |
The operation variables are also optional, depending on the request, and are similar to function parameters. Operation variables are covered in their own section below, so we’ll skip them here, but they are required for most mutations, and are very helpful for application code reuse.
The operation fields specify the actions to perform against the server. The term fields is used generically in GraphQL, as fields can perform different server actions, such as:
Reusing the ReadCompanyId
example, this query operation specifies id.
1 2 3 4 5 | query ReadCompanyId { company { id } } |
Another use of fields would be to alter data on the server, using fields that take input, such as the createTransactions_Transaction
field.
We will cover more about its link to mutations later.
GraphQL has very powerful argument handling when working with fields, allowing clients to fetch exactly the data that they need. Here’s an example of filterBy argument passed to the transactions field, allowing the client to read only Bill transactions:
1 2 3 4 5 6 7 8 9 10 11 | query BillsRead { company { transactions(filterBy:"type='PURCHASE_BILL'") { edges { node { id } } } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | { "data": { "company": { "transactions": { "edges": [ { "node": { "id": "djQuMToxMjMxNDY3MTI2OTQ4OTk6ODAyNzFlZGQ4YQ:11" } } ] } } } } |
Arguments are supported on every field in GraphQL, giving clients the ability to filter data to cater to their exact specifications. Learn more here.
Variables in GraphQL allow clients to separate dynamic input from otherwise static operations. For instance, taking the same example above to read bill transactions with a filterBy argument, we can rewrite the same request using variables. In this case, we will name our variable type, with its type specified as String.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | query BillsRead($type:String) { company { transactions(filterBy:$type) { edges { node { id } } } } } { "type": "type='PURCHASE_BILL'" } |
Allowing variables to be separate from the otherwise static operation, allows clients the ability to reuse code, storing the generic operation and any specific workflow variables separately. For instance, the same GraphQL request above could be used to retrieve invoices by changing the type variable’s value to type='SALE_INVOICE'
.
In the raw request body, which we are seeing for the first time here, the variables section of the request is separate from the operation. See raw request section below for more information on structure and encoding.
1 | {"query":"query BillsRead($type:String) {\n company {\n transactions(filterBy:$type) {\n edges {\n\t\t\t\tcursor\n\t\t\t\t\n node {\n id\n }\n }\n }\n }\n}","variables":{"type":"type='PURCHASE_BILL'"},"operationName":"BillsRead"} |
Learn more here.
The bill read examples above show the concept of edges and nodes. Edges and nodes allow GraphQL clients to interact with lists served from the API, such as a list of transactions.
An edge represents a generic list, and allows for cursor-based pagination, while keeping the list separate from the list type. While fields, such as transactions, support pagination arguments, such as first, last, and offset, edges support a generic way to efficiently paginate using cursors, regardless of whether it is transactions, items, or another list type. We recommend using the pagination type appropriate for your client.
A node represents an object of the list, such as a transaction. Fields can be specified for a node, such as querying id from transaction.
While nodes and edges can be confusing at first, edges just represent generic information about lists, and a node represents information about a list item. Learn more here.
Mutations give clients the ability to alter data on the server, such as creating or updating a transaction. When building a mutation operation, you must specify a mutation field to execute, such as createTransaction_Transactions.
Mutation fields often have required input to be specified as arguments. Keeping with our example of using the createTransaction_Transactions field in a mutation, the required input to the mutation is CreateTransactions_TransactionInput!. The exclamation mark indicates that the field is required, or non-nullable.
The list of mutations, and the required input types as arguments to those mutations, can be found in the schema, or by using schema browsing tools, such as GraphQL Inquirer.
Given that we want to execute the createTransaction_Transactions mutation, and we know the required input type is CreateTransactions_TransactionInput from the schema, and we want the id, amount, and transaction date returned from the transactions node, the mutation would be:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | mutation TransactionsCreate($transactions_create: CreateTransactions_TransactionInput!) { createTransactions_Transaction(input: $transactions_create) { transactionsTransactionEdge { node { id header{ amount txnDate } } } } } { "transactions_create": { "clientMutationId": "ebfbdd0-88bc-4b39-9cd1-0751b795d9c2", "transactionsTransaction": { "type": "PURCHASE_BILL", "header": { "amount": "999.00", "txnDate": "2018-06-20", "contact": { "id": "djQuMToxMjMxNDY3MTI2OTQ4OTk6OWQ2OTllOTYwOA:1608e4bc80eb340668e2765d486d971b1" } }, "lines": { "itemLines": [{ "amount": "999.00", "description": "Hardware", "traits": { "item": { "quantity": "1", "rate": "999.00", "item": { "id": "djQuMToxMjMxNDc1NTMwNDg1OTQ6MTEyZGU3NDY5OQ:4" } } } }] } } } } |
The CreateTransactions_TransactionInput
input is passed as a variable to the TransactionsCreate mutation
, and down to the createTransactions_Transaction
field for the server to execute.
It’s important to know the structure of the input type to your mutation field by looking at the schema, and its required fields, to successfully execute a mutation.
Learn more here.
Queries against the node field, or node queries, allow clients to retrieve nodes of a specific type directly by their id, and retrieve fields of that node. For instance, to retrieve a transaction by the value of its id field, and retrieve the amount, query the node field specifying the Transactions_Transaction
as the type:
1 2 3 4 5 6 7 8 | query TransactionsReadOne { node(id: "djQuMToxMjMxNDY3MTI2OTQ4OTk6ODAyNzFlZGQ4YQ:11") { ... on Transactions_Transaction { header{ amount } } } } |
The types on which node queries can be performed can be found in the schema.
The raw request coming from your application should have the following form. Depending on which client is building and executing your requests, this may be automatically done:
1 2 3 4 5 | { "query": "...", "operationName": "...", "variables": { "myVariable": "someValue", ... } } |
Once the content has been escaped, an example request would look like:
1 | {"query":"query BillsRead($type:String) {\n company {\n transactions(filterBy:$type) {\n edges {\n\t\t\t\tcursor\n\t\t\t\t\n node {\n id\n }\n }\n }\n }\n}","variables":{"type":"type='PURCHASE_BILL'"},"operationName":"BillsRead"} |
Learn more here.
More advanced GraphQL concepts, such as aliases and fragments, can be found here.