Stargate GraphQL 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.

Prerequisites

To use Stargate you need:

  • Docker installed and running

  • cURL to run REST queries

Pull the Docker image

This image contains the Cassandra Query Language (CQL), REST and GraphQL APIs, and GraphQL Playground along with an Apache Cassandra 3.11 backend.

docker pull stargateio/stargate-3_11:v0.0.8

Start the Stargate container

Start the Stargate container in developer mode. Developer mode removes the need to set up a separate Cassandra instance and is meant for development and testing only.

docker run --name stargate \
  -p 8080:8080 \
  -p 8081:8081 \
  -p 8082:8082 \
  -p 127.0.0.1:9042:9042 \
  -d \
  -e CLUSTER_NAME=stargate \
  -e CLUSTER_VERSION=3.11 \
  -e DEVELOPER_MODE=true \
  stargateio/stargate-3_11:v0.0.8

The ports align to the following services and interfaces:

Table 1. Default Port assignments for Stargate
Port Service/Interface

Port 8080

GraphQL interface for CRUD

Port 8081

REST authorization service for generating tokens

Port 8082

REST interface for CRUD

Port 9042

CQL service

Using the Auth API to generate an auth token

In order to use the Stargate Document API, an authorization token must be generated to access the interface. A REST API token is used for this purpose.

The step below uses cURL to access the REST interface to generate the needed token.

Generate an auth token

First generate an auth token that is required in each subsequent request in the X-Cassandra-Token header. Note the port for the auth service is 8081.

curl -L -X POST 'http://localhost:8081/v1/auth' \
  -H 'Content-Type: application/json' \
  --data-raw '{
    "username": "cassandra",
    "password": "cassandra"
}'

You should receive a token in the response.

{"authToken":"{auth-token}"}

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 is to use the built-in GraphQL playground that is included in the stargate docker container. It can be accessed at http://localhost:8080/playground using a local browser after the docker container is started.

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:

  • /graphql-schema

    • An API for exploring and creating schema, or Data Definition Language (DDL). For example, Cassandra has queries to create, modify, drop keyspaces and tables, such as CREATE KEYSPACE, CREATE TABLE1, or DROP TABLE.

  • /graphql/<keyspace>

    • An API for querying and modifying your Cassandra tables using GraphQL fields.

We’ll start the playground with /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.

Creating a keyspace

Before you can start using the GraphQL API, you must first create a Cassandra keyspace and at least one table in your database. If you are connecting to a Cassandra database with existing schema, you can skip this step.

Inside the GraphQL playground, navigate to http://localhost:8080/graphql-schema and create a keyspace by executing the following mutation:

mutation createKeyspaceLibrary {
  createKeyspace(name:"library", replicas: 1)
}

For each keyspace created in your Cassandra schema, a new path is created under the graphql-path root (default is: /graphql). For example, the mutation just executed creates a path /graphql/library for the library keyspace when Cassandra creates the keyspace.

Add the auth token to the HTTP Headers box in the lower lefthand corner:

{
  "x-cassandra-token":"bff43799-4682-4375-99e8-23c8a9d0f304"
}

Notice that the key for this JSON token is different than the value that the generate token has. It is x-cassandra-token, not auth-token.

Now run the mutation to create the keyspace. You should see a return value of:

{
  "data": {
    "createKeyspace": true
  }
}

Creating a table

After the keyspace exists, you can create two tables by executing the following mutation:

mutation {
  books: createTable(
    keyspaceName:"library",
    tableName:"books",
    partitionKeys: [ # The keys required to access your data
      { name: "title", type: {basic: TEXT} }
    ]
    values: [ # The values associated with the keys
      { name: "author", type: {basic: TEXT} }
    ]
  )
  authors: createTable(
    keyspaceName:"library",
    tableName:"authors",
    partitionKeys: [
      { name: "name", type: {basic: TEXT} }
    ]
    clusteringKeys: [ # Secondary key used to access values within the partition
      { name: "title", type: {basic: TEXT}, order: "ASC" }
  	]
  )
}

returns

{
  "data": {
    "books": true,
    "authors": true
  }
}

Worth noting is that one mutation is used to create two tables. Information about partition keys and clustering keys are found in the CQL reference.

Checking that keyspaces and tables exist

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

For keyspaces and tables:

query GetKeyspace {
  keyspace(name: "library") {
      name
      dcs {
          name
          replicas
      }
      tables {
          name
          columns {
              name
              kind
              type {
                  basic
                  info {
                      name
                  }
              }
          }
      }
  }
}
query GetTables {
  keyspace(name: "library") {
      name
      tables {
          name
          columns {
              name
              kind
              type {
                  basic
                  info {
                      name
                  }
              }
          }
      }
  }
}

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 {
  books(value: BooksInput, filter: BooksFilterInput, orderBy: [BooksOrder], options: QueryOptions): BooksResult
  booksFilter(filter: BooksFilterInput!, orderBy: [BooksOrder], options: QueryOptions): BooksResult
}

type Mutation {
  insertBooks(value: BooksInput!, ifNotExists: Boolean, options: UpdateOptions): BooksMutationResult
  updateBooks(value: BooksInput!, ifExists: Boolean, ifCondition: BooksFilterInput, options: UpdateOptions): BooksMutationResult
  deleteBooks(value: BooksInput!, ifExists: Boolean, ifCondition: BooksFilterInput, options: UpdateOptions): BooksMutationResult
}

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.

Write 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:

mutation {
  moby: insertBooks(value: {title:"Moby Dick", author:"Herman Melville"}) {
    value {
      title
    }
  }
  catch22: insertBooks(value: {title:"Catch-22", author:"Joseph Heller"}) {
    value {
      title
    }
  }
}

returns

{
  "data": {
    "moby": {
      "value": {
        "title": "Moby Dick"
      }
    },
    "catch22": {
      "value": {
        "title": "Catch-22"
      }
    }
  }
}

Read 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. 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":

query oneBook {
    books (value: {title:"Moby Dick"}) {
      values {
      	title
      	author
      }
    }
}

returns

{
  "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:

mutation updateABook {
  moby: updateBooks(value: {title:"Moby Dick", author:"Herman Melville", isbn: "9780140861723"}, ifExists: true ) {
    value {
      title
      author
      isbn
    }
  }
}
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(), I can delete the book using deleteBooks() to illustrate deleting data:

mutation deleteABook {
  PaP: deleteBooks(value: {title:"Pride and Prejudice"}, ifExists: true ) {
    value {
      title
    }
  }
}

For more information on the GraphQL API, see Using the GraphQL API in the Data API section.