Working with directives for a GraphQL schema
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
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
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):
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
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 and the Air Routes Example.
The @graphQuery
and @cypher
directives
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
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
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
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.