Air Routes Example: Starting from a Neptune database with data - v2.0.0

August 5, 2025 ยท View on GitHub

Amazon Neptune uses the Air Routes dataset in several Notebook tutorials. If you don't have a Neptune database with the Air Routes data you can create a new database, and follow one of the Notebook tutorials to seed it with the Air Routes data.

Then, you can run the Neptune GraphQL Utility to create an AppSync GraphQL API or generate Apollo Server artifacts.

AppSync command

neptune-for-graphql \
  --input-graphdb-schema-neptune-endpoint <your-neptune-database-endpoint:port> \
  --create-update-aws-pipeline \
  --create-update-aws-pipeline-name AirRoutesExample \
  --output-resolver-query-https

Apollo command

neptune-for-graphql \
  --input-graphdb-schema-neptune-endpoint <your-neptune-database-endpoint:port> \
  --create-update-apollo-server \
  --output-resolver-query-https

The utility will log the graph database schema it finds, the files it creates, and any AWS resources it creates or modifies during execution. If you ever want to run the command while logging only errors, use the --quiet CLI option.

Running

For AppSync, the utility creates these files naming them based on the --create-update-aws-pipeline-name option, in our case AirRoutesExample:

filedescription
AirRoutes.zipthe Lambda code zip file
AirRoutesExample.source.schema.graphqlthe GraphQL schema with directives
AirRoutesExample.schema.graphqlthe GraphQL schema
AirRoutesExample.resolver.graphql.jsthe JavaScript resolver
AirRoutesExample.neptune.schema.jsonthe graph schema it discovered
AirRoutesExample-resources.jsonthe list of AWS Resources it created

For the Apollo Server, the utility creates the files below while naming them based on the name of your Neptune database. For example, if the Neptune database name was air-routes-example:

filedescription
apollo-server-air-routes-example-.zipthe Apollo Server zip file
air-routes-example.source.schema.graphqlthe GraphQL schema with directives
air-routes-example.schema.graphqlthe GraphQL schema
air-routes-example.resolver.graphql.jsthe JavaScript resolver
air-routes-example.neptune.schema.jsonthe graph schema it discovered

The graph schema it discovered

Below is the content of the file, AirRoutesExample.neptune.schema.json (or air-routes-example.neptune.schema.json for Apollo Server). The file contains nodes, edges, properties, and edges cardinality. The utility then uses this data to inference the GraphQL schema.

{
  "nodeStructures": [
    {
      "label": "continent",
      "properties": [
        {
          "name": "code",
          "type": "String"
        },
        {
          "name": "type",
          "type": "String"
        },
        {
          "name": "desc",
          "type": "String"
        }
      ]
    },
    {
      "label": "country",
      "properties": [
        {
          "name": "code",
          "type": "String"
        },
        {
          "name": "type",
          "type": "String"
        },
        {
          "name": "desc",
          "type": "String"
        }
      ]
    },
    {
      "label": "version",
      "properties": [
        {
          "name": "date",
          "type": "String"
        },
        {
          "name": "code",
          "type": "String"
        },
        {
          "name": "author",
          "type": "String"
        },
        {
          "name": "type",
          "type": "String"
        },
        {
          "name": "desc",
          "type": "String"
        }
      ]
    },
    {
      "label": "airport",
      "properties": [
        {
          "name": "country",
          "type": "String"
        },
        {
          "name": "longest",
          "type": "Int"
        },
        {
          "name": "code",
          "type": "String"
        },
        {
          "name": "city",
          "type": "String"
        },
        {
          "name": "elev",
          "type": "Int"
        },
        {
          "name": "icao",
          "type": "String"
        },
        {
          "name": "lon",
          "type": "Float"
        },
        {
          "name": "runways",
          "type": "Int"
        },
        {
          "name": "region",
          "type": "String"
        },
        {
          "name": "type",
          "type": "String"
        },
        {
          "name": "lat",
          "type": "Float"
        },
        {
          "name": "desc",
          "type": "String"
        }
      ]
    }
  ],
  "edgeStructures": [
    {
      "label": "contains",
      "properties": [],
      "directions": [
        {
          "from": "continent",
          "to": "airport",
          "relationship": "ONE-MANY"
        },
        {
          "from": "country",
          "to": "airport",
          "relationship": "ONE-MANY"
        }
      ]
    },
    {
      "label": "route",
      "properties": [
        {
          "name": "dist",
          "type": "Int"
        }
      ],
      "directions": [
        {
          "from": "airport",
          "to": "airport",
          "relationship": "MANY-MANY"
        }
      ]
    }
  ]
}

The GraphQL schema with directives

