11[ ![ Chat at https://discord.gg/sgak4e5NKv ] ( https://img.shields.io/badge/chat-on%20Discord-%234cb697 )] ( https://discord.gg/sgak4e5NKv )
2- [ ![ Test all JDKs on all OSes ] ( https://github.com/javalin/javalin-graphql/actions/workflows/main .yml/badge.svg )] ( https://github.com/javalin/javalin-graphql/actions/workflows/main .yml )
2+ [ ![ Build ] ( https://github.com/javalin/javalin-graphql/actions/workflows/build .yml/badge.svg )] ( https://github.com/javalin/javalin-graphql/actions/workflows/build .yml )
33[ ![ License] ( https://img.shields.io/badge/License-Apache%202.0-blue.svg )] ( https://opensource.org/licenses/Apache-2.0 )
4- [ ![ Maven] ( https://img.shields.io/maven-central/v/io.javalin/javalin.svg )] ( https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22io.javalin%22%20AND%20a%3A%22javalin%22 )
54
65# About Javalin
76
1211
1312## Javalin GraphQL
1413
15- This plugin allows implementing the [ GraphQL specification] ( https://graphql.org )
16- with some easy steps.
14+ Serve a [ GraphQL] ( https://graphql.org ) schema, GraphiQL and subscriptions from a Javalin
15+ application. Schemas are generated from Kotlin (or Java) classes by
16+ [ graphql-kotlin] ( https://github.com/ExpediaGroup/graphql-kotlin ) .
17+
18+ | | |
19+ | ---| ---|
20+ | Javalin | 7.x |
21+ | graphql-kotlin | 10.x |
22+ | JVM | 17+ |
23+
24+ > ** Upgrading from 5.x?** The public API changed. See [ Migrating from 5.x] ( #migrating-from-5x ) .
1725
1826### Getting Started
1927
20- Add the dependencies :
28+ Add the dependency :
2129
2230<details >
23- <summary>Gradle setup for Javalin 5.x</summary>
24-
25- ``` groovy
26- repositories {
27- maven {
28- url "https://maven.reposilite.com/releases"
29- }
30- }
31+ <summary>Gradle</summary>
3132
33+ ``` kotlin
3234dependencies {
33- implementation "io.javalin.community.graphql:javalin-graphql:5 .0.1"
35+ implementation( " io.javalin.community.graphql:javalin-graphql:7 .0.0 " )
3436}
3537```
3638
3739</details >
3840
39-
4041<details >
41- <summary>Maven setup for Javalin 5.x </summary>
42+ <summary>Maven</summary>
4243
4344``` xml
44- <project >
45-
46- <repositories >
47- <repository >
48- <id >reposilite-repository</id >
49- <url >https://maven.reposilite.com/releases</url >
50- </repository >
51- </repositories >
52-
53- <dependencies >
54- <dependency >
55- <groupId >io.javalin.community.graphql</groupId >
56- <artifactId >javalin-graphql</artifactId >
57- <version >5.0.1</version >
58- </dependency >
59- </dependencies >
60- </project >
45+ <dependency >
46+ <groupId >io.javalin.community.graphql</groupId >
47+ <artifactId >javalin-graphql</artifactId >
48+ <version >7.0.0</version >
49+ </dependency >
6150```
6251
6352</details >
6453
54+ Javalin 7 does not bundle an object mapper, and this plugin uses the one your application
55+ configures. If you have not set one up, add ` jackson-databind ` (plus ` jackson-module-kotlin `
56+ if your schema classes are written in Kotlin).
57+
6558Register the plugin:
6659
6760``` kotlin
68- val app = Javalin .create {
69- val graphQLOption = GraphQLOptions (" /graphql" , ContextExample () )
70- .addPackage(" io.javalin.examples " )
71- .register(QueryExample (message ))
72- .register(MutationExample (message ))
73- .register(SubscriptionExample ())
74- .context()
75- it .registerPlugin(GraphQLPlugin (graphQLOption ))
61+ val app = Javalin .create { config ->
62+ val options = GraphQLOptions (" /graphql" )
63+ .addPackage(" com.example.schema " )
64+ .register(QueryExample ())
65+ .register(MutationExample ())
66+ .register(SubscriptionExample ())
67+
68+ config .registerPlugin(GraphQLPlugin (options ))
7669}
7770
78- app.start()
71+ app.start(7070 )
7972```
8073
81- The GraphQL is now available under the ` /graphql ` endpoint.
82-
83- ### Create Query
74+ GraphQL is now served under ` /graphql ` : ` GET ` returns GraphiQL, ` POST ` executes queries and
75+ mutations, and the WebSocket on the same path serves subscriptions.
8476
85- This section contains an overview of all the available to create queries.
77+ ### Queries
8678
8779``` kotlin
8880@GraphQLDescription(" Query Example" )
@@ -93,54 +85,152 @@ class QueryExample : QueryGraphql {
9385}
9486```
9587
96- After creating this class is necessary to register the class at the start of the plugin.
97-
98- ### Create Command
99-
100- This section contains an overview of all the available to create commands.
88+ ### Mutations
10189
10290``` kotlin
103- @GraphQLDescription(" Command Example" )
104- class CommandExample : CommandGraphql {
105- fun hello (): String = " Hello world"
106-
107- fun demoData (@GraphQLDescription(" awesome input" ) data : DemoData ): DemoData = data
91+ @GraphQLDescription(" Mutation Example" )
92+ class MutationExample (private var message : String ) : MutationGraphql {
93+ fun changeMessage (newMessage : String ): String {
94+ message = newMessage
95+ return message
96+ }
10897}
10998```
11099
111- After creating this class is necessary to register the class at the start of the plugin.
100+ ### Subscriptions
112101
113- ### Create Subscription
114-
115- This section contains an overview of all the available to create a subscription.
102+ A subscription resolver returns a kotlinx [ ` Flow ` ] ( https://kotlinlang.org/docs/flow.html ) :
116103
117104``` kotlin
118105@GraphQLDescription(" Subscription Example" )
119- class SubscriptionExample : SubscriptionGraphql {
120- fun counter (): Flux <Int > = Flux .interval(Duration .ofMillis(100 )).map { 1 }
106+ class SubscriptionExample : SubscriptionGraphql {
107+ fun counter (): Flow <Int > = flow {
108+ while (true ) {
109+ delay(100 )
110+ emit(1 )
111+ }
112+ }
121113}
122114```
123115
124- After creating this class is necessary to register the class at the start of the plugin .
116+ Every class has to be registered when the plugin is built .
125117
126- ### Pass context
118+ Subscriptions are served over the
119+ [ graphql-transport-ws] ( https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md )
120+ protocol, so a standard client such as [ graphql-ws] ( https://github.com/enisdenjo/graphql-ws )
121+ can talk to the endpoint directly. The connection is acknowledged with ` connection_init ` /
122+ ` connection_ack ` , each operation carries an id, and a subscription is cancelled by the
123+ client's ` complete ` message or when the socket closes.
127124
128- Sometimes it is necessary to pass the context in the method. You can create this context with this class.
125+ > Javalin exposes no API for WebSocket subprotocol negotiation, and the Jetty handshake
126+ > underneath echoes back whichever subprotocol the client requested first, without checking
127+ > it. A ` graphql-transport-ws ` client connects correctly; a client asking for a protocol this
128+ > plugin does not speak — the legacy ` graphql-ws ` subprotocol, say — is told yes and then
129+ > receives messages it cannot understand.
130+
131+ ### Context
132+
133+ Build a context by putting values into graphql-java's ` GraphQLContext ` , keyed by class:
129134
130135``` kotlin
131- class ContextExample {
132- val globalEnvironment = " globalEnvironment "
136+ data class MyContext ( val authorization : String? ) {
137+ val isValid = authorization != null
133138}
134- ```
135139
136- After creating this class is necessary to register the class at the start of the plugin.
140+ class MyContextFactory : GraphQLContextFactory <Context > {
141+ override suspend fun generateContext (request : Context ): GraphQLContext =
142+ mapOf (MyContext ::class to MyContext (request.header("Authorization ")))
143+ .toGraphQLContext()
144+ }
145+ ```
137146
138- Then is possible to access this context with this annotation @ GraphQLContext .
147+ Read it in a resolver through the ` DataFetchingEnvironment ` :
139148
140149```kotlin
141- class QueryExample () : QueryGraphql {
142- fun context (@GraphQLContext context : ContextExample ): ContextExample {
143- return context
144- }
150+ class QueryExample : QueryGraphql {
151+ fun isAuthorized (environment : DataFetchingEnvironment ): Boolean =
152+ environment.graphQlContext.get<MyContext >(MyContext ::class )?.isValid == true
145153}
146154```
155+
156+ Context factories are passed to the builder, one for HTTP and one for subscriptions:
157+
158+ ```kotlin
159+ val plugin = GraphQLPluginBuilder (" /graphql" , MyContextFactory (), MyWsContextFactory ())
160+ .add(" com.example.schema" )
161+ .register(QueryExample ())
162+ .build()
163+
164+ config.registerPlugin(plugin)
165+ ```
166+
167+ ### JPMS
168+
169+ The artifact declares `Automatic - Module - Name : io.javalin.community.graphql`, so a modular
170+ application can depend on it:
171+
172+ ```java
173+ requires io.javalin.community.graphql;
174+ ```
175+
176+ It does not ship a `module- info.java` yet. The graphql- kotlin artifacts declare neither a
177+ module descriptor nor an `Automatic - Module - Name `, so they resolve as automatic modules named
178+ after their file names; requiring those would freeze unstable names into the descriptor. See
179+ [#5 ](https: // github.com/javalin/javalin-graphql/issues/5).
180+
181+ ### Design decisions
182+
183+ The reasoning behind the 7.0 design is recorded as ADRs in [`docs/ adr`](.. / docs/ adr):
184+
185+ | | |
186+ |-- - |-- - |
187+ | [ADR - 001 ](.. / docs/ adr/ adr- 001 - context- model.md) | Context model follows graphql- java instead of a plugin type |
188+ | [ADR - 002 ](.. / docs/ adr/ adr- 002 - subscription- protocol.md) | Serve subscriptions over graphql- transport- ws |
189+ | [ADR - 003 ](.. / docs/ adr/ adr- 003 - json- mapping.md) | Parse GraphQL payloads without graphql- kotlin' s sealed types |
190+ | [ADR-004](../docs/adr/adr-004-jpms.md) | Declare an automatic module name instead of shipping module-info |
191+
192+ ### Migrating from 5.x
193+
194+ **Registering the plugin.** `config.plugins.register(...)` became `config.registerPlugin(...)`.
195+
196+ **Context is no longer a type of yours.** graphql-kotlin removed its `GraphQLContext` marker
197+ interface in favour of graphql-java' s map- like `GraphQLContext `.
198+
199+ ```diff
200+ - data class MyContext (val authorization : String? ) : GraphQLContext
201+ + data class MyContext (val authorization : String? )
202+
203+ - class MyContextFactory : GraphQLContextFactory <MyContext , Context > {
204+ - override suspend fun generateContext (request : Context ): MyContext =
205+ - MyContext (request.header(" Authorization" ))
206+ + class MyContextFactory : GraphQLContextFactory <Context > {
207+ + override suspend fun generateContext (request : Context ): GraphQLContext =
208+ + mapOf (MyContext ::class to MyContext (request.header("Authorization ")))
209+ + .toGraphQLContext()
210+ }
211+ ```
212+
213+ `GraphQLPluginBuilder ` lost its context type parameter as a result.
214+
215+ ** Context is no longer injected into resolvers.** A resolver parameter typed as your context
216+ class is now treated as a GraphQL argument. Take a `DataFetchingEnvironment ` instead:
217+
218+ ```diff
219+ - fun isAuthorized (context : MyContext ? ): Boolean = context?.isValid == true
220+ + fun isAuthorized (environment : DataFetchingEnvironment ): Boolean =
221+ + environment.graphQlContext.get<MyContext >(MyContext ::class )?.isValid == true
222+ ```
223+
224+ ** Subscriptions return `Flow `, not `Publisher `.** Reactor is no longer a dependency.
225+
226+ ```diff
227+ - fun counter (): Flux <Int > = Flux .interval(Duration .ofMillis(100 )).map { 1 }
228+ + fun counter (): Flow <Int > = flow { while (true ) { delay(100 ); emit(1 ) } }
229+ ```
230+
231+ ** Subscriptions speak graphql- transport- ws.** 5 .x used an ad- hoc exchange — send a query
232+ frame, receive bare result data — which matched no standard. A client now has to send
233+ `connection_init` and `subscribe` messages. Off - the- shelf GraphQL clients do this for you.
234+
235+ ** Removed : ** `GraphQLRun ` (use `GraphQLRequestHandler .executeSubscription`) and
236+ `JavalinDataLoaderRegistryFactory ` (use graphql- kotlin' s `KotlinDataLoaderRegistryFactory`).
0 commit comments