Skip to content

Commit 689ded1

Browse files
authored
Add example for integrating with new Spring GraphQL (#264)
1 parent 39caa21 commit 689ded1

File tree

12 files changed

+487
-0
lines changed

12 files changed

+487
-0
lines changed

examples/graphql-spring-boot/pom.xml

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
<parent>
6+
<groupId>org.springframework.boot</groupId>
7+
<artifactId>spring-boot-starter-parent</artifactId>
8+
<version>2.7.0-M1</version>
9+
<relativePath/> <!-- lookup parent from repository -->
10+
</parent>
11+
12+
<groupId>org.neo4j.graphql.examples</groupId>
13+
<artifactId>graphql-spring-boot</artifactId>
14+
<version>1.0-SNAPSHOT</version>
15+
16+
<name>Example - graphql-spring-boot</name>
17+
<description>Example for using neo4j-graphql-java with Spring Boot</description>
18+
19+
<properties>
20+
<java.version>11</java.version>
21+
<testcontainers.version>1.16.2</testcontainers.version>
22+
</properties>
23+
24+
<dependencies>
25+
<!-- spring dependencies -->
26+
<dependency>
27+
<groupId>org.springframework.boot</groupId>
28+
<artifactId>spring-boot-configuration-processor</artifactId>
29+
<optional>true</optional>
30+
</dependency>
31+
<dependency>
32+
<groupId>org.springframework.boot</groupId>
33+
<artifactId>spring-boot-starter-graphql</artifactId>
34+
</dependency>
35+
<dependency>
36+
<groupId>org.springframework.boot</groupId>
37+
<artifactId>spring-boot-starter-web</artifactId>
38+
</dependency>
39+
40+
<!-- neo4j driver + the neo4j-graphql-java library -->
41+
<dependency>
42+
<groupId>org.neo4j.driver</groupId>
43+
<artifactId>neo4j-java-driver-spring-boot-starter</artifactId>
44+
<version>4.2.7.0</version>
45+
</dependency>
46+
<dependency>
47+
<groupId>org.neo4j</groupId>
48+
<artifactId>neo4j-graphql-java</artifactId>
49+
<version>1.5.1-SNAPSHOT</version>
50+
</dependency>
51+
52+
53+
<!-- Test dependencies -->
54+
<dependency>
55+
<groupId>org.springframework.boot</groupId>
56+
<artifactId>spring-boot-starter-test</artifactId>
57+
<scope>test</scope>
58+
</dependency>
59+
<dependency>
60+
<groupId>org.springframework</groupId>
61+
<artifactId>spring-webflux</artifactId>
62+
<scope>test</scope>
63+
</dependency>
64+
<dependency>
65+
<groupId>org.springframework.graphql</groupId>
66+
<artifactId>spring-graphql-test</artifactId>
67+
<scope>test</scope>
68+
</dependency>
69+
<dependency>
70+
<groupId>org.testcontainers</groupId>
71+
<artifactId>neo4j</artifactId>
72+
<version>${testcontainers.version}</version>
73+
<scope>test</scope>
74+
</dependency>
75+
<dependency>
76+
<groupId>org.testcontainers</groupId>
77+
<artifactId>junit-jupiter</artifactId>
78+
<version>${testcontainers.version}</version>
79+
<scope>test</scope>
80+
</dependency>
81+
</dependencies>
82+
83+
84+
<build>
85+
<plugins>
86+
<plugin>
87+
<groupId>org.springframework.boot</groupId>
88+
<artifactId>spring-boot-maven-plugin</artifactId>
89+
</plugin>
90+
</plugins>
91+
</build>
92+
93+
<repositories>
94+
<!-- TODO remove after release of Spring Boot 2.7 -->
95+
<repository>
96+
<id>spring-milestones</id>
97+
<name>Spring Milestones</name>
98+
<url>https://repo.spring.io/milestone</url>
99+
<snapshots>
100+
<enabled>false</enabled>
101+
</snapshots>
102+
</repository>
103+
</repositories>
104+
<pluginRepositories>
105+
<pluginRepository>
106+
<id>spring-milestones</id>
107+
<name>Spring Milestones</name>
108+
<url>https://repo.spring.io/milestone</url>
109+
<snapshots>
110+
<enabled>false</enabled>
111+
</snapshots>
112+
</pluginRepository>
113+
</pluginRepositories>
114+
115+
</project>
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
= Example: Integration of Neo4j-GraphQL-Java into a Spring Boot application using Springs GraphQL
2+
3+
== Overview
4+
5+
This example uses the https://docs.spring.io/spring-graphql/docs/1.0.0-SNAPSHOT/reference/html/[Springs new GraphQL library]
6+
7+
In the link:src/main/java/org/neo4j/graphql/examples/graphqlspringboot/config/Neo4jConfiguration.java[Neo4jConfiguration]
8+
a DataFetchingInterceptor is created, which will be bound to all fields augmented by the neo4j-graphql-library.
9+
Its purpose is the execution of the cypher query and the transformation of the query result.
10+
11+
In the link:src/main/java/org/neo4j/graphql/examples/graphqlspringboot/config/GraphQLConfiguration.java[GraphQLConfiguration]
12+
the type definitions of link:src/main/resources/neo4j.graphql[schema] are loaded and augmented.
13+
14+
In this example some fields of the enhanced type (neo4j) are extended with
15+
link:src/main/java/org/neo4j/graphql/examples/graphqlspringboot/datafetcher/AdditionalDataFetcher.java[custom data fetcher] whose link:src/main/resources/graphql/schema.graphqls[schema is separately defined].
16+
17+
With This in place you can
18+
19+
== Run the example
20+
21+
1. link:src/main/resources/application.yaml[configure your neo4j db] or use a public one
22+
2. run the link:src/main/java/org/neo4j/graphql/examples/graphqlspringboot/GraphqlSpringBootApplication.java[spring boot application]
23+
3. open http://localhost:8080/graphiql to run some graphql queries e.g. try:
24+
25+
```graphql
26+
query{
27+
other
28+
movies (options: {limit: 3}){
29+
title
30+
bar
31+
javaData {
32+
name
33+
}
34+
}
35+
}
36+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package org.neo4j.graphql.examples.graphqlspringboot;
2+
3+
import org.springframework.boot.SpringApplication;
4+
import org.springframework.boot.autoconfigure.SpringBootApplication;
5+
6+
@SpringBootApplication
7+
public class GraphqlSpringBootApplication {
8+
9+
public static void main(String[] args) {
10+
SpringApplication.run(GraphqlSpringBootApplication.class, args);
11+
}
12+
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package org.neo4j.graphql.examples.graphqlspringboot.config;
2+
3+
import graphql.schema.GraphQLCodeRegistry;
4+
import graphql.schema.idl.SchemaGenerator;
5+
import graphql.schema.idl.SchemaParser;
6+
import graphql.schema.idl.TypeDefinitionRegistry;
7+
import org.neo4j.graphql.DataFetchingInterceptor;
8+
import org.neo4j.graphql.SchemaBuilder;
9+
import org.neo4j.graphql.SchemaConfig;
10+
import org.springframework.beans.factory.annotation.Autowired;
11+
import org.springframework.beans.factory.annotation.Value;
12+
import org.springframework.boot.autoconfigure.graphql.GraphQlSourceBuilderCustomizer;
13+
import org.springframework.context.annotation.Bean;
14+
import org.springframework.context.annotation.Configuration;
15+
import org.springframework.core.io.Resource;
16+
17+
import java.io.IOException;
18+
import java.nio.charset.StandardCharsets;
19+
import java.util.List;
20+
21+
@Configuration
22+
public class GraphQLConfiguration {
23+
24+
@Value("classpath:neo4j.graphql")
25+
public Resource graphQl;
26+
27+
@Autowired(required = false)
28+
public DataFetchingInterceptor dataFetchingInterceptor;
29+
30+
@Bean
31+
public GraphQlSourceBuilderCustomizer graphQlSourceBuilderCustomizer() throws IOException {
32+
33+
String schema = new String(graphQl.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
34+
35+
TypeDefinitionRegistry neo4jTypeDefinitionRegistry = new SchemaParser().parse(schema);
36+
SchemaBuilder schemaBuilder = new SchemaBuilder(neo4jTypeDefinitionRegistry, new SchemaConfig(
37+
new SchemaConfig.CRUDConfig(),
38+
new SchemaConfig.CRUDConfig(false, List.of()),
39+
false, true, SchemaConfig.InputStyle.INPUT_TYPE, true, false));
40+
schemaBuilder.augmentTypes();
41+
42+
return builder -> builder
43+
.configureRuntimeWiring(runtimeWiringBuilder -> {
44+
schemaBuilder.registerTypeNameResolver(runtimeWiringBuilder);
45+
schemaBuilder.registerScalars(runtimeWiringBuilder);
46+
GraphQLCodeRegistry.Builder codeRegistryBuilder = GraphQLCodeRegistry.newCodeRegistry();
47+
schemaBuilder.registerDataFetcher(codeRegistryBuilder, dataFetchingInterceptor);
48+
runtimeWiringBuilder.codeRegistry(codeRegistryBuilder);
49+
})
50+
.schemaFactory((typeDefinitionRegistry, runtimeWiring) -> {
51+
typeDefinitionRegistry.merge(neo4jTypeDefinitionRegistry);
52+
return new SchemaGenerator().makeExecutableSchema(typeDefinitionRegistry, runtimeWiring);
53+
});
54+
}
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package org.neo4j.graphql.examples.graphqlspringboot.config;
2+
3+
import graphql.schema.GraphQLList;
4+
import graphql.schema.GraphQLNonNull;
5+
import graphql.schema.GraphQLType;
6+
import org.neo4j.driver.Driver;
7+
import org.neo4j.driver.Result;
8+
import org.neo4j.driver.SessionConfig;
9+
import org.neo4j.graphql.Cypher;
10+
import org.neo4j.graphql.DataFetchingInterceptor;
11+
import org.springframework.beans.factory.annotation.Value;
12+
import org.springframework.context.annotation.Bean;
13+
import org.springframework.context.annotation.Configuration;
14+
15+
import java.math.BigDecimal;
16+
import java.math.BigInteger;
17+
import java.util.Collections;
18+
import java.util.HashMap;
19+
import java.util.Map;
20+
import java.util.stream.Collectors;
21+
22+
/**
23+
* Configuration of the DataFetchingInterceptor
24+
*/
25+
@Configuration
26+
public class Neo4jConfiguration {
27+
28+
/**
29+
* This interceptor is bound to all the graphql fields generated by the neo4j-graphql-library.
30+
* Its purpose is the execution of the cypher query and the transformation of the query result.
31+
*/
32+
@Bean
33+
public DataFetchingInterceptor dataFetchingInterceptor(
34+
Driver driver,
35+
@Value("${database}") String database) {
36+
return (env, delegate) -> {
37+
Cypher cypher = delegate.get(env);
38+
return driver.session(SessionConfig.forDatabase(database)).writeTransaction(tx -> {
39+
Map<String, Object> boltParams = new HashMap<>(cypher.getParams());
40+
boltParams.replaceAll((key, value) -> toBoltValue(value));
41+
42+
Result result = tx.run(cypher.getQuery(), boltParams);
43+
if (isListType(cypher.getType())) {
44+
return result.list()
45+
.stream()
46+
.map(record -> record.get(cypher.getVariable()).asObject())
47+
.collect(Collectors.toList());
48+
} else {
49+
return result.list()
50+
.stream()
51+
.map(record -> record.get(cypher.getVariable()).asObject())
52+
.collect(Collectors.toList())
53+
.stream().findFirst()
54+
.orElse(Collections.emptyMap());
55+
}
56+
});
57+
};
58+
}
59+
60+
private Object toBoltValue(Object value) {
61+
if (value instanceof BigInteger) {
62+
return ((BigInteger) value).longValueExact();
63+
}
64+
if (value instanceof BigDecimal) {
65+
return ((BigDecimal) value).doubleValue();
66+
}
67+
return value;
68+
}
69+
70+
private boolean isListType(GraphQLType type) {
71+
if (type instanceof GraphQLList) {
72+
return true;
73+
}
74+
return type instanceof GraphQLNonNull
75+
&& this.isListType(((GraphQLNonNull) type).getWrappedType());
76+
}
77+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package org.neo4j.graphql.examples.graphqlspringboot.datafetcher;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import graphql.schema.DataFetchingEnvironment;
5+
import org.springframework.graphql.data.method.annotation.QueryMapping;
6+
import org.springframework.graphql.data.method.annotation.SchemaMapping;
7+
import org.springframework.stereotype.Controller;
8+
9+
import java.util.Collections;
10+
import java.util.List;
11+
import java.util.Map;
12+
13+
@Controller
14+
class AdditionalDataFetcher {
15+
16+
@SchemaMapping(typeName = "Movie", field = "bar")
17+
public String bar() {
18+
return "foo";
19+
}
20+
21+
@SchemaMapping(typeName = "Movie", field = "javaData")
22+
public List<JavaData> javaData(DataFetchingEnvironment env) {
23+
//noinspection unchecked
24+
Object title = ((Map<String, Object>) env.getSource()).get("title");
25+
return Collections.singletonList(new JavaData("test " + title));
26+
}
27+
28+
@QueryMapping
29+
public String other() {
30+
return "other";
31+
}
32+
33+
public static class JavaData {
34+
@JsonProperty("name")
35+
public String name;
36+
37+
public JavaData(String name) {
38+
this.name = name;
39+
}
40+
}
41+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
org :
2+
neo4j :
3+
driver :
4+
uri : bolt://demo.neo4jlabs.com:7687
5+
authentication :
6+
username : movies
7+
password : movies
8+
config :
9+
encrypted : true
10+
database : movies
11+
spring :
12+
graphql :
13+
schema :
14+
printer :
15+
enabled : true
16+
graphiql :
17+
enabled : true
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
extend type Movie {
2+
bar: String @ignore
3+
javaData: [JavaData!] @ignore
4+
}
5+
6+
type JavaData {
7+
name: String
8+
}
9+
10+
extend type Query {
11+
other: String
12+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
type Movie {
2+
title: String
3+
}

0 commit comments

Comments
 (0)