Below is the GraphQL schema with directives, inferred by the utility.

  • The Air Routes nodes' labels are lower case, and GraphQL type names are typically in Pascal case, so the utility added the @alias directive to map the names between GraphQL and the Neptune Database.
  • Because the node labelled continent is connected to the airport using the edge contains and the cardinality is one to many, you can see the directive @relationship in the GraphQL type Continent and new field names airportContainssOut returning an array of Airport. The type Airport has an opposite field called continentContainsIn as single Continent. This tuple declaration will enable you to query the Continent from the Airport and the list of Airport/s from a Continent.
  • The edge route that connects an airport to another airport has a property called dist. You can find it in the type Route, and is added to the type Airport as well. We will later see how to query it.
  • For each type the utility also added inputs, which are used as helpers for queries and mutations.
  • For each node label the utility added two queries. One to retrieve a single node/type using an id or any of the type fields listed in the input, and the second to retrieve multiple values, again filtered using the input of that node/type.
  • For each node three mutations: create, update and delete. Selecting the node to delete using an id or the input for that node/type.
  • For edges two mutations: connect and delete. They take as input the ids of the from and to node, and in case the edge type has properties the correspondent input.

Tip

The queries and mutations you see below are recognized by the resolver based on the name pattern. If you need to customize it, first look at the documentation section: Customize the GraphQL schema with directives.

Air Routes GraphQL Schema

enum SortingDirection {
    ASC
    DESC
}

type Continent @alias(property: "continent") {
    _id: ID! @id
    code: String
    type: String
    desc: String
    airportContainssOut(filter: AirportInput, options: Options, sort: [AirportSort!]): [Airport] @relationship(edgeType: "contains", direction: OUT)
    contains: Contains
}

input ContinentInput {
    _id: ID @id
    code: StringScalarFilters
    type: StringScalarFilters
    desc: StringScalarFilters
}

input ContinentCreateInput {
    _id: ID @id
    type: String
    code: String
    desc: String
}

input ContinentUpdateInput {
    _id: ID! @id
    type: String
    code: String
    desc: String
}

input ContinentSort {
    _id: SortingDirection
    type: SortingDirection
    code: SortingDirection
    desc: SortingDirection
}

type Country @alias(property: "country") {
    _id: ID! @id
    code: String
    type: String
    desc: String
    airportContainssOut(filter: AirportInput, options: Options, sort: [AirportSort!]): [Airport] @relationship(edgeType: "contains", direction: OUT)
    contains: Contains
}

input CountryInput {
    _id: ID @id
    code: StringScalarFilters
    type: StringScalarFilters
    desc: StringScalarFilters
}

input CountryCreateInput {
    _id: ID @id
    type: String
    code: String
    desc: String
}

input CountryUpdateInput {
    _id: ID! @id
    type: String
    code: String
    desc: String
}

input CountrySort {
    _id: SortingDirection
    type: SortingDirection
    code: SortingDirection
    desc: SortingDirection
}

type Version @alias(property: "version") {
    _id: ID! @id
    date: String
    code: String
    author: String
    type: String
    desc: String
}

input VersionInput {
    _id: ID @id
    date: StringScalarFilters
    code: StringScalarFilters
    author: StringScalarFilters
    type: StringScalarFilters
    desc: StringScalarFilters
}

input VersionCreateInput {
    _id: ID @id
    date: String
    code: String
    author: String
    type: String
    desc: String
}

input VersionUpdateInput {
    _id: ID! @id
    date: String
    code: String
    author: String
    type: String
    desc: String
}

input VersionSort {
    _id: SortingDirection
    date: SortingDirection
    code: SortingDirection
    author: SortingDirection
    type: SortingDirection
    desc: SortingDirection
}

type Airport @alias(property: "airport") {
    _id: ID! @id
    country: String
    longest: Int
    code: String
    city: String
    elev: Int
    icao: String
    lon: Float
    runways: Int
    region: String
    type: String
    lat: Float
    desc: String
    continentContainsIn: Continent @relationship(edgeType: "contains", direction: IN)
    countryContainsIn: Country @relationship(edgeType: "contains", direction: IN)
    airportRoutesOut(filter: AirportInput, options: Options, sort: [AirportSort!]): [Airport] @relationship(edgeType: "route", direction: OUT)
    airportRoutesIn(filter: AirportInput, options: Options, sort: [AirportSort!]): [Airport] @relationship(edgeType: "route", direction: IN)
    contains: Contains
    route: Route
}

input AirportInput {
    _id: ID @id
    country: StringScalarFilters
    longest: Int
    code: StringScalarFilters
    city: StringScalarFilters
    elev: Int
    icao: StringScalarFilters
    lon: Float
    runways: Int
    region: StringScalarFilters
    type: StringScalarFilters
    lat: Float
    desc: StringScalarFilters
}

