Neo4j + nestjs + graphql : directives @relashionship unknown and not configurable

Hi I am having massive trouble to do an apparently very simple thing:

NestJS + GraphQL + Neo4J

The issue: the custom directive of neo4j generated from graphql schema are unknown to any framework from the outside.

I see that many other people have struggled with that and can not find any example or doc how to find it.

Here is my generated schema out of neo4j:

interface ActedInProperties @relationshipProperties {

roles: [String]!

}

type Movie {

genre: String!

peopleActedIn: [Person!]! @relationship(type: "ACTED_IN", direction: IN, properties: "ActedInProperties")

peopleDirected: [Person!]! @relationship(type: "DIRECTED", direction: IN)

peopleProduced: [Person!]! @relationship(type: "PRODUCED", direction: IN)

peopleReviewed: [Person!]! @relationship(type: "REVIEWED", direction: IN, properties: "ReviewedProperties")

peopleWrote: [Person!]! @relationship(type: "WROTE", direction: IN)

released: BigInt!

tagline: String!

title: String!

}

type Person {

actedInMovies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT, properties: "ActedInProperties")

born: BigInt!

directedMovies: [Movie!]! @relationship(type: "DIRECTED", direction: OUT)

followsPeople: [Person!]! @relationship(type: "FOLLOWS", direction: OUT)

name: String!

peopleFollows: [Person!]! @relationship(type: "FOLLOWS", direction: IN)

producedMovies: [Movie!]! @relationship(type: "PRODUCED", direction: OUT)

reviewedMovies: [Movie!]! @relationship(type: "REVIEWED", direction: OUT, properties: "ReviewedProperties")

wroteMovies: [Movie!]! @relationship(type: "WROTE", direction: OUT)

}

interface ReviewedProperties @relationshipProperties {

rating: BigInt!

summary: String!

}

Now I want just to spin up a nestjs stack using this schema and generate the types for it, so I do something like:

import { readFileSync } from 'fs';

import { Module } from '@nestjs/common';

import { AppController } from './app.controller';

import { AppService } from './app.service';

import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';

import { GraphQLModule } from '@nestjs/graphql';

import { Neo4jGraphQL } from '@neo4j/graphql';

const path = `${process.cwd()}/apps/be/src/app/schema/generatedGraphql.ts`;

const typeDefs = readFileSync(

'apps/be/src/app/schema/generatedSchemaRel.graphql',

'utf-8'

);

// how to combine this specific schema with nestjs ?

// const augmentedSchema = makeAugmentedSchema({ typeDefs });

// here is the newest way according to docu

const neo4jGraphQL = new Neo4jGraphQL({ typeDefs });

let schema;

neo4jGraphQL.getSchema().then((s)=>{

schema = s;

});

const graphQLModule = GraphQLModule.forRoot<ApolloDriverConfig>({

driver: ApolloDriver,

schema, // how do I propaget the neo4j specific schema to nestjs?

typePaths: ['./**/*.graphql'],

definitions: {

path,

outputAs: 'class',

},

});

@Module({

imports: [graphQLModule],

controllers: [AppController],

providers: [AppService],

})

export class AppModule {}

The error is always the same, the @relationship directive is just unknown to nestjs and there is no way to configure it easilly.

Thanks in advance for your help.

You need to await the async schema generation before you pass it to Apollo Server,

you definitely need to use the schema generated by the library.

Hi Michael,

thanks for the tip, yes I know I had this issue in the code, but I though its not about it.

Now I managed to create it loading before with async config in nest, but I still have the same issue. Here is my currenct code, its obviously still not taking the schema.

import { Neo4jGraphQL } from '@neo4j/graphql';
import { ApolloDriverConfig, ApolloDriver } from '@nestjs/apollo';
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { readFileSync } from 'fs';
import neo4j from 'neo4j-driver';
import { AppController } from './app.controller';
import { AppService } from './app.service';
  
const neoDriver = neo4j.driver(
  'neo4j://localhost:7687',
  neo4j.auth.basic('neo4j', 'password')
);

