Skip to content
This repository has been archived by the owner on Jan 7, 2023. It is now read-only.

Example starter project with autogenerated CRUD operations based on NEO4J Graph database

Notifications You must be signed in to change notification settings

rxdi/starter-neo4j-typescript-complex

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

@Gapi Starter Neo4J Graph

gapi-cli

To start developing clone repository

git clone https://github.com/rxdi/starter-neo4j

Install @gapi command line interface and typescript node

npm i -g @gapi/cli ts-node

Install project dependencies

cd starter-neo4j && npm install

Download Neo4J database https://neo4j.com/download/

Follow the steps and create your Graph using interface provided and set password to it

default graphName for neo4j is neo4j

Go to src/app/app.module.ts and setup password field

import { Module, CoreModule } from '@gapi/core';
import { VoyagerModule } from '@gapi/voyager';
import { Neo4JModule } from '@rxdi/neo4j';

@Module({
  imports: [
    CoreModule.forRoot(),
    Neo4JModule.forRoot({
      types: [],
      graphName: 'neo4j',
      graphAddress: 'bolt://localhost:7687',
      password: 'your-password',
      excludedTypes: {
        mutation: {
          exclude: []
        },
        query: {
          exclude: []
        }
      }
    }),
    VoyagerModule.forRoot()
  ]
})
export class AppModule {}
Simple GraphqlObject UserType
import { GraphQLObjectType, GraphQLString } from 'graphql';

export const UserType = new GraphQLObjectType({
  name: 'User',
  fields: () => ({
    userName: {
      type: GraphQLString
    },
  })
});
Import inside AppModule or CoreModule
import { Module, CoreModule } from "@gapi/core";
import { UserContext } from "./types/user/user-context.type";
import { User } from "./types/user/user.type";
import { Message } from "./types/message/message.type";
import { Channel } from "./types/channel/channel.type";
import { AttachmentType } from "./types/attachment/attachment.type";
import { VoyagerModule } from "@gapi/voyager";
import { Neo4JModule } from "@rxdi/neo4j";
import { ToUpperCaseDirective } from "./core/directives/toUppercase.directive";

// import { AppQueriesController } from "./app.controller";
// Uncomment to override some methods which are provided from neo4js

@Module({
  // controllers: [AppQueriesController],
  imports: [
    CoreModule.forRoot({
      graphql: { directives: [ToUpperCaseDirective] }
    }),
    Neo4JModule.forRoot({
      types: [UserContext, User, Message, Channel, AttachmentType],
      graphName: "neo4j",
      graphAddress: "bolt://localhost:7687",
      password: "your-password",
      excludedTypes: {
        mutation: {
          exclude: [UserContext]
        }
      }
    }),
    VoyagerModule.forRoot({
      endpointUrl: "/graphql",
      path: "/voyager"
    })
  ]
})
export class AppModule {}
Graphql Directive
import { GraphQLCustomDirective } from '@gapi/core';
import { DirectiveLocation } from 'graphql';

export const ToUpperCaseDirective = new GraphQLCustomDirective<string>({
  name: 'toUpperCase',
  description: 'change the case of a string to uppercase',
  locations: [DirectiveLocation.FIELD],
  resolve: async resolve => (await resolve()).toUpperCase()
});
Interceptor
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { Service } from '@rxdi/core';
import { InterceptResolver, GenericGapiResolversType } from '@gapi/core';
import { GraphQLContext } from '../../app.context';

@Service()
export class LoggerInterceptor implements InterceptResolver {
  intercept(
    chainable$: Observable<any>,
    context: GraphQLContext,
    payload,
    descriptor: GenericGapiResolversType
  ) {
    console.log('Before...');
    const now = Date.now();
    return chainable$.pipe(
      tap(() => console.log(`After... ${Date.now() - now}ms`))
    );
  }
}
Guard
import { Service } from '@rxdi/core';
import { CanActivateResolver, GenericGapiResolversType } from '@gapi/core';
import { GraphQLContext } from '../../app.context';