input AirportCreateInput {
    _id: ID @id
    country: String
    longest: Int
    code: String
    city: String
    elev: Int
    icao: String
    lon: Float
    runways: Int
    region: String
    type: String
    lat: Float
    desc: String
}

input AirportUpdateInput {
    _id: ID! @id
    country: String
    longest: Int
    code: String
    city: String
    elev: Int
    icao: String
    lon: Float
    runways: Int
    region: String
    type: String
    lat: Float
    desc: String
}

input AirportSort {
    _id: SortingDirection
    country: SortingDirection
    longest: SortingDirection
    code: SortingDirection
    city: SortingDirection
    elev: SortingDirection
    icao: SortingDirection
    lon: SortingDirection
    runways: SortingDirection
    region: SortingDirection
    type: SortingDirection
    lat: SortingDirection
    desc: SortingDirection
}

type Contains @alias(property: "contains") {
    _id: ID! @id
}

type Route @alias(property: "route") {
    _id: ID! @id
    dist: Int
}

input RouteInput {
    dist: Int
}

input Options {
    limit: Int
}

input StringScalarFilters {
    eq: String
    contains: String
    endsWith: String
    startsWith: String
}

type Query {
    getContinent(filter: ContinentInput): Continent
    getContinents(filter: ContinentInput, options: Options, sort: [ContinentSort!]): [Continent]
    getCountry(filter: CountryInput): Country
    getCountrys(filter: CountryInput, options: Options, sort: [CountrySort!]): [Country]
    getVersion(filter: VersionInput): Version
    getVersions(filter: VersionInput, options: Options, sort: [VersionSort!]): [Version]
    getAirport(filter: AirportInput): Airport
    getAirports(filter: AirportInput, options: Options, sort: [AirportSort!]): [Airport]
}

type Mutation {
    createContinent(input: ContinentInput!): Continent
    updateContinent(input: ContinentInput!): Continent
    deleteContinent(_id: ID!): Boolean
    createCountry(input: CountryInput!): Country
    updateCountry(input: CountryInput!): Country
    deleteCountry(_id: ID!): Boolean
    createVersion(input: VersionInput!): Version
    updateVersion(input: VersionInput!): Version
    deleteVersion(_id: ID!): Boolean
    createAirport(input: AirportInput!): Airport
    updateAirport(input: AirportInput!): Airport
    deleteAirport(_id: ID!): Boolean
    connectContinentToAirportThroughContains(from_id: ID!, to_id: ID!): Contains
    deleteContainsConnectionFromContinentToAirport(from_id: ID!, to_id: ID!): Boolean
    connectCountryToAirportThroughContains(from_id: ID!, to_id: ID!): Contains
    deleteContainsConnectionFromCountryToAirport(from_id: ID!, to_id: ID!): Boolean
    connectAirportToAirportThroughRoute(from_id: ID!, to_id: ID!, edge: RouteInput!): Route
    updateRouteConnectionFromAirportToAirport(from_id: ID!, to_id: ID!, edge: RouteInput!): Route
    deleteRouteConnectionFromAirportToAirport(from_id: ID!, to_id: ID!): Boolean
}

schema {
    query: Query
    mutation: Mutation
}

Let's use our new GraphQL API from AppSync console

Below is a snapshot of the AppSync Queries console used to test our new GraphQL API named AirRoutesExampleAPI. In the middle window, the Explorer shows you the queries and mutations. You can then pick a query, the input parameters and the return fields.

In this case it formed a GraphQL query that is looking for one Airport, with code equal SEA, and return the city, the outbound flights airportsOut, and for each destination the city and the route distance, dist. As mentioned earlier, in the graph database the nodes airport are connected with the edge type route, and dist is a property of the edge.

You can then follow the AppSync documentation on how to call the GraphQL API from your application, enable caching and other AppSync API features.

AppSync Queries UI

Using the new GraphQL API from the Apollo Server

If you had opted to use Apollo Server instead of App Sync with the --create-update-apollo-server option, you will need to execute the following steps to start the Apollo Server and access the Queries console:

  1. unzip apollo-server--.zip
  2. change directory into the unzipped folder
  3. execute npm install to install required dependencies
  4. execute node index.mjs to start the Apollo Server
  5. access the graphQL application in a browser by visiting http://localhost:4000/

Below is a snapshot of the Apollo Server Queries console used to test our new GraphQL API. On the left, the Documentation panel shows you the queries and mutations. You can then pick a query, the input parameters and the return fields.

The GraphQL query generated in the Operations panel follows the same pattern as the AppSync GraphQL query above.

Apollo Queries UI