Introspect schema from an existing Neo4j database

With a separate npm package, @neo4j/introspector, Neo4j provides a tool that enables the generation of GraphQL type definitions from an existing database. This is usually a one-time-thing and should be considered a starting point for a GraphQL schema.

@neo4j/introspector has full support for generating type definitions, including:

  • A @relationship directive, including relationship properties.

  • A @node type featuring:

    • label for mapping where a node label might use a character that is not in the GraphQL supported character set.

    • additionalLabels for nodes that has multiple labels.

  • A read-only version of the GraphQL type definitions.

If an element property has mixed types throughout your graph, that property is excluded from the generated type definitions. The reason for this is that your GraphQL server throws an error if it finds data that doesn’t match the specified type.

If any properties are skipped, this is noted in the Debug logging.

Usage examples

You can use a programmatic API for introspecting the Neo4j schema and generating GraphQL type definitions. Here are some scenarios you might encounter.

Introspect and persist to file

This example introspects the database schema, generates GraphQL type definitions and persists them to a file schema.graphql. You can then serve this file with your GraphQL server:

import { toGraphQLTypeDefs } from "@neo4j/introspector";
import fs from "fs";
import neo4j from "neo4j-driver";

const driver = neo4j.driver(
    "neo4j://localhost:7687",
    neo4j.auth.basic("username", "password")
);

const sessionFactory = () => driver.session({ defaultAccessMode: neo4j.session.READ })

// We create a async function here until "top level await" has landed
// so we can use async/await
async function main() {
    const typeDefs = await toGraphQLTypeDefs(sessionFactory)
    fs.writeFileSync('schema.graphql', typeDefs)
    await driver.close();
}
main()

Introspect and spin up a read-only schema

This example generates a read-only version of the schema from the database and immediately spins up an Apollo server. Here the type definitions are never persisted to disk:

import { ApolloServer } from "@apollo/server";
import { startStandaloneServer } from '@apollo/server/standalone';
import { Neo4jGraphQL } from "@neo4j/graphql";
import { toGraphQLTypeDefs } from "@neo4j/introspector";
import neo4j from "neo4j-driver";

const driver = neo4j.driver(
    "neo4j://localhost:7687",
    neo4j.auth.basic("username", "password")
);

const sessionFactory = () =>
    driver.session({ defaultAccessMode: neo4j.session.READ });

// We create a async function here until "top level await" has landed
// so we can use async/await
async function main() {
    const readonly = true; // We don't want to expose mutations in this case
    const typeDefs = await toGraphQLTypeDefs(sessionFactory, readonly);

    const neoSchema = new Neo4jGraphQL({ typeDefs, driver });

    const server = new ApolloServer({
        schema: await neoSchema.getSchema(),
    });

    await startStandaloneServer(server, {
        context: async ({ req }) => ({ req }),
    });
}

main();