Skip to content
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

GraphQL subscriptions not working #6617

Open
mstephano opened this issue Apr 16, 2020 · 27 comments
Open

GraphQL subscriptions not working #6617

mstephano opened this issue Apr 16, 2020 · 27 comments
Labels
type:feature New feature or improvement of existing feature

Comments

@mstephano
Copy link

mstephano commented Apr 16, 2020

Issue Description

Based on the Parse-Server documentation at https://docs.parseplatform.org/graphql/guide/#graphql, I am wondering if GraphQL subscriptions work out-of-the-box. So far in my testing, it doesn't work.

Steps to reproduce

  1. Create a class named "todo" with a new column "name"
  2. In parse-dashboard, go to GraphQL Console
  3. Write down this subscription query and hit the execute button:
subscription MySubscription {
  todos {
    edges {
      node {
        id
        objectId
        name
        updatedAt
        createdAt
      }
    }
  }
}
  1. In another console tab, create a mutation as follow:
mutation MyMutation {
  createTodo (input: {fields: {name: "hello world"}}) {
    todo {
      id
      objectId
      name
      createdAt
      updatedAt
    }
  }
}
  1. You can verify your newly created record with this query in a new console tab:
query MyTodos {
  todos {
    edges {
      node {
        id
        objectId
        name
        createdAt
        updatedAt
      }
    }
  }
}

Expected Results

Once the subscription query is executed and running, every mutation result (related to the class in the subscription query) should be displayed on the result side of the console.

Actual Outcome

An error is displayed in the result side of the GraphQL console:

{
  "error": "Could not connect to websocket endpoint ws://localhost:1337/graphql. Please check if the endpoint url is correct."
}

If I uncomment the following 2 lines in my docker-compose file (see at the end), I am not getting the above error, but just a console running and not displaying any result after a mutation:

  • PARSE_SERVER_START_LIVE_QUERY_SERVER=true
  • PARSE_SERVER_LIVE_QUERY={"classNames":["todo"]}

