spring-bootgraphql-java

How to set response headers from a resolver in graphql-java


I'm using graphql-java 20.0 (in spring-boot-starter-graphql in Kotlin) and want to add custom headers to my resolver's responses. Currently, my resolvers return just the entity (of type MyEntity) that should be included in the graphql response - which works:

@QueryMapping
fun myResolver(
    @Argument arg1: String,
    @Argument arg2: MyInput,
): MyEntity = service.getMyEntity(arg1, arg2)

I tried changing this to a ResponseEntity as recommended in this SO answer but that's apparently not handled by graphql-java and results in my responses' data being null:

/* this does not work, since graphql-java doe not map the ResponseEntity's body
   and results in a graphql response with null in the data object */
@QueryMapping
fun myResolver(
    @Argument arg1: String,
    @Argument arg2: MyInput,
): ResponseEntity<MyEntity> = ResponseEntity.ok().run {
        this.header("X-My-Header", "whatever")
    }.body(service.getMyEntity(arg1, arg2))

I could not, however, find any alternative that allows me to set custom headers alongside my response. On StackOverflow, I only found answers for Laravel and Astro. On the graphql-spring-boot repo there's a similar question left unanswered since almost two years.

Does anyone know of a way to set custom headers in my graphql resolvers? I need to do this because my headers need to be different based on some request properties.


Solution

  • After upgrading dependencies my other answer no longer works. Thankfully, I found a cleaner solution:

    Preconditions

    This answer works with the following dependency tree (GraphQl-relevant parts):

    Outline:

    based on Spring GraphQL / Server Transports / Interception

    Example:

    The resolver gets a graphql.GraphQLContext (automatically injected):

    @QueryMapping
    fun myResolver(
        @Argument arg1: String,
        @Argument arg2: MyInput,
        context: GraphQLContext,
    ): MyEntity = service.getMyEntity(arg1, arg2).also {
        context.put("my-value", "whatever inferred from ${it}")
    }
    

    And a org.springframework.graphql.server.WebGraphQlInterceptor retrieves the value from GraphQLContext and modifies the response (headers):

    import org.springframework.graphql.server.WebGraphQlInterceptor
    import org.springframework.graphql.server.WebGraphQlRequest
    import org.springframework.graphql.server.WebGraphQlResponse
    import org.springframework.stereotype.Component
    import reactor.core.publisher.Mono
    
    @Component
    class GraphQLHeaderInterceptor : WebGraphQlInterceptor {
        override fun intercept(request: WebGraphQlRequest, chain: WebGraphQlInterceptor.Chain): Mono<WebGraphQlResponse> {
            return chain.next(request).doOnNext { response: WebGraphQlResponse ->
                val value = response.executionInput.graphQLContext.get<String>("my-value")
                response.responseHeaders.set("X-My-Header", value ?: "default value")
            }
        }
    }