How to use the OGM with subscriptions

The Neo4j GraphQL Library can be used in conjunction with the OGM in order to extend the library’s functionalities, or to take advantage of the @private directive.

Usage

This section shows how to use subscriptions with the OGM inside custom resolvers.

Prerequisites

  1. Use the following type definitions:

const typeDefs = `#graphql
    type User {
      email: String!
      password: String! @private
    }
`;
  1. Set up a subscriptions plugin instance to be shared between the Library and the OGM constructors:

const subscriptionsPlugin = new Neo4jGraphQLSubscriptionsSingleInstancePlugin();
  1. Set up a server that supports subscriptions. See more instructions in the Getting started page.

Adding the OGM

In order to utilize the @private marked field on the User type, you need to create the User model. Also, to use the subscriptions, you need to pass in the subscriptions plugin instance to the OGM constructor:

const ogm = new OGM({ typeDefs, driver, plugins: { subscriptions: subscriptionsPlugin } });
const Profile = ogm.model("Profile");

Make sure you initialize the OGM instance before using it by adding the following line to the main() function:

await ogm.init();

Adding a custom resolver

Custom resolvers can be used for multiple reasons such as performing data manipulation and checks, or interact with third party systems. In this case, you only need to create a User node with the password field set. You can do that by adding a sign-up mutation. For example:

const resolvers = {
  Mutation: {
    signUp: async (_source, { username, password }) => {
      const [existing] = await User.find({
          where: {
              username,
          },
      });
      if (existing) {
          throw new Error(`User with username ${username} already exists!`);
      }
      const { users } = await User.create({
          input: [
              {
                  username,
                  password,
              }
          ]
      });
      return createJWT({ sub: users[0].id });
    },
  },
};
const typeDefs = `
   type Mutation {
        signUp(username: String!, password: String!): String!
    }
`

Conclusion

Altogether, it should look like this:

const subscriptionsPlugin = new Neo4jGraphQLSubscriptionsSingleInstancePlugin();
const driver = neo4j.driver(
  "bolt://localhost:7687",
  neo4j.auth.basic("neo4j", "password")
);
const typeDefs = `
  type User {
   email: String!
   password: String! @private
  }
  type Mutation {
    signUp(username: String!, password: String!): String! # custom resolver
  }
`
const resolvers = {
  Mutation: {
    signUp: async (_source, { username, password }) => {
      const [existing] = await User.find({
          where: {
              username,
          },
      });
      if (existing) {
          throw new Error(`User with username ${username} already exists!`);
      }
      const { users } = await User.create({
          input: [
              {
                  username,
                  password,
              }
          ]
      });
      return createJWT({ sub: users[0].id });
    },
  },
};
// Share the subscriptions plugin instance across the Library and the OGM
const neoSchema = new Neo4jGraphQL({
  typeDefs,
  driver,
  resolvers,
  plugins: {
    subscriptions: subscriptionsPlugin,
  },
});
const ogm = new OGM({ typeDefs, driver, plugins: { subscriptions: subscriptionsPlugin } });
const Profile = ogm.model("Profile");

async function main() {
  // initialize the OGM instance
  await ogm.init();

   // Apollo server setup with WebSockets
  const app = express();
  const httpServer = createServer(app);
  const wsServer = new WebSocketServer({
    server: httpServer,
    path: "/graphql",
  });

  // Neo4j schema
  const schema = await neoSchema.getSchema();

  const serverCleanup = useServer(
    {
      schema,
      context: (ctx) => {
        return ctx;
      },
    },
    wsServer
  );

  const server = new ApolloServer({
    schema,
    plugins: [
      ApolloServerPluginDrainHttpServer({
        httpServer,
      }),
      {
        async serverWillStart() {
          return Promise.resolve({
            async drainServer() {
              await serverCleanup.dispose();
            },
          });
        },
      },
    ],
  });
  await server.start();

  app.use(
    "/graphql",
    cors(),
    bodyParser.json(),
    expressMiddleware(server, {
      context: async ({ req }) => ({ req }),
    })
  );

  const PORT = 4000;
  httpServer.listen(PORT, () => {
    console.log(`Server is now running on http://localhost:${PORT}/graphql`);
  });
}

Receiving the subscription events

First, run the following subscription to receive User creation events:

subscription {
  userCreated {
    createdUser {
      email
    }
    event
  }
}

Then run the sign-up mutation:

mutation {
  signUp(email: "jon.doe@xyz.com", password: "jondoe") {
    email
    password
  }
}

The results should look like this:

{
  "data": {
    "userCreated": {
      "createdUser": {
        "email": "jon.doe@xyz.com",
        "password": "jondoe"
      },
      "event": "CREATE"
    }
  }
}