Environment Setup

  • Server

    • parse-server version (Be specific! Don't say 'latest'.) : 4.2.0
    • Operating System: windows 10
    • Hardware: docker-compose file with following images:
      • Postgres:12
      • parseplatform/parse-server:4.2.0
      • parseplatform/parse-dashboard:2.0.5
    • Localhost or remote server? (AWS, Heroku, Azure, Digital Ocean, etc): local docker-desktop for windows v.2.2.0.5
  • Database

    • Postgres version: 12
    • Localhost or remote server? (AWS, mLab, ObjectRocket, Digital Ocean, etc): local docker-desktop for windows v.2.2.0.5

Logs/Trace

Docker logs of my parse-server container:

PS D:\git\parse-platform> docker logs 65a
allowClientClassCreation: true
appId: APPLICATION_ID
cacheMaxSize: 10000
cacheTTL: 5000
customPages: {}
databaseURI: postgres://postgres:example@postgres:5432/postgres
enableAnonymousUsers: true
expireInactiveSessions: true
graphQLPath: /graphql
host: 0.0.0.0
logsFolder: ./logs
masterKey: ***REDACTED***
masterKeyIps: []
maxUploadSize: 20mb
mountGraphQL: true
mountPath: /parse
mountPlayground: true
objectIdSize: 10
playgroundPath: /playground
port: 1337
protectedFields: {"_User":{"*":["email"]}}
revokeSessionOnPasswordReset: true
schemaCacheTTL: 5000
sessionLength: 31536000
allowCustomObjectId: false
collectionPrefix:
directAccess: false
enableExpressErrorHandler: false
enableSingleSchemaCache: false
preserveFileName: false
preventLoginWithUnverifiedEmail: false
scheduledPush: false
verifyUserEmails: false
jsonLogs: false
verbose: false
level: undefined
serverURL: http://localhost:1337/parse

[1] parse-server running on http://localhost:1337/parse
[1] GraphQL running on http://localhost:1337/graphql
[1] Playground running on http://localhost:1337/playground
PS D:\git\parse-platform>

My docker-compose.yml file:

version: "3"
services:
# PostgreSQL image
    postgres:
        image: postgres:12
        restart: always
        container_name: my-postgres
        volumes:
            - db_data:/var/lib/postgresql/data
        environment:
            - POSTGRES_PASSWORD=example

# parse-server application image
    parse-server:
        image: parseplatform/parse-server:4.2.0
        restart: always
        container_name: my-parse-server
        environment:
            - PARSE_SERVER_MASTER_KEY=MASTER_KEY
            - PARSE_SERVER_APPLICATION_ID=APPLICATION_ID
            - PARSE_SERVER_DATABASE_URI=postgres://postgres:example@postgres:5432/postgres
            - PARSE_SERVER_MOUNT_GRAPHQL=true
            - PARSE_SERVER_MOUNT_PLAYGROUND=true
            #- PARSE_SERVER_START_LIVE_QUERY_SERVER=true
            #- PARSE_SERVER_LIVE_QUERY={"classNames":["todo"]}
        depends_on:
            - postgres
        links:
            - postgres
        ports:
            - "1337:1337"
        volumes:
            - parseserverconfig:/parse-server/config
            - parseservercloud:/parse-server/cloud

# parse-dashboard application image
    parse-dashboard:
        image: parseplatform/parse-dashboard:2.0.5
        restart: always
        container_name: my-parse-dashboard
        ports:
            - "4040:4040"
        environment:
            - PARSE_DASHBOARD_ALLOW_INSECURE_HTTP=1
            - PARSE_DASHBOARD_CONFIG={"apps":[{"appId":"APPLICATION_ID","serverURL":"http://localhost:1337/parse","graphQLServerURL":"http://localhost:1337/graphql","masterKey":"MASTER_KEY","appName":"TestApp"}],"users":[{"user":"admin","pass":"admin"}]}
        depends_on:
            - parse-server

volumes:
# create named volume for postgreSQL /var/lib/postgresql/data so data persist when we shutdown/restart the container
    db_data:
  
# create following named volumes so we do not have many anonymous volumes that keeps growing when we run command: docker volume ls
    parseserverconfig:
    parseservercloud:
@davimacedo
Copy link
Member

davimacedo commented Apr 21, 2020

They are actually not implemented yet. I've started doing this but turned out never finishing. I will try to push something soon if nobody beats me and push it before.

@davimacedo davimacedo added the type:feature New feature or improvement of existing feature label Apr 21, 2020
@mstephano
Copy link
Author

Great! Thank you @davimacedo !

@mstephano
Copy link
Author

For those interested in a workaround while waiting for the GraphQL subscription, here is some code to refresh data on the page using useQuery from @apollo/react-hooks and the option pollInterval:

import React from 'react'
import { useQuery } from '@apollo/react-hooks'
import gql from 'graphql-tag'

const GET_TODOS = gql`
  query todos {
    todos {
      edges {
        node {
          objectId
          name
          createdAt
          updatedAt
        }
      }
    }
  }
`

const MyTodoPage = () => {
  const { loading, error, data } = useQuery(GET_TODOS, {
    pollInterval: 1000, //poll new data every second
  })

  if (loading) return <p>Loading ...</p>
  if (error) return <p>Error! ${error.message}</p>

  return (
    <div>
      <ul>
        {data.todos.edges.map(({ node: todo }) => ( // node is the object returned by GraphQL, todo is an alias used in the code here
          <li key={todo.objectId}>{todo.name}</li>
        ))}
      </ul>
    </div>
  )
}

export default MyTodoPage

My project uses Gatsby and @apollo/react-hooks to display live data. I spent a whole day looking for a solution to configure Gatsby to make it work with parse-server. If anyone interested in how I did it, let me know.

@TheTyrius
Copy link

They are actually not implemented yet. I've started doing this but turned out never finishing. I will try to push something soon if nobody beats me and push it before.

Thank you for your effort! Subscriptions would be great for my project. How difficult is it to implement into parse-server? Should I be holding my breath?

@facuparedes
Copy link

facuparedes commented Aug 30, 2020

+1 It could be useful on my project

@raajon
Copy link

raajon commented Oct 28, 2020

+1 If I could be helpful, please let me know.

@Moumouls
Copy link
Member

Moumouls commented Nov 8, 2020

@davimacedo do you have an idea about a simple implementation ?

After reading the LiveQueryServer and Apollo Server Sub docs i'm not sure how to implement the feature ?

@davimacedo
Copy link
Member

davimacedo commented Nov 8, 2020

Yes. The easiest way will be using the code we already have in place for the Live Query. I worked on a draft long time ago but turned out never finishing. I will organize what I have here and push to a new branch so we can continue the discussion from there. BTW, it would be good we agree in the API that we want for that. @Moumouls you are the guy for that :) Do you have any suggestion?

