GraphQL server side streaming implementation

Viewed 1169

So, in the past I have been able to create a REST endpoint and give it the ability to stream out large files. I did this using Java and Jersey (REST library). I could create an endpoint similar to the code below - a client could hit it and the server would stream out large files without using a ton of memory, and the client would immediately start downloading the file.

@GET
@Path("/getFile")
public Response getFile(){
    InputStream file = getFileStreamFromDB();
    Response response = Response.ok().entity(stream);

    return response;
}

Right now I am trying to figure out how to apply a similar pattern using GraphQL. The use case is a little different - now I am pulling out large result sets from a DB instead of files. I have a GraphQL server built in Kotlin and KGraphQL. From the research I've done, it looks like I need to send an entire object as a response to a GraphQL call.

My use case is having data show on a React-based dashboard. We have a ton of data in some cases and it can take some time for a response to be sent to the client because the entire result set has to be sent. I'm hoping to stream the data into the dashboard so the user can immediately start seeing some data rather than wait 15 or 20 seconds for it to show.

The GraphQL resolver is fine if it's some kind of object or string:

query("returnString"){
    resolver { -> "this is a string"}
}

However, If I try returning an InputStream like the following:

query("returnStream"){
    resolver { -> ({
            val stream = ByteArrayInputStream("this is a string".toByteArray(Charsets.UTF_8))
            stream
        })
    }
}

I get the following exception when trying to start up my GraphQL server:

Caused by: com.apurebase.kgraphql.schema.SchemaException: Generic types are not supported by GraphQL, found () -> java.io.ByteArrayInputStream at com.apurebase.kgraphql.schema.structure2.SchemaCompilation.handlePossiblyWrappedType(SchemaCompilation.kt:147) at com.apurebase.kgraphql.schema.structure2.SchemaCompilation.handleOperation(SchemaCompilation.kt:131) at com.apurebase.kgraphql.schema.structure2.SchemaCompilation.handleQueries(SchemaCompilation.kt:112) at com.apurebase.kgraphql.schema.structure2.SchemaCompilation.perform(SchemaCompilation.kt:55) at com.apurebase.kgraphql.schema.dsl.SchemaBuilder.build(SchemaBuilder.kt:26) at com.apurebase.kgraphql.KGraphQL$Companion.schema(KGraphQL.kt:8)

We're using apollo on the frontend so I feel like we could leverage the stream directive on the client side - I'm just not sure how to design the GraphQL resolver to return a stream.

Is there a way I can stream a response to a GraphQL call? Or do I currently have to use REST for that?

1 Answers

The closest GraphQL equivalent of a streaming response is probably a subscription: https://graphql.org/blog/subscriptions-in-graphql-and-relay/ . Implementing what you're doing as a subscription to DB row "events" seems reasonable to me.

It looks like that's not currently implemented in KGraphQL: https://github.com/aPureBase/KGraphQL/issues/10

The graphql-kotlin project (https://github.com/ExpediaGroup/graphql-kotlin) however, has subscriptions implemented on top of a spring-boot base: https://expediagroup.github.io/graphql-kotlin/docs/server/subscriptions .

If you're used to jersey and would prefer to stick with that, we've put together a separate implementation leveraging graphql-kotlin's base libraries to implement subscriptions on a dropwizard (jersey, etc) base: https://github.com/trib3/leakycauldron/tree/master/graphql

Related