@Module({
  imports: [
    GraphQLModule.forRootAsync<ApolloDriverConfig>({
      driver: ApolloDriver,
      useFactory: async () => {
        const typeDefs = readFileSync(
          'apps/be/src/app/schema/generatedSchemaRel.graphql',
          'utf-8'
        );
        const neo4jGraphQL = new Neo4jGraphQL({ typeDefs, driver: neoDriver });
        const schema = await neo4jGraphQL.getSchema();
        console.log('neo4jGraphQLSchema in app ', Object.keys(schema));
        return {
          schema,
          typePaths: ['./**/*.graphql'],
          definitions: {
            path: `${process.cwd()}/apps/be/src/app/schema/generatedGraphql.ts`,
          },
        };
      },
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Here is the log

[Nest] 75139  - 07/13/2022, 11:08:44 AM     LOG [NestFactory] Starting Nest application...
neo4jGraphQLSchema in app  [
  '__validationErrors',
  'description',
  'extensions',
  'astNode',
  'extensionASTNodes',
  '_queryType',
  '_mutationType',
  '_subscriptionType',
  '_directives',
  '_typeMap',
  '_subTypeMap',
  '_implementationsMap'
]
[Nest] 75139  - 07/13/2022, 11:08:44 AM     LOG [InstanceLoader] AppModule dependencies initialized +138ms
[Nest] 75139  - 07/13/2022, 11:08:44 AM     LOG [InstanceLoader] GraphQLSchemaBuilderModule dependencies initialized +0ms
[Nest] 75139  - 07/13/2022, 11:08:44 AM     LOG [InstanceLoader] GraphQLModule dependencies initialized +0ms
[Nest] 75139  - 07/13/2022, 11:08:44 AM     LOG [RoutesResolver] AppController {/api}: +2ms
[Nest] 75139  - 07/13/2022, 11:08:44 AM     LOG [RouterExplorer] Mapped {/api, GET} route +2ms

/projectRootPath/node_modules/graphql/validation/validate.js:135
    throw new Error(errors.map((error) => error.message).join('\n\n'));
          ^
Error: Unknown directive "@relationshipProperties".

Unknown directive "@relationship".
Unknown type "BigInt". Did you mean "Int"?

.... some more similar Unknown type errors here...

Unknown type "BigInt". Did you mean "Int"?
    at assertValidSDL (/projectRootPath/node_modules/graphql/validation/validate.js:135:11)
    at Object.buildASTSchema (/projectRootPath/node_modules/graphql/utilities/buildASTSchema.js:44:34)
    at makeExecutableSchema (/projectRootPath/node_modules/@nestjs/graphql/node_modules/@graphql-tools/schema/index.js:495:26)
    at GraphQLFactory.mergeWithSchema (/projectRootPath/node_modules/@nestjs/graphql/dist/graphql.factory.js:67:68)
    at ApolloDriver.start (/projectRootPath/node_modules/@nestjs/apollo/dist/drivers/apollo.driver.js:19:51)
    at GraphQLModule.onModuleInit (/projectRootPath/node_modules/@nestjs/graphql/dist/graphql.module.js:104:36)
    at callModuleInitHook (/projectRootPath/node_modules/@nestjs/core/hooks/on-module-init.hook.js:51:9)
    at NestApplication.callInitHook (/projectRootPath/node_modules/@nestjs/core/nest-application-context.js:178:13)
    at NestApplication.init (/projectRootPath/node_modules/@nestjs/core/nest-application.js:96:9)
    at NestApplication.listen (/projectRootPath/node_modules/@nestjs/core/nest-application.js:158:33)

You could try declaring the directives in the GraphQL typedefs. For example

directive @relationship on FIELD_DEFINITION

Thank you!

Let me try to go down that route. I have seen implementations like that but was assuming you should have inside the neo graph ql package, since it looks like you are doing it without nestjs in other places.

Hi Minimalist.

Did you figure out how to resolve this issue? I'm stuck in the same place as you :disappointed:

Not yet, I am starting this week, I will post here my progress. If you find something new, pls update me as well....

Hi Minimalist.

I went down the rabbit hole of adding directives to the schema, but that didn't feel right.

Then found a working example with the below code and that worked. Haven't looked into the details as to why, but it allowed me to continue testing other bits and pieces.

Hope this helps!

BR

import { Neo4jGraphQL,  } from '@neo4j/graphql';
import { ApolloDriverConfig, ApolloDriver } from '@nestjs/apollo';
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { readFileSync } from 'fs';
import neo4j from 'neo4j-driver';

import { AppController } from './app.controller';
import { AppService } from './app.service';

import { ConfigService, ConfigModule } from '@nestjs/config';

@Module({
  imports: [
    ConfigModule.forRoot(),
    GraphQLModule.forRootAsync<ApolloDriverConfig>({
      imports: [ConfigModule],
      inject: [ConfigService],
      driver: ApolloDriver,
      
      useFactory: async (configService: ConfigService) => {
        
        const typeDefs = readFileSync('schema/schema.graphql', 'utf-8');

        const neo4jUri = configService.get('NEO4J_URI');
        const neo4jUser = configService.get('NEO4J_USERNAME');
        const neo4jPassword = configService.get('NEO4J_PASSWORD');

        const driver = neo4j.driver(
          neo4jUri,
          neo4j.auth.basic(neo4jUser, neo4jPassword)
        );

        const neoSchema = new Neo4jGraphQL({ typeDefs, driver });
        const schema = await neoSchema.getSchema()
        return {
          debug: true,
          playground: true,
          schema: schema
        };
      },
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Hi Isevme1,

thank you so much for this update. Yes, I see this example here: https://github.com/chenxinhu/nestjs-graphql-neo4j-example

Unfortunately it only works if the TS types are not generated out of schema, but this was actually my motivation. Anyways, I think I will find a different way to generate the typings and update you if I have something new. Are you able to submit a call out of graphql that hits the neo4j with this setup?

It looks like I found a solution, and its name is ... OGM

With OGM I get my generated types out of the graphQL schema and I can pass a custom resolver to my neo4jgraphql server so I use the custom resolvers and thats it. My mistake was in the assumption that nestjs can do the type generation out of a neo4j specific schema, but the only way to resolve that is to explain custom directives to nestjs, which is too much work and not needed.

To save time to others, here is a full working example, assuming you have a generated graphql schema.

import { Neo4jGraphQL } from '@neo4j/graphql';
import { generate, OGM } from '@neo4j/graphql-ogm';
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { readFileSync } from 'fs';
import neo4j from 'neo4j-driver';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ModelMap } from './schema/ogmTypes';

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

// Step 1. generate your schema out of neo4j db, using neo4j driver 
// like 
// const { toGraphQLTypeDefs } = require("@neo4j/introspector")
    // const typeDefs = await toGraphQLTypeDefs(sessionFactory);
    // fs.writeFileSync(
    //   'apps/be/src/app/schema/generatedSchema.graphql',
    //   typeDefs
    // );
    // await driver.close();
//
const SCHEMA = 'apps/be/src/app/schema/schemaMovieDB.graphql';

const generateTypesFromGraphQlSchema = false;
const generatedTypesPath = 'apps/be/src/app/schema/ogmTypes.ts';

@Module({
  imports: [
    GraphQLModule.forRootAsync<ApolloDriverConfig>({
      driver: ApolloDriver,
      useFactory: async () => {
        
        const typeDefs = readFileSync(SCHEMA, 'utf-8');

        // 2. Step, generate TS types according to given schema to use it for custom logic
        const ogm = new OGM<ModelMap>({ typeDefs, driver });
        if (generateTypesFromGraphQlSchema) {
          await generate({
            ogm,
            outFile: generatedTypesPath,
          });
          console.log('Types Generated to ', generatedTypesPath);
          process.exit(1);
        }
        await ogm.init();

        const resolvers = {
          Query: {
            yourCustomerResolverName: async (_source, { name }) => {
              const extractedType = 'Person';
              const SomeCustomType = ogm.model(extractedType);
              const companies = await SomeCustomType.find({ where:{name:"SomeName"} });
              // const companies = [];
              return companies;
            },
          },
        };

        // 3. Step bring it all together with graphQL Schema and custom resolvers to configure the final graphQL server
        const neo4jGraphQL = new Neo4jGraphQL({ typeDefs, driver, resolvers });
        const schema = await neo4jGraphQL.getSchema();

        return {
          schema,
          playground: true,
        };
      },
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}