@Moumouls
Copy link
Member

Moumouls commented Nov 9, 2020

Yes no problem @davimacedo , i will try to make an API suggestion during the week. I already have an idea to get something easy to use and to understand.

@Moumouls
Copy link
Member

So @davimacedo here our spec for Sub API, what do you think about this one ?

@mstephano, @TheTyrius, @FNPCMDs, @raajon Do you have a feedback on this proposal ?

# Schema Parse GraphQL Subscription Proposal

# GraphQL enum support description: https://graphql.org/graphql-js/type/#graphqlenumtype
# So we can use it for better documentation
enum EventKindEnum {
    # When a Node is created and fulfill the where query
    create
    # When a Node existed and now fulfill the where query
    enter
    # When a Node is updated and fulfill the where query
    update
    # When a Node existed and now do not fulfill the where query
    leave
    # When a Node is deleted and fulfill the where query
    delete
    # When a Node match any of the events above
    all
}

# Subscribe to updates
type Subscription {

    # Listen event on Comment class
    comment(on: [EventKindEnum!], $where: CommentWhereInput){
        # Kind of event triggered
        event: EventKindEnum
        node: Comment
    }

    # Listen event on Post class
    post(on: [EventKindEnum!], $where: PostWhereInput){
        # Kind of event triggered
        event: EventKindEnum
        node: Post
    }
}

# Note: 
# We should use "on" with array of events to support multi event subscription, and avoid unnecessary complexity
# for developers 
# We need to investigate if we can use the same "WhereInput" as the main query,
# 2 years ago i used intensively LiveQueries and i remember that some complex queries were not supported
# otherwise a specific "SubscriptionWhereInput" will do the job to avoid developers to get unwanted behavior
# Usage example
subscription watchComments {
    comment(on: [create, update, delete], where: { content: { equalTo: "GraphQL Spec" } }){
        event
        node {
            id
            content
        }
    }
}

@raajon
Copy link

raajon commented Nov 15, 2020

@Moumouls looks like exacly what I need :-)
I have no expirence in developing nodejs servers but I will be glad to support you all in testing.

@davimacedo
Copy link
Member

It looks good to me. I liked the idea of having a single subscription for all event types.

@mstephano
Copy link
Author

Having EventKindEnum is very interesting for us to be able to have multiple subscriptions with one or multiple event kind.

I should also be able to serve you as a tester for this functionality. Thank again again @davimacedo @Moumouls for your hard work!

@mstephano
Copy link
Author

