Leveraging the Power of GraphQL with Nautobot

By now, almost every device or system has a REST API. REST APIs are well understood and streamline getting data into and out of systems programmatically. Since REST APIs are resource-specific, there is a different API for every resource. Because of this paradigm, users tend not to understand the relationships between the data resources. And in order to get all of the data needed to perform an operation, users may need to make numerous API calls, such as fetching a resource, getting an index of the related resource, fetching that related resource and the index of yet another related object, and the process continues.

This post introduces GraphQL, a flexible API query language that streamlines working with relational data programmatically. You’ll get to see firsthand the value of GraphQL as we explore Nautobot’s GraphQL implementation. Having GraphQL as part of Nautobot makes it much simpler to gather data from your Network Source of Truth!

This is the first in a series of posts that will discuss:

  • Using Nautobot’s GraphiQL interface to construct and practice GraphQL queries (this post)
  • Using Postman to construct programmatic GraphQL queries
  • Leveraging queries constructed in GraphiQL
  • Using Python to leverage programmatic GraphQL queries via
    • Programmatic GraphQL queries constructed with Postman
    • The pynautobot python library
  • Empirically demonstrating how GraphQL saves time and effort over traditional REST queries

A Quick Primer: User Interfaces, Data, and Data Context

A good user interface (UI) will present information in a way the user can understand. It does so by clearly presenting requested data points in a way easy for the user to understand, providing context for a data point, and allowing the user to quickly access related data points.

GraphQL is for programmatic data what a good UI is for a person

Take IP addresses as an example: a workflow may require retrieval of a single IP address for a given interface. Nautobot’s Web UI presents an interface’s IP address along with contextual information, including the remotely connected device and interface, as in the screenshot below:

Not only does this view provide info on the objects related to the Interface and IP address, it also provides links to the related objects. This contextual data in the UI helps people understand the larger relationship for the IP address and easily access related information.

The next section will explore how GraphQL provides the same clear presentation of requested data and contextual information, but in a programmatic format.

What Is GraphQL?

GraphQL is a query language for APIs. It allows a user to programmatically request and receive data from a server. One of GraphQL’s defining features is the capability to allow the user to specify the exact data needed, even if the data components are in different data silos. And, unlike REST API queries, a GraphQL query will return only the specific, requested data, even if the requested data spans multiple resources.

GraphQL benefits summary:

  • Returns a data set with only the requested data; it does not require any filtering or post-processing
  • Easily returns a data set that shows the context and relationships around a given data point

A single GraphQL query returns concise data would otherwise require multiple REST queries and extensive filtering

For example, imagine you wanted to retrieve the IP address for an interface. It may follow that your process will also need other information related to the interface IP address: interface name, remote interface name, remote IP address, and remote device. Using GraphQL, you can easily specify the exact data you need, including the required related data. This post will demonstrate how to get started with GraphQL queries via Nautobot’s GraphiQL interface.

Nautobot’s GraphiQL Browser

Nautobot contains a GraphiQL interface. GraphiQL (notice the included i) is an interactive browser environment that allows users to easily craft GraphQL queries and explore the returned data.

The exercises in this article use the public Nautobot sandbox available at https://demo.nautobot.com. We encourage you to follow along as we explore Nautobot’s GraphiQL interface.

Accessing Nautobot’s GraphiQL Interface

From the Nautobot Web UI homepage, scroll down and look to the lower right corner for a multi-choice menu. On that multi-choice menu, click on the GraphQL icon.

You’ve arrived at the GraphiQL Interface:

The GraphiQL interface for the public demo is https://demo.nautobot.com/graphql/.

Now that we’ve reached the GraphiQL UI, let’s explore how to leverage it.

GraphiQL’s Interactive UI

Autocompletion

One very interactive feature in GraphiQL is autocompletion. When the user constructs a query, the UI provides allowed completions for the level the user is typing on. This is possible because the system understands the GraphQL schema and relationships. In the example below, the user is typing within the query stanza, and the UI is showing allowed autocompletions that include the text the user has already typed:

Interactive Data Schema Model in the UI

Nautobot’s GraphiQL interactive environment enables the user to quickly create queries via access to the relevant data model in the UI. In GraphiQL, notice the <Docs button on the upper-right side. Clicking on this opens an interactive Documentation Explorer menu.

In the screenshot below, to see what options are available for query in devices, type out (1) devices, then (2) click on the [DeviceType] link (you may have to hover the cursor over devices for a moment to see the Query:devices:[DeviceType] link appear). This opens the DeviceType Explorer. In this example, the user explores the schema to find that (3) name is an allowed field to query within the devices stanza.

Quick access to Nautobot’s data models in real time gives the user the power to quickly create an effective query.

GraphQL Query Examples

The following are all examples from Network to Code’s public Nautobot demo instance, which has a robust set of data to demo and practice against. We encourage you to follow along at https://demo.nautobot.com.

Example 1: Get All Device Names

This first query will return all device names and only the device names: the data returned will not require any filtering. Start typing in the query on the left hand side. In the example below, Nautobot is showing a list of valid attributes that contain the letter ‘n’ for a device object. Once you’ve complete the query (1), press the play button (2) to execute and see the data (3):

Take specific notice that only the requested data about the devices (each device’s name) was returned. GraphQL returns a data set that does not require filtering out extraneous information. A subsequent post in this series will explore that capability further.

NOTE: Complete text examples for this and the remaining queries are at the end of this post

Example 2: Query Devices and Include Site Names

The next query returns the site name along with each device name. Notice the site attribute nests within the device object, in accordance with the Device data model.

Here is the full query and some of the returned data:

Example 3: Narrow the Scope Using Query Parameters

In the next scenario, the user wants to retrieve a list of device names, but only for devices in the ams site. To narrow the returned data to a specific site, use a query parameter. In this case we will query for devices in the ams site using the (site: “ams”)query parameter. Since the query is explicitly narrowing the data to the ams site, returning the device’s site name is no longer necessary.

This is a fairly simple query with the expected results. Notice that GraphQL returned only the requested data, saving us the time of filtering out unneeded data.

The next example will be a bit more sophisticated and start to show how GraphQL can return data that would otherwise require multiple REST queries.

Example 4: A More Sophisticated Query with Amazing Results

GraphQL can return in one request data that would otherwise require multiple API requests. This next example shows a user requesting a list of interface names for each device in the ams site and the IP address on each interface:

See the Text Query Examples and Results at the end of this post for a more complete example of the returned GraphQL data.

This GraphQL query returns a data structure that otherwise would require 12 REST queries and extensive post processing

This example humbly executes and demonstrates a powerful GraphQL capability in Nautobot. Namely:

  • GraphQL saves the user from having to execute 12 REST API calls
    • https://demo.nautobot.com/api/dcim/devices?site=ams
      • Returns a list of devices in ams
      • NOTE – There are 10 devices in ams site
    • https://demo.nautobot.com/api/dcim/interfaces?site=ams
      • Returns data for all interfaces in ams site
    • https://demo.nautobot.com/api/ipam/ip-addresses?device=ams-edge-01
      • Contains IP address information for a device
      • Run once for each of the ten devices in ams
  • With GraphQL, the user does not have to process the results for each of the REST API calls above and construct a data structure to hold the information
  • GraphQL saves the user the time needed to scour the API docs looking for the endpoint that would most efficiently return the data
    • In the example above, the user may have to spend time looking for the https://demo.nautobot.com/api/dcim/interfaces?site=ams API, which returns data for all interfaces and allows site to be specified as a query parameter
  • GraphQL returns only the data the user asks for, which limits or likely eliminates the effort to post-process the data

A follow-on post in this series will explore in-depth those required REST calls and the subsequent Python data processing that would produce results equivalent to this single GraphQL query.

Example 5: Easily Include Additional Relevant Data

To finish out our exploration of the ams site, the user also needs the interface’s remote device and connected interface:

Leveraging the Autocomplete Feature

When constructing sophisticated queries like this one, it is very helpful to leverage the autocomplete feature in the query. This feature will show you if you are in the correct position in the data model to get the data point you are looking for (connected_interface autocompletes in the interfaces stanza) . . .

. . . or not! Notice that connected_interface is not an option and will not autocomplete in the devices stanza:

Example 6: A GraphQL Example of the Prior Web UI Case

Earlier, this article showed Nautobot’s Web UI displaying the IP address (10.0.192.0/32) for Interface Ethernet1/1 on atl-edge-01. That same page also showed the connected Interface (Ethernet1/1) on the connected device (atl-edge-02).

GraphQL’s ease of querying and concise data retrieval allow both automation and people to efficiently retrieve and process data

The GraphQL equivalent query and results in GraphiQL are:

Spotlight on Query Parameters

This last example showcases two query parameters that qualify the interfaces query:

  • device:“atl-edge-01”
    • Returns interface data for only the specified device
  • The name__ie lookup expression
    • This specific expression allows for a case-insensitive exact match on the interface name (case insenstive, exact match)
    • Depending on your use case, this query could have also used name:"Ethernet1/1 instead, but would require exact capitalization matching
    • A full explanation of Nautobot’s available lookup expressions is available at https://nautobot.readthedocs.io/en/latest/rest-api/filtering/#lookup-expressions

Final Thoughts and a Look Ahead

Most optimizations have trade-offs, and GraphQL is no exception. By returning only the requested data that may span multiple resources, GraphQL offloads much of the data processing from the user onto the server. Depending on your situation and use case, this may or may not be the desired behavior.

In terms of performance, GraphQL is best suited for

  • Lots of data for a single device
  • A few bits of data for hundreds of devices

In general, it is best to use multiple GraphQL queries or multiple individual REST API queries for

  • Requesting data about thousands of devices

The next post in this series will go on to demonstrate how to retrieve GraphQL data programmatically in Postman and in Python. Another post will use the fourth example in this post as a case study to examine the required REST API calls and processing the user would have to perform if using REST API queries instead of GraphQL.

-Tim


Text Query Examples and Results

The text of the GraphQL queries and the results for each are below. The GraphQL queries can be pasted into a GraphiQL query or used in the body of an API call.

Example 1: Get All Device Names

Query Nautobot for all device names

query {
  devices {
    name
  }
}

Result (abbreviated for brevity)

{
  "data": {
    "devices": [
      {
        "name": "ams-edge-01"
      },
      {
        "name": "ams-edge-02"
      },
      {
        "name": "ams-leaf-01"
      },


      {
        "name": "sin-leaf-07"
      },
      {
        "name": "sin-leaf-08"
      }
    ]
  }
}

Example 2: Query Devices with Site Names

query {
  devices {
    name
	site {
      name
    }
  }
}

Here is the returned data, snipped for brevity:

{
  "data": {
    "devices": [
      {
        "name": "ams-edge-01",
        "site": {
          "name": "ams"
        }
      },
      {
        "name": "ams-edge-02",
        "site": {
          "name": "ams"
        }
      },
    ...
    ...
      {
        "name": "sin-leaf-07",
        "site": {
          "name": "sin"
        }
      },
      {
        "name": "sin-leaf-08",
        "site": {
          "name": "sin"
        }
      }
    ]
  }
}

Example 3: Narrow the Scope Using Query Parameters

Query Nautobot for the names of all the devices in the ams site:

query {
 devices(site:"ams") {
   name
 }
}

Here is the sample output, snipped for brevity:

{
 "data": {
   "devices": [
     {
       "name": "ams-edge-01"
     },
     {
       "name": "ams-edge-02"
     },
     {
       "name": "ams-leaf-01"
     },
   ...
   ...
     {
       "name": "ams-leaf-07"
     },
     {
       "name": "ams-leaf-08"
     }
   ]
 }
}

Example 4: A More Sophisticated Query with Amazing Results

query {
 devices(site:"ams") {
   name
   interfaces {
     name
     ip_addresses {
       address
     }
   }
 }
}

Abbreviated results:

{
 "data": {
   "devices": [
     {
       "name": "ams-edge-01",
       "interfaces": [
         {
           "name": "Ethernet1/1",
           "ip_addresses": [
             {
               "address": "10.11.192.0/32"
             }
           ]
         },
         {
           "name": "Ethernet2/1",
           "ip_addresses": [
             {
               "address": "10.11.192.2/32"
             }
           ]
         },
   ...
   ...
         {
       "name": "ams-leaf-08",
       "interfaces": [
         {
           "name": "Ethernet1",
           "ip_addresses": [
             {
               "address": "10.11.192.33/32"
             }
           ]
         },
         ...
         ...
           {
           "name": "vlan99",
           "ip_addresses": [
             {
               "address": "10.11.71.0/32"
             }
           ]
         },
         {
           "name": "vlan1000",
           "ip_addresses": [
             {
               "address": "10.11.7.0/32"
             }
           ]
         }
       ]
     }
   ]
 }
}       

Example 5: Easily Include Additional Relevant Data

query {
 devices(site:"ams") {
   name
   interfaces {
     name
     ip_addresses {
       address
     }
     connected_interface {
       device {
         name
       }
       name
     }
   }
 }
}

Abbreviated results:

{
 "data": {
   "devices": [
     {
       "name": "ams-edge-01",
       "interfaces": [
         {
           "name": "Ethernet1/1",
           "ip_addresses": [
             {
               "address": "10.11.192.0/32"
             }
           ],
           "connected_interface": {
             "device": {
               "name": "ams-edge-02"
             },
             "name": "Ethernet1/1"
           }
         },
         {
           "name": "Ethernet2/1",
           "ip_addresses": [
             {
               "address": "10.11.192.2/32"
             }
           ],
           "connected_interface": {
             "device": {
               "name": "ams-edge-02"
             },
             "name": "Ethernet2/1"
           }
         },
       ...
       ...
         {
           "name": "Management1",
           "ip_addresses": [],
           "connected_interface": null
         }
       ]
     },
   ...
   ...
     {
       "name": "ams-leaf-08",
       "interfaces": [
         {
           "name": "Ethernet1",
           "ip_addresses": [
             {
               "address": "10.11.192.33/32"
             }
           ],
           "connected_interface": {
             "device": {
               "name": "ams-edge-01"
             },
             "name": "Ethernet10/1"
           }
         },
         {
           "name": "Ethernet2",
           "ip_addresses": [
             {
               "address": "10.11.192.35/32"
             }
           ],
           "connected_interface": {
             "device": {
               "name": "ams-edge-02"
             },
             "name": "Ethernet10/1"
           }
         },
       ...
       ...
         {
           "name": "vlan99",
           "ip_addresses": [
             {
               "address": "10.11.71.0/32"
             }
           ],
           "connected_interface": null
         },
         {
           "name": "vlan1000",
           "ip_addresses": [
             {
               "address": "10.11.7.0/32"
             }
           ],
           "connected_interface": null
         }
       ]
     }
   ]
 }
}

Example 6: A GraphQL Example of the Prior Web UI Case

query {
  interfaces(device:"atl-edge-01", name__ie:"Ethernet1/1") {
     name
   ip_addresses {
     address
   }
   connected_interface {
     name
     device {
       name
     }
   }
 }
}

Result:

{
 "data": {
   "interfaces": [
     {
       "name": "Ethernet1/1",
       "ip_addresses": [
         {
           "address": "10.0.192.0/32"
         }
       ],
       "connected_interface": {
         "name": "Ethernet1/1",
         "device": {
           "name": "atl-edge-02"
         }
       }
     }
   ]
 }
}


ntc img
ntc img

Contact Us to Learn More

Share details about yourself & someone from our team will reach out to you ASAP!

Author