@Service()
export class AdminOnly implements CanActivateResolver {
  canActivate(
    context: GraphQLContext,
    payload,
    descriptor: GenericGapiResolversType
  ) {
    return false;
  }
}
Controller

The name of our class methods is important since we want to override default neo4j-graphql autogenerated types

import { Controller, Type, Mutation, GraphQLString, Query, Interceptor, Guard } from '@gapi/core';
import { Message } from './types/message/message.type';
import { GraphQLContext } from './app.context';
import { GraphQLList } from 'graphql';
import { graphRequest } from '@rxdi/neo4j';
import { IMessage } from './core/api-introspection';
import { LoggerInterceptor } from './core/interceptors/logger.interceptor';
import { AdminOnly } from './core/guards/admin-only.guard';

@Controller()
export class AppQueriesController {

  @Interceptor(LoggerInterceptor)
  @Type(Message)
  @Guard(AdminOnly)
  @Mutation({
    messageId: {
      type: GraphQLString
    },
    channelId: {
      type: GraphQLString
    }
  })
  CreateMessage(root, params, ctx: GraphQLContext, resolveInfo): Promise<IMessage> {
    return graphRequest<IMessage>(root, params, ctx, resolveInfo);
  }

  @Type(new GraphQLList(Message))
  @Query({
    messageId: {
      type: GraphQLString
    },
    channelId: {
      type: GraphQLString
    }
  })
  Messages(root, params, ctx: GraphQLContext, resolveInfo): Promise<IMessage[]> {
    return graphRequest<IMessage[]>(root, params, ctx, resolveInfo);
  }

  @Type(Message)
  @Query({
    messageId: {
      type: GraphQLString
    },
    channelId: {
      type: GraphQLString
    }
  })
  Message(root, params, ctx: GraphQLContext, resolveInfo): Promise<IMessage> {
    return graphRequest<IMessage>(root, params, ctx, resolveInfo);
  }


  @Type(Message)
  @Subscribe((self: AppQueriesController) => self.pubsub.asyncIterator('CREATE_SIGNAL_BASIC'))
  @Subscription()
  subscribeToUserMessagesBasic(message: IMessage): IMessage {
      return message;
  }


  @Type(Message)
  @Subscribe(
      withFilter(
          (self: AppQueriesController) => self.pubsub.asyncIterator('CREATE_MESSAGE_WITH_FILTER'),
          (payload, {id}, context) => {
              console.log('Subscribed User: ', id, context);
              return true;
          }
      )
  )
  @Subscription({
      id: {
          type: new GraphQLNonNull(GraphQLInt)
      }
  })
  subscribeToUserMessagesWithFilter(message: IMessage): IMessage {
      return message;
  }

}
Effects
import { Effect, OfType, PubSubService } from "@gapi/core";
import { EffectTypes } from "../api-introspection/EffectTypes";
import { GraphQLContext } from "../../app.context";
import { IMessage } from "../api-introspection";

@Effect()
export class MessagesEffect {
  constructor(
      private pubsub: PubSubService
  ) {}

  @OfType(EffectTypes.CreateMessage)
  createMessageEffect(result: IMessage, args, context: GraphQLContext) {
    this.pubsub.publish('CREATE_MESSAGE', result);
    // this will be triggered when CreateMessage effect is executed
    console.log(result, args, context);
  }
}

Important! The effect needs to be imported inside module to work

import { Module } from "@gapi/core";
import { AppQueriesController } from "./app.controller";
import { MessagesEffect } from "./core/effects/messages.effect";

@Module({
  controllers: [AppQueriesController],
  effects: [MessagesEffect]
})
export class AppModule {}
Context interface
import { Driver } from '@rxdi/neo4j';

export interface GraphQLContext {
  driver: Driver;
}
Example query

Note: offset, first, orderBy are autogenerated for convenience

query {
  User(userName: "your-name", first: 10, offset: 10, orderBy: userName_asc) {
    userName
  }
}

Types can be found inside ./src/app/types

TODO: Better documentation...

Enjoy ! :)

About

Example starter project with autogenerated CRUD operations based on NEO4J Graph database

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published