-
Notifications
You must be signed in to change notification settings - Fork 15
How-to guide on subscriptions and OGM #101
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,247 @@ | ||
| [[ogm-subscriptions]] | ||
| :description: This how-to guide shows how to use the OGM with subscriptions. | ||
| = 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 xref:ogm/private.adoc[`@private` directive]. | ||
|
|
||
| == Usage | ||
|
|
||
| This section shows how to use subscriptions with the OGM inside custom resolvers. | ||
|
|
||
| === Prerequisites | ||
|
|
||
| . Use the following type definitions: | ||
| [source, javascript, indent=0] | ||
| ---- | ||
| const typeDefs = `#graphql | ||
| type User { | ||
| email: String! | ||
| password: String! @private | ||
| } | ||
| `; | ||
| ---- | ||
|
|
||
| . Set up a subscriptions plugin instance to be shared between the Library and the OGM constructors: | ||
| [source, javascript, indent=0] | ||
| ---- | ||
| const subscriptionsPlugin = new Neo4jGraphQLSubscriptionsSingleInstancePlugin(); | ||
| ---- | ||
|
|
||
| . Set up a server that supports subscriptions. | ||
| See more instructions in the xref:subscriptions/getting-started.adoc#setting-up-server[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. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if this line shouldn't be a step in the guide with an example of how to do that? |
||
| Also, to use the subscriptions, you need to pass in the subscriptions plugin instance to the OGM constructor: | ||
|
|
||
| [source, javascript, indent=0] | ||
| ---- | ||
| 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: | ||
| [source, javascript, indent=0] | ||
| ---- | ||
| await ogm.init(); | ||
| ---- | ||
|
|
||
| === Adding a custom resolver | ||
|
|
||
| xref:custom-resolvers/[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: | ||
|
|
||
| [source, javascript, indent=0] | ||
| ---- | ||
| 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! | ||
| } | ||
| ` | ||
| ---- | ||
|
|
||
| [discrete] | ||
| === Conclusion | ||
|
|
||
| Altogether, it should look like this: | ||
|
|
||
| [source, javascript, indent=0] | ||
| ---- | ||
| 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: | ||
| [source, gql, indent=0] | ||
| ---- | ||
| subscription { | ||
| userCreated { | ||
| createdUser { | ||
| } | ||
| event | ||
| } | ||
| } | ||
| ---- | ||
|
|
||
| Then run the sign-up mutation: | ||
| [source, gql, indent=0] | ||
| ---- | ||
| mutation { | ||
| signUp(email: "jon.doe@xyz.com", password: "jondoe") { | ||
| password | ||
| } | ||
| } | ||
| ---- | ||
|
|
||
| The results should look like this: | ||
| [source, gql, indent=0] | ||
| ---- | ||
| { | ||
| "data": { | ||
| "userCreated": { | ||
| "createdUser": { | ||
| "email": "jon.doe@xyz.com", | ||
| "password": "jondoe" | ||
| }, | ||
| "event": "CREATE" | ||
| } | ||
| } | ||
| } | ||
| ---- | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.