git clone https://github.com/rxdi/starter-neo4j
npm i -g @gapi/cli ts-node
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 isneo4j
Go to
src/app/app.module.ts
and setuppassword
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 {}
import { GraphQLObjectType, GraphQLString } from 'graphql';
export const UserType = new GraphQLObjectType({
name: 'User',
fields: () => ({
userName: {
type: GraphQLString
},
})
});
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 {}
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()
});
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`))
);
}
}
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;
}
}
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;
}
}
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 {}
import { Driver } from '@rxdi/neo4j';
export interface GraphQLContext {
driver: Driver;
}
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 ! :)