Following the current implementation of GraphQL getting multiple objects (https://docs.parseplatform.org/graphql/guide/#where), I presume the usage example from @Moumouls should be something like this? (comments with edges)

# Usage example
subscription watchComments {
    comments(on: [create, update, delete], where: { content: { equalTo: "GraphQL Spec" } }){
        event
        edges {
            node {
                id
                content
            }
        }
    }
}

@Moumouls
Copy link
Member

@mstephano here we do not have edges since events cannot occur simultaneously, each event will always return only one node (like Parse JS SDK live queries)

@mstephano
Copy link
Author

mstephano commented Nov 22, 2020

Following my example of react-hooks query with pollInterval, I would have hoped to only replace the word "useQuery" for "useSubscription" + remove pollInterval, and my page would refresh automatically with the updated list of objects:

import React from 'react'
import { useSubscription } from '@apollo/react-hooks'
import gql from 'graphql-tag'

const SUBSCRIPTION_TODOS = gql`
  subscription todos {
    todos {
      edges {
        node {
          objectId
          name
          createdAt
          updatedAt
        }
      }
    }
  }
`

const MyTodoPage = () => {
  const { loading, error, data } = useSubscription(SUBSCRIPTION_TODOS)

  if (loading) return <p>Loading ...</p>
  if (error) return <p>Error! ${error.message}</p>

  return (
    <div>
      <ul>
        {data.todos.edges.map(({ node: todo }) => ( // node is the object returned by GraphQL, todo is an alias used in the code here
          <li key={todo.objectId}>{todo.name}</li>
        ))}
      </ul>
    </div>
  )
}

export default MyTodoPage

Question: If there is no edges, it would mean I need to add a method like Live Query and keep an internal list of objects?

subscription.on('create', (todo) => {
  todos.push(todo) //add object to internal list
});

@Moumouls
Copy link
Member

TLDR: Parse can't return edges for subscription, but Apollo currently has a system designed to handle your use case: https://www.apollographql.com/docs/react/data/subscriptions/#subscribing-to-updates-for-a-query

@mstephano , yes parse server cannot return a list of object, so you need to have the query and the subscription.

Then you can combine the subscription result with an Apollo Cache Update: https://www.apollographql.com/docs/react/caching/cache-interaction/

Note: On update event, Apollo will do the magic without any code since apollo watch the subscription result and then try to update directly the cache if the object already exist in the cache.

@mstephano
Copy link
Author

Great! Thank you @Moumouls for your explanation. I can't wait to test the functionality on update event!

@amkarkhi
Copy link

amkarkhi commented Oct 8, 2021

Are there any updates on how to use the subscription on the client and the playground? is there any specific config I should do on the server?

errors:
Failed to construct 'WebSocket': The URL '/graphql' is invalid.
Could not connect to websocket endpoint ws://localhost:1337/graphql. Please check if the endpoint url is correct.

@Moumouls
Copy link
Member

Moumouls commented Oct 9, 2021

Hi @amkarkhi this feature is still "up for grabs", graphql subscriptions are not currently supported.

@tran-huy-phuc
Copy link

tran-huy-phuc commented Mar 24, 2022

Hi @Moumouls

Do we support graphql subscription now?

@Moumouls
Copy link
Member

Hi @thphuc

GraphQL subscription is not yet supported. A draft PR #7227 is still open.
The draft PR is open for a while, i don't know if @davimacedo will have sometime to finish the draft pr soon.

Currently the only alternative is to use a Parse SDK, but it will also add a new dependency in you project.

We are also open for contributions, i believe that's a nice feature and will help the community to design real time apps using GQL.

Note: If your app do not need super fast real time, you can also use polling feature of a graphql client like apollo.

@jsduffy

This comment was marked as off-topic.

@simonsasse

This comment was marked as off-topic.

@Vildnex

This comment was marked as duplicate.

@Moumouls
Copy link
Member

Moumouls commented Apr 1, 2023

Hi @Vildnex unfortunately not, do you want to submit a PR ? 🙂

@andreafalzetti
Copy link

This would be a great addition

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type:feature New feature or improvement of existing feature
Projects
None yet
Development

No branches or pull requests