Stargate GraphQL CQL-first API QuickStart

Time to complete: 5 minutes

Stargate is a data gateway deployed between client applications and a database. In this QuickStart, you’ll be up and running on your local machine with the GraphQL API plugin that exposes CRUD access to data stored in Cassandra tables.

For more information about the GraphQL API, see the blog post on the GraphQL API.

Prerequisites

If you haven’t already, create a database using Astra DB.

  • Install cURL, a utility for running REST, Document, or GraphQL queries on the command line.

  • [Optional] If you prefer, you can use Postman as a client interface for exploring the APIs

    • You will also find links to downloadable collections and environments in Using Postman

  • [Optional] If you going to use the GraphQL API, you will want to use the GraphQL Playground to deploy schema and execute mutations and queries.

  • [Optional] For the REST and Document APIs, you can use the Swagger UI.

Before you get started, set your environment variables to save time developing on your database. There are four environment variables, three of which you will get from the Astra dashboard (database id, region, and keyspace), and one that you must create (token).

  1. In Astra DB, select the database to which you want to connect.

  2. In your Database Dashboard, select Connect.

  3. Select your API.

    If you have multiple regions, select the region you want to connect to from the dropdown menu for instructions.

  4. Follow the steps to get your application token and set up your environment variables. Or if you have an older Astra Classic database, follow the steps in Authentication for classic databases.

You will need to add this token to the GraphQL Playground in order to authorize your GraphQL requests. Copy the value after "authToken" to use later.

Using the GraphQL Playground

The easiest way to get started with GraphQL is to use the built-in GraphQL playground. In Astra DB, go to the Connect tab for your database, choose GraphQL under the Connect using an API and you’ll see instructions for accessing the playground. The GraphQL playground launches the url: https://$ASTRA_CLUSTER_ID-$ASTRA_REGION.apps.astra.datastax.com:8080/api/playground in your browser.

Add your application token to the HTTP HEADERS section at the lower lefthand corner of the GraphQL Playground window:

{"x-cassandra-token":"$AUTH_TOKEN"}

Once in the playground, you can create new schema and interact with the GraphQL APIs. The server paths are structured to provide access to creating and querying schema, as well as querying and modifying Cassandra data:

  • /api/graphql-schema

    • An API for exploring and creating schema, or Data Definition Language (DDL). For example, Astra DB has queries to create, modify, or drop tables, such as createTable, or dropTable.

  • /api/graphql/<keyspace>

    • An API for querying and modifying your tables using GraphQL fields. Generally, you will start the playground with /api/graphql-schema to create some schema.

Creating schema

In order to use the GraphQL API, you must create schema that defines the keyspace and tables that will store the data. A keyspace is a container for which a replication factor defines the number of data replicas the database will store. Tables consist of columns that have a defined data type. Multiple tables are contained in a keyspace, but a table cannot be contained in multiple keyspaces.

Check keyspace existence

To check if a keyspace exists, execute a GraphQL query:

  • graphQL command

  • Result

# Works in localhost:8080/graphql-schema
# for either CQL-first or schema-first
query GetKeyspace {
  keyspace(name: "library") {
      name
      dcs {
          name
          replicas
      }
      tables {
          name
          columns {
              name
              kind
              type {
                  basic
                  info {
                      name
                  }
              }
          }
      }
  }
}
{
  "data": {
    "keyspace": {
      "name": "library",
      "dcs": [],
      "tables": [
        {
          "name": "book",
          "columns": [
            {
              "name": "title",
              "kind": "PARTITION",
              "type": {
                "basic": "VARCHAR",
                "info": null
              }
            },
            {
              "name": "author",
              "kind": "CLUSTERING",
              "type": {
                "basic": "VARCHAR",
                "info": null
              }
            },
            {
              "name": "format",
              "kind": "REGULAR",
              "type": {
                "basic": "SET",
                "info": {
                  "name": null
                }
              }
            },
            {
              "name": "genre",
              "kind": "REGULAR",
              "type": {
                "basic": "SET",
                "info": {
                  "name": null
                }
              }
            },
            {
              "name": "isbn",
              "kind": "REGULAR",
              "type": {
                "basic": "VARCHAR",
                "info": null
              }
            },
            {
              "name": "language",
              "kind": "REGULAR",
              "type": {
                "basic": "VARCHAR",
                "info": null
              }
            },
            {
              "name": "pub_year",
              "kind": "REGULAR",
              "type": {
                "basic": "INT",
                "info": null
              }
            }
          ]
        },
        {
          "name": "reader",
          "columns": [
            {
              "name": "name",
              "kind": "PARTITION",
              "type": {
                "basic": "VARCHAR",
                "info": null
              }
            },
            {
              "name": "user_id",
              "kind": "CLUSTERING",
              "type": {
                "basic": "UUID",
                "info": null
              }
            },
            {
              "name": "addresses",
              "kind": "REGULAR",
              "type": {
                "basic": "LIST",
                "info": {
                  "name": null
                }
              }
            },
            {
              "name": "birthdate",
              "kind": "REGULAR",
              "type": {
                "basic": "DATE",
                "info": null
              }
            },
            {
              "name": "email",
              "kind": "REGULAR",
              "type": {
                "basic": "SET",
                "info": {
                  "name": null
                }
              }
            },
            {
              "name": "reviews",
              "kind": "REGULAR",
              "type": {
                "basic": "TUPLE",
                "info": {
                  "name": null
                }
              }
            }
          ]
        }
      }
    }
}

Create a table

After the keyspace exists, you can create a table by executing mutations. For this example, two tables are created:

  • graphQL command

  • Result

# create two tables (book, reader) in library with a single mutation
# DATA TYPES: TEXT, UUID, SET(TEXT), TUPLE(TEXT, INT, DATE), LIST(UDT)
mutation createTables {
  book: createTable(
    keyspaceName:"library",
    tableName:"book",
    partitionKeys: [ # The keys required to access your data
      { name: "title", type: {basic: TEXT} }
    ]
    clusteringKeys: [
      { name: "author", type: {basic: TEXT} }
    ]
  )
  reader: createTable(
    keyspaceName:"library",
    tableName:"reader",
    partitionKeys: [
      { name: "name", type: {basic: TEXT} }
    ]
    clusteringKeys: [ # Secondary key used to access values within the partition
      { name: "user_id", type: {basic: UUID}, order: "ASC" }
  	]
    values: [
      { name: "birthdate", type: {basic: DATE} }
      { name: "email", type: {basic: SET, info:{ subTypes: [ { basic: TEXT } ] } } }
      { name: "reviews", type: {basic: TUPLE, info: { subTypes: [ { basic: TEXT }, { basic: INT }, { basic: DATE } ] } } }
      { name: "addresses", type: { basic: LIST, info: { subTypes: [ { basic: UDT, info: { name: "address_type", frozen: true } } ] } } }
    ]
  )
}
  "data": {
    "book": true,
    "reader": true
  }
}

It is worth noting that one mutation is used to create two tables. Information about partition keys and clustering keys can be found in the CQL reference.

The second table, reader, also defines a column using a user-defined type (UDT).

One of these tables includes creating a column with the data type LIST, an ordered collection of text values.

Collection (set, list, map) columns

Including a collection in a table has a couple of extra parts:

  • graphQL command

  • Result

# create a table with a MAP
# DATA TYPE: TEXT, INT, MAP(TEXT, DATE)
# Sample: btype=Editor, badge_id=1, earned = [Gold:010120, Silver:020221]
mutation createMapTable {
  badge: createTable (
    keyspaceName:"library",
    tableName: "badge",
    partitionKeys: [
      {name: "btype", type: {basic:TEXT}}
    ]
    clusteringKeys: [
      { name: "badge_id", type: { basic: INT} }
    ],
    ifNotExists:true,
    values: [
      {name: "earned", type:{basic:LIST { basic:MAP, info:{ subTypes: [ { basic: TEXT }, {basic: DATE}]}}}}
    ]
  )
}
{
  "data": {
    "badge": true
  }
}

This example shows a map. A previous example shows a list. In the next example, a set will be defined.

Add columns to table schema

If you need to add more attributes to something you are storing in a table, you can add one or more columns:

  • graphQL command

  • Result

# alter a table and add columns
# DATA TYPES: TEXT, INT, SET(TEXT)
mutation alterTableAddCols {
  alterTableAdd(
    keyspaceName:"library",
    tableName:"book",
    toAdd:[
      { name: "isbn", type: { basic: TEXT } }
      { name: "language", type: {basic: TEXT} }
      { name: "pub_year", type: {basic: INT} }
      { name: "genre", type: {basic:SET, info:{ subTypes: [ { basic: TEXT } ] } } }
      { name: "format", type: {basic:SET, info:{ subTypes: [ { basic: TEXT } ] } } }
    ]
  )
}
{
  "data": {
    "alterTableAdd": true
  }
}

Check table and column existence

To check if a table or particular table columns exist, execute a GraphQL query:

  • graphQL command

  • Result

query GetTables {
  keyspace(name: "library") {
      name
      tables {
          name
          columns {
              name
              kind
              type {
                  basic
                  info {
                      name
                  }
              }
          }
      }
  }
}
{
  "data": {
    "keyspace": {
      "name": "library",
      "tables": [
        {
          "name": "reader",
          "columns": [
            {
              "name": "name",
              "kind": "PARTITION",
              "type": {
                "basic": "VARCHAR",
                "info": null
              }
            },
          ]
        },
        {
          "name": "book",
          "columns": [
            {
              "name": "title",
              "kind": "PARTITION",
              "type": {
                "basic": "VARCHAR",
                "info": null
              }
            },
            {
              "name": "author",
              "kind": "REGULAR",
              "type": {
                "basic": "VARCHAR",
                "info": null
              }
            },
            {
              "name": "isbn",
              "kind": "REGULAR",
              "type": {
                "basic": "VARCHAR",
                "info": null
              }
            }
          ]
        }
      ]
    }
  }
}

Because these queries are named, the GraphQL playground will allow you to select which query to run. The first query will return information about the keyspace library and the tables within it. The second query will return just information about the tables in that keyspace.

Interacting with tables

API generation

Once schema is created, the GraphQL API generates mutations and queries can be used. In the GraphQL playground, expand the tabs on the righthand side labelled "DOCS" or "SCHEMA", to discover the items available and the syntax to use.

For each table in the Cassandra schema that we just created, several GraphQL fields are created for handling queries and mutations. For example, the GraphQL API generated for the books table is:

schema {
  query: Query
  mutation: Mutation
}

type Query {
  book(value: bookInput, filter: bookFilterInput, orderBy: [bookOrder], options: QueryOptions): bookResult
  bookFilter(filter: bookFilterInput!, orderBy: [bookOrder], options: QueryOptions): bookResult
}

type Mutation {
  insertbook(value: bookInput!, ifNotExists: Boolean, options: UpdateOptions): bookMutationResult
  updatebook(value: bookInput!, ifExists: Boolean, ifCondition: bookFilterInput, options: UpdateOptions): bookMutationResult
  deletebook(value: bookInput!, ifExists: Boolean, ifCondition: bookFilterInput, options: UpdateOptions): bookMutationResult
}

The query books() can query book values by equality. If no value argument is provided, then the first hundred (default pagesize) values are returned.

Several mutations are created that you can use to insert, update, or delete books. Some important facts about these mutations are:

  • insertBooks() is an upsert operation if a book with the same information exist, unless the ifNotExists is set to true.

  • updateBooks() is also an upsert operation, and will create a new book if it doesn’t exist, unless ifNotExists is set to true.

  • Using the ifNotExists or ifCondition options affects the performance of operations because of the compare-and-set execution path in Cassandra. Under the hood these operations are using a feature in Cassandra called lightweight transactions (LWTs).

As more tables are added to a keyspace, additional GraphQL fields will add query and mutation types that can be used to interact with the table data.

Insert data

Any of the created APIs can be used to interact with the GraphQL data, to write or read data.

First, let’s navigate to your new keyspace library inside the playground. Change the location to http://localhost:8080/graphql/library and add a couple of books to the book table:

  • graphQL command

  • Result

# insert 2 books in one mutation
mutation insert2Books {
  moby: insertbook(value: {title:"Moby Dick", author:"Herman Melville"}) {
    value {
      title
    }
  }
  catch22: insertbook(value: {title:"Catch-22", author:"Joseph Heller"}) {
    value {
      title
    }
  }
}
{
  "data": {
    "moby": {
      "value": {
        "title": "Moby Dick"
      }
    },
    "catch22": {
      "value": {
        "title": "Catch-22"
      }
    }
  }
}

Note that the keyword value is used twice in the mutation. The first use defines the value that the record is set to, for instance, the title to Moby Dick and the author to Herman Melville. The second use defines the values that will be displayed after the success of the mutation, so that proper insertion can be verified. This same method is valid for updates and read queries.

Retrieve data

Let’s check that the data was inserted.

Now let’s search for a particular record using a WHERE clause. The primary key of the table can be used in the WHERE clause, but non-primary key columns cannot be used unless indexed. The following query, looking at the location http://localhost:8080/graphql/library will get both the title and the author for the specified book WHERE title:"Moby Dick":

  • graphQL command

  • Result

# get one book using the primary key title with a value
query oneBook {
    book (value: {title:"Moby Dick"}) {
      values {
      	title
      	author
      }
    }
}
{
  "data": {
    "books": {
      "values": [
        {
          "title": "Moby Dick",
          "author": "Herman Melville"
        }
      ]
    }
  }
}

Update data

Using the column that we added earlier, the data for a book is updated with the ISBN value:

  • graphQL command

  • Result

mutation updateOneBook {
  moby: updatebook(value: {title:"Moby Dick", author:"Herman Melville", isbn: "9780140861723"}, ifExists: true ) {
    value {
      title
      author
      isbn
    }
  }
}
{
  "data": {
    "moby": {
      "value": {
        "title": "Moby Dick",
        "author": "Herman Melville",
        "isbn": "9780140861723"
      }
    }
  }
}

Updates are upserts. If the row doesn’t exist, it will be created. If it does exist, it will be updated with the new row data.

Delete data

After adding the book "Pride and Prejudice" with an insertBooks(), you can delete the book using deleteBooks() to illustrate deleting data:

  • graphQL command

  • Result

mutation deleteOneBook {
  PaP: deletebook(value: {title:"Pride and Prejudice", author: "Jane Austen"}, ifExists: true ) {
    value {
      title
    }
  }
}
{
  "data": {
    "PaP": {
      "value": {
        "title": "Pride and Prejudice"
      }
    }
  }
}

Note the use of ifExists to validate that the book exists before deleting it.

For more information on the GraphQL API (CQL-first), see Using the GraphQL API (CQL-first) in the Developing with Stargate APIs section.