

# Amazon Neptune utility for GraphQL
<a name="tools-graphql"></a>

The Amazon Neptune utility for [GraphQL](https://graphql.org/) is an open-source Node.js command-line tool that can help you create and maintain a GraphQL API for a Neptune property-graph database (it does not yet work with RDF data). It is a no-code way to create a GraphQL resolver for GraphQL queries that have a variable number of input parameters and return a variable number of nested fields.

It has been released as an open-source project located at [https://github.com/aws/amazon-neptune-for-graphql](https://github.com/aws/amazon-neptune-for-graphql).

You can install the utility using NPM like this (see [Installation and Setup](tools-graphql-setup.md) for details):

```
npm i @aws/neptune-for-graphql -g
```

The utility can discover the graph schema of an existing Neptune property graph, including nodes, edges, properties, and edge cardinality. It then generates a GraphQL schema with the directives needed to map the GraphQL types to the nodes and edges the database, and auto-generates resolver code. The resolver code is designed to minimize latency by returning only the data requested by the GraphQL query.

You can also start with an existing GraphQL schema and an empty Neptune database, and let the utility infer the directives needed to map that GraphQL schema to the nodes and edges of data to be loaded into the database. Or, you can start with a GraphQL schema and directives that you've already created or modified.

The utility is capable of creating all the AWS resources it needs for its pipeline, including the AWS AppSync API, the IAM roles, the data source, schema, and resolver, and the AWS Lambda function that queries Neptune.

**Note**  
Command-line examples here assume a Linux console. If you are using Windows, replace the backslashes ('\$1') at the end of lines with carets ('^').

**Topics**
+ [Installing and setting up the Amazon Neptune utility for GraphQL](tools-graphql-setup.md)
+ [Scanning data in an existing Neptune database](tools-graphql-scan-existing.md)
+ [Starting from a GraphQL schema with no directives](tools-graphql-start-from-schema.md)
+ [Working with directives for a GraphQL schema](tools-graphql-schema-with-directives.md)
+ [Command-line arguments for the GraphQL utility](tools-graphql-cmd-line-args.md)

# Installing and setting up the Amazon Neptune utility for GraphQL
<a name="tools-graphql-setup"></a>

If you're going to use the utility with an existing Neptune database, you need it to be able to connect to the database endpoint. By default, a Neptune database is accessible only from within the VPC where it is located.

Because the utility is a Node.js command-line tool, you must have Node.js (version 18 or above) installed for the utility to run. To install Node.js on an EC2 instance in the same VPC as your Neptune database, follow the [instructions here](https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/setting-up-node-on-ec2-instance.html). The minimum size instance to run the utility is t2.micro. During the creation of the instance select the Neptune database VPC from the **Common Security Groups** pulldown menu.

To install the utility itself on an EC2 instance or your local machine, use NPM:

```
npm i @aws/neptune-for-graphql -g
```

You can then run the utility's help command to check whether it installed properly:

```
neptune-for-graphql --help
```

You may also want to [install the AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) to manage AWS resources.

# Scanning data in an existing Neptune database
<a name="tools-graphql-scan-existing"></a>

Whether you are familiar with GraphQL or not, the command below is the fastest way to create a GraphQL API. This assumes that you have installed and configured the Neptune utility for GraphQL as described in the [installation section](tools-graphql-setup.md), so that it's connected to the endpoint of your Neptune database.

```
neptune-for-graphql \
  --input-graphdb-schema-neptune-endpoint (your neptune database endpoint):(port number) \
  --create-update-aws-pipeline \
  --create-update-aws-pipeline-name (your new GraphQL API name) \
  --output-resolver-query-https
```

The utility analyzes the database to discover the schema of the nodes, edges, and properties in it. Based on that schema, it infers a GraphQL schema with associated queries and mutations. Then it creates an AppSync GraphQL API and the required AWS resources to use it. These resources include a pair of IAM roles and a Lambda function containing the GraphQL resolver code.

When the utility has finished you'll find a new GraphQL API in the AppSync console under the name you assigned in the command. To test it, use the AppSync **Queries** option on the menu.

If you run the same command again after adding more data to the database, will update the AppSync API and Lambda code accordingly.

To release all the resources associated with the command, run:

```
neptune-for-graphql \
  --remove-aws-pipeline-name (your new GraphQL API name from above)
```

# Starting from a GraphQL schema with no directives
<a name="tools-graphql-start-from-schema"></a>

You can start from an empty Neptune database and use a GraphQL schema with no directives to create the data and query it. The command below automatically creates AWS resources to do this:

```
neptune-for-graphql \
  --input-schema-file (your GraphQL schema file)
  --create-update-aws-pipeline \
  --create-update-aws-pipeline-name (name for your new GraphQL API) \
  --create-update-aws-pipeline-neptune-endpoint (your Neptune database endpoint):(port number) \
  --output-resolver-query-https
```

The GraphQL schema file must include the GraphQL schema types, as shown in the TODO example below. The utility analyzes your schema and creates an extended version based on your types. It adds queries and mutations for the nodes stored in the graph database, and if your schema has nested types, it adds relationships between the types stored as edges in the database.

The utility creates an AppSync GraphQL API, and all the AWS resources required. These include a pair of IAM roles and a Lambda function that contains the GraphQL resolver code. When the command completes, you can find a new GraphQL API with the name you specified in the AppSync console. To test it, use **Queries** in the AppSync menu.

The example below illustrates how this works:

## Todo example, starting from a GraphQL schema with no directives
<a name="tools-graphql-todo-example"></a>

In this example we start from a Todo GraphQL schema with no directives, which you can find in the *???samples???* directory. It includes these two types:

```
type Todo {
  name: String
  description: String
  priority: Int
  status: String
  comments: [Comment]
}

type Comment {
  content: String
}
```

This command processes the Todo schema and an endpoint of an empty Neptune database to create a GraphQL API in AWS AppSync:

```
neptune-for-graphql /
  --input-schema-file ./samples/todo.schema.graphql \
  --create-update-aws-pipeline \
  --create-update-aws-pipeline-name TodoExample \
  --create-update-aws-pipeline-neptune-endpoint (empty Neptune database endpoint):(port number) \
  --output-resolver-query-https
```

The utility creates a new file in the output folder called `TodoExample.source.graphql`, and the GraphQL API in AppSync. The utility infers the following:
+ In the Todo type it added `@relationship` for a new CommentEdge type. This instructs the resolver to connect Todo to Comment using a graph database edge called CommentEdge.
+ It added a new input called TodoInput to help the queries and mutations.
+ It added two queries for each type (Todo, Comment): one to retrieve a single type using an `id` or any of the type fields listed in the input, and the other to retrieve multiple values, filtered using the input for that type.
+ It added three mutations for each type: create, update and delete. The type to delete is specified using an `id` or the input for that type. These mutations affect the data stored in the Neptune database.
+ It added two mutations for connections: connect and delete. They take as input the node ids of the from and to vertices used by Neptune and the connection are edges in the database.

The resolver recognizes the queries and mutations by their names, but you can customize them as shown [below](tools.md).

Here is the content of the `TodoExample.source.graphql` file:

```
type Todo {
  _id: ID! @id
  name: String
  description: String
  priority: Int
  status: String
  comments(filter: CommentInput, options: Options): [Comment] @relationship(type: "CommentEdge", direction: OUT)
  bestComment: Comment @relationship(type: "CommentEdge", direction: OUT)
  commentEdge: CommentEdge
}

type Comment {
  _id: ID! @id
  content: String
}

input Options {
  limit: Int
}

input TodoInput {
  _id: ID @id
  name: String
  description: String
  priority: Int
  status: String
}

type CommentEdge {
  _id: ID! @id
}

input CommentInput {
  _id: ID @id
  content: String
}

input Options {
  limit: Int
}

type Query {
  getNodeTodo(filter: TodoInput, options: Options): Todo
  getNodeTodos(filter: TodoInput): [Todo]
  getNodeComment(filter: CommentInput, options: Options): Comment
  getNodeComments(filter: CommentInput): [Comment]
}

type Mutation {
  createNodeTodo(input: TodoInput!): Todo
  updateNodeTodo(input: TodoInput!): Todo
  deleteNodeTodo(_id: ID!): Boolean
  connectNodeTodoToNodeCommentEdgeCommentEdge(from_id: ID!, to_id: ID!): CommentEdge
  deleteEdgeCommentEdgeFromTodoToComment(from_id: ID!, to_id: ID!): Boolean
  createNodeComment(input: CommentInput!): Comment
  updateNodeComment(input: CommentInput!): Comment
  deleteNodeComment(_id: ID!): Boolean
}

schema {
  query: Query
  mutation: Mutation
}
```

Now you can create and query data. Here is a snapshot of the AppSync **Queries** console used to test the new GraphQL API, named `TodoExampleAPI` in this case. In the middle window, the Explorer shows you a list of queries and mutations from which you can pick a query, the input parameters, and the return fields. This screenshot shows the the creation of a Todo node type using the `createNodeTodo` mutation:

![\[Screenshot showing the AppSync Queries console creating a Todo node\]](http://docs.aws.amazon.com/neptune/latest/userguide/images/todoCreate.png)


This screenshot shows querying all Todo nodes using the `getNodeTodos` query:

![\[Screenshot showing the AppSync Queries console querying Todo nodes\]](http://docs.aws.amazon.com/neptune/latest/userguide/images/todoGetTodos.png)


After having created a Comment using `createNodeComment`, you can use the `connectNodeTodoToNodeCommentEdgeCommentEdge` mutation to connect them by specifying their ids. Here is a nested query to retrieve Todos and their attached comments:

![\[Screenshot showing a nested query to retrieve Todos and their attached comments in the AppSync Queries console\]](http://docs.aws.amazon.com/neptune/latest/userguide/images/todoNestedQuery.png)


If you want to make changes to the `TodoExample.source.graphql` file as described in [Working with directives](tools-graphql-schema-with-directives.md), you can then use the edited schema as input and run the utility again. The utility will then modify the GraphQL API accordingly.

# Working with directives for a GraphQL schema
<a name="tools-graphql-schema-with-directives"></a>

You can start from a GraphQL schema that already has directives, using a command like the following:

```
neptune-for-graphql \
  --input-schema-file (your GraphQL schema file with directives) \
  --create-update-aws-pipeline \
  --create-update-aws-pipeline-name (name for your new GraphQL API) \
  --create-update-aws-pipeline-neptune-endpoint  (empty Neptune database endpoint):(port number) \
  --output-resolver-query-https
```

You can modify directives that the utility has created or add your own directives to a GraphQL schema. Here are some of the ways to work with directives:

## Running the utility so that it doesn't generate mutations
<a name="tools-graphql-no-mutations"></a>

To prevent the utility from generating muatations in the GraphQL API, use the the `--output-schema-no-mutations` option in the `neptune-for-graphql` command.

## The `@alias` directive
<a name="tools-graphql-alias-directive"></a>

The `@alias` directive can be applied to GraphQL schema types or fields. It maps different names between the graph database and the GraphQL schema. The syntax is:

```
@alias(property: (property name))
```

In the example below `airport` is the graph database node label mapped to the `Airport` GraphQL type, and `desc` is the the graph node property mapped to the `description` field (see the [Air Routes Example](tools-graphql.md)):

```
type Airport @alias(property: "airport") {
  city: String
  description: String @alias(property: "desc")
}
```

Note that standard GraphQL formatting calls for Pascal-casing type namess and camel-casing field names.

## The `@relationship` directive
<a name="tools-graphql-relationship-directive"></a>

The `@relationship` directive maps nested GraphQL types to graph database edges. The syntax is:

```
@relationship(edgeType: (edge name), direction: (IN or OUT))
```

Here is an example command:

```
type Airport @alias(property: "airport") {
  ...
  continentContainsIn: Continent @relationship(edgeType: "contains", direction: IN)
  countryContainsIn: Country @relationship(edgeType: "contains", direction: IN)
  airportRoutesOut(filter: AirportInput, options: Options): [Airport] @relationship(edgeType: "route", direction: OUT)
  airportRoutesIn(filter: AirportInput, options: Options): [Airport] @relationship(edgeType: "route", direction: IN)
}
```

You can find `@relationship` directives in both the [Todo example](tools-graphql-start-from-schema.md#tools-graphql-todo-example) and the [Air Routes Example](tools-graphql.md).

## The `@graphQuery` and `@cypher` directives
<a name="tools-graphql-graphquery-cypher-directives"></a>

You can define openCypher queries to resolve a field value, add queries or add mutations. For example, this adds a new `outboundRoutesCount` field to the `Airport` type to count the outboud routes:

```
type Airport @alias(property: "airport") {
  ...
  outboundRoutesCount: Int @graphQuery(statement: "MATCH (this)-[r:route]->(a) RETURN count(r)")
}
```

Here an example of new queries and mutations:

```
type Query {
  getAirportConnection(fromCode: String!, toCode: String!): Airport \
    @cypher(statement: \
      "MATCH (:airport{code: '$fromCode'})-[:route]->(this:airport)-[:route]->(:airport{code:'$toCode'})")
}

type Mutation {
  createAirport(input: AirportInput!): Airport @graphQuery(statement: "CREATE (this:airport {$input}) RETURN this")
  addRoute(fromAirportCode:String, toAirportCode:String, dist:Int): Route \
    @graphQuery(statement: \
     "MATCH (from:airport{code:'$fromAirportCode'}), (to:airport{code:'$toAirportCode'}) \
      CREATE (from)-[this:route{dist:$dist}]->(to) \
      RETURN this")
}
```

Note that if you omit the `RETURN`, the resolver assumes the keyword `this` is the returning scope.

You can also add a query or mututation using a Gremlin query:

```
type Query {
  getAirportWithGremlin(code:String): Airport \
    @graphQuery(statement: "g.V().has('airport', 'code', '$code').elementMap()")  # single node
  getAirportsWithGremlin: [Airport] \
    @graphQuery(statement: "g.V().hasLabel('airport').elementMap().fold()")       # list of nodes
  getCountriesCount: Int \
    @graphQuery(statement: "g.V().hasLabel('country').count()")                   # scalar
}
```

At this time Gremlin queries are limited to ones that return scalar values, or `elementMap()` for a single node, or `elementMap().fold()` for a list of nodes.

## The `@id` directive
<a name="tools-graphql-id-directive"></a>

The `@id` directive identifies the field mapped to the `id` graph database entity. Graph databases like Amazon Neptune always have a unique `id` for nodes and edges that is assigned during bulk imports or that is autogenerated. For example:

```
type Airport {
  _id: ID! @id
  city: String
  code: String
}
```

## Reserved type, query and mutation names
<a name="tools-graphql-reserved-names"></a>

The utility autogenerates queries and mutations to creeate a working GraphQL API. The pattern of these names is recognized by the resolver and is reserved. Here are examples for the type `Airport` and the connecting type `Route`:

The `Options` type is reserved.

```
input Options {
  limit: Int
}
```

The `filter` and `options` function parameters are reserved.

```
type Query {
  getNodeAirports(filter: AirportInput, options: Options): [Airport]
}
```

The getNode prefix of query names is reserved, and prefixes of mutations names like `createNode`, `updateNode`, `deleteNode`, `connectNode`, `deleteNode`, `updateEdge`, and `deleteEdge` are reserved.

```
type Query {
  getNodeAirport(id: ID, filter: AirportInput): Airport
  getNodeAirports(filter: AirportInput): [Airport]
}

type Mutation {
  createNodeAirport(input: AirportInput!): Airport
  updateNodeAirport(id: ID!, input: AirportInput!): Airport
  deleteNodeAirport(id: ID!): Boolean
  connectNodeAirportToNodeAirportEdgeRout(from: ID!, to: ID!, edge: RouteInput!): Route
  updateEdgeRouteFromAirportToAirport(from: ID!, to: ID!, edge: RouteInput!): Route
  deleteEdgeRouteFromAirportToAirport(from: ID!, to: ID!): Boolean
}
```

## Applying changes to the GraphQL schema
<a name="tools-graphql-apply-schema-changes"></a>

You can modify the GraphQL source schema and run the utility again, getting the latest schema from your Neptune database. Every time the utility discovers a new schema in the database, it generates a new GraphQL schema.

You can also manually edit the GraphQL source schema and run the utility again using the source schema as input instead of the Neptune database endpoint.

Finally, you can put your changes in a file using this JSON format:

```
[
  {
    "type": "(GraphQL type name)",
    "field": "(GraphQL field name)",
    "action": "(remove or add)",
    "value": "(value)"
  }
]
```

For example:

```
[
  {
    "type": "Airport",
    "field": "outboundRoutesCountAdd",
    "action": "add",
    "value":"outboundRoutesCountAdd: Int @graphQuery(statement: \"MATCH (this)-[r:route]->(a) RETURN count(r)\")"
  },
  {
    "type": "Mutation",
    "field": "deleteNodeVersion",
    "action": "remove",
    "value": ""
  },
  {
    "type": "Mutation",
    "field": "createNodeVersion",
    "action": "remove",
    "value": ""
  }
]
```

Then, as you run the utility on this file using the `--input-schema-changes-file` parameter in the command, the utility applies your changes at once.

# Command-line arguments for the GraphQL utility
<a name="tools-graphql-cmd-line-args"></a>
+ **`--help, -h`**   –   Returns help text for the GraphQL utility to the console.

   
+ **`--input-schema (schema text)`**   –   A GraphQL schema, with or without directives, to use as input.

   
+ **`--input-schema-file (file URL)`**   –   The URL of a file containing a GraphQL schema to use as input.

   
+ **`--input-schema-changes-file (file URL)`**   –   The URL of a file containing changes you want made to a GraphQL schema. If you run the utility against a Neptune database multiple times, and also manually change the GraphQL source schema, maybe adding a custom query, your manual chnages will be lost. To avoid this, put your changes in a changes file and pass it in using this argument.

  The changes file uses the following JSON format:

  ```
  [
    {
      "type": "(GraphQL type name)",
      "field": "(GraphQL field name)",
      "action": "(remove or add)",
      "value": "(value)"
    }
  ]
  ```

  See the[Todo example](tools-graphql-start-from-schema.md#tools-graphql-todo-example) for more information.

   
+ **`--input-graphdb-schema (schema text)`**   –   Instead of running the utility against a Neptune database, you can express a graphdb schema in text form to use as input. A graphdb schema has a JSON format like this:

  ```
  {
    "nodeStructures": [
      { "label":"nodelabel1",
        "properties": [
          { "name":"name1", "type":"type1" }
        ]
      },
      { "label":"nodelabel2",
        "properties": [
            { "name":"name2", "type":"type1" }
        ]
      }
    ],
    "edgeStructures": [
      {
        "label":"label1",
        "directions": [
          { "from":"nodelabel1", "to":"nodelabel2", "relationship":"ONE-ONE|ONE-MANY|MANY-MANY"  }
        ],
        "properties": [
          { "name":"name1", "type":"type1" }
        ]
      }
    ]
  }
  ```

   
+ **`--input-graphdb-schema-file (file URL)`**   –   Instead of running the utility against a Neptune database, you can save a graphdb schema in in a file to use as input. See `--input-graphdb-schema` above for an example of the JSON format for a graphdb schema file.

   
+ **`--input-graphdb-schema-neptune-endpoint (endpoint URL)`**   –   The Neptune database enpoint from which the utility should extract the graphdb schema.

   
+ **`--output-schema-file (file name)`**   –   The output file name for the GraphQL schema. If not specified the default is `output.schema.graphql`, unless a pipeline name has been set using `--create-update-aws-pipeline-name`, in which case the default file name is `(pipline name).schema.graphql`.

   
+ **`--output-source-schema-file (file name)`**   –   The output file name for the GraphQL schema with directives. If not specified the default is `output.source.schema.graphql`, unless a pipeline name has been set using `--create-update-aws-pipeline-name`, in which case the default name is `(pipeline name).source.schema.graphql`.

   
+ **`--output-schema-no-mutations`**   –   If this argument is present, the utility generates no mutations in the GraphQL API, only queries.

   
+ **`--output-neptune-schema-file (file name)`**   –   The output file name for Neptune graphdb schema that the utility discovers. If not specified the default is `output.graphdb.json`, unless a pipeline name has been set using `--create-update-aws-pipeline-name`, in which case the default file name is `(pipeline name).graphdb.json`.

   
+ **`--output-js-resolver-file (file name)`**   –   The output file name for a copy of the resolver code. If not specified the default is `output.resolver.graphql.js`, unless a pipeline name has been set using `--create-update-aws-pipeline-name`, in which case the file name is `(pipeline name).resolver.graphql.js`.

  This file is zipped in the code package uploaded to the Lambda function that runs the resolver.

   
+ **`--output-resolver-query-sdk`**   –   This argument specifies that the utility's Lambda function should query Neptune using the Neptune data SDK, which has been available starting with Neptune [engine version 1.2.1.0.R5](engine-releases-1.2.1.0.R5.md) (this is the default). However, if the utility detects an older Neptune engine version, it suggests using the HTTPS Lambda option instead, which you can invoke using the `--output-resolver-query-https` argument.

   
+ **`--output-resolver-query-https`**   –   This argument specifies that the utility's Lambda function should query Neptune using the Neptune HTTPS API.

  

   
+ **`--create-update-aws-pipeline`**   –   This argument triggers the creation of the AWS resources for the GraphQL API to use, including the AppSync GraphQL API and the Lambda that runs the resolver.

   
+ **`--create-update-aws-pipeline-name (pipeline name)`**   –   This argument sets the name for the pipeline, like the `pipeline-name` API for AppSync or `pipeline-name` function for the Lambda function. If a name is not specified, `--create-update-aws-pipeline` uses the `Neptune `database name.
+ **`--create-update-aws-pipeline-region (AWS region)`**   –   This argument sets the AWS region in which the pipeline for the GraphQL API is created. If not specified, the default region is either `us-east-1` or the region where the Neptune database is located, extracted from the database endpoint.
+ **`--create-update-aws-pipeline-neptune-endpoint (endpoint URL)`**   –   This argument sets the Neptune database endpoint used by the Lambda function to query the database. If not set, the endpoint set by `--input-graphdb-schema-neptune-endpoint` is used.
+ **`--remove-aws-pipeline-name (pipeline name)`**   –   This argument removes a pipeline created using `--create-update-aws-pipeline`. The resources to remove are listed in a file named `(pipeline name).resources.json`.
+ **`--output-aws-pipeline-cdk`**   –   This argument triggers the creation of a CDK file that can be used to create the AWS resources for the GraphQL API, including the AppSync GraphQL API and the Lambda function that runs the resolver.
+ **`--output-aws-pipeline-cdk-neptume-endpoint (endpoint URL)`**   –   This argument sets the Neptune database endpoint used by the Lambda function to query the Neptune database. If not set, the endpoint set by `--input-graphdb-schema-neptune-endpoint` is used.
+ **`--output-aws-pipeline-cdk-name (pipeline name)`**   –   This argument sets the pipeline name for the AppSync API and the Lambda pipeline-name function to use. If not specified, `--create-update-aws-pipeline` uses the Neptune database name.
+ **`--output-aws-pipeline-cdk-region (AWS region)`**   –   This sets the AWS region in which the pipeline for the GraphQL API is created. If not specified, it defaults to `us-east-1` or region where the Neptune database is located, extracted from the database endpoint.
+ **`--output-aws-pipeline-cdk-file (file name)`**   –   This sets the CDK file name. If not set the default is `(pipeline name)-cdk.js`.