-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbuilding-a-graphql-service.html
192 lines (172 loc) · 22.9 KB
/
building-a-graphql-service.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
<!DOCTYPE html><html lang="de-ch"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Building a GraphQL service - Finecloud</title><meta name="description" content="Let's build a GraphQL Spring Application that will accept GraphQL requests at http://localhost:8080/graphql. First let's navigate to https://start.spring.io. This service pulls in all the dependencies you need for an application and does most of the setup for you. GraphQL is a query language to retrieve…"><meta name="generator" content="Publii Open-Source CMS for Static Site"><link rel="stylesheet" href="https://www.finecloud.ch/media/plugins/syntaxHighlighter/prism-black.css"><link rel="canonical" href="https://www.finecloud.ch/building-a-graphql-service.html"><link rel="alternate" type="application/atom+xml" href="https://www.finecloud.ch/feed.xml"><link rel="alternate" type="application/json" href="https://www.finecloud.ch/feed.json"><meta property="og:title" content="Building a GraphQL service"><meta property="og:site_name" content="Finecloud"><meta property="og:description" content="Let's build a GraphQL Spring Application that will accept GraphQL requests at http://localhost:8080/graphql. First let's navigate to https://start.spring.io. This service pulls in all the dependencies you need for an application and does most of the setup for you. GraphQL is a query language to retrieve…"><meta property="og:url" content="https://www.finecloud.ch/building-a-graphql-service.html"><meta property="og:type" content="article"><link rel="shortcut icon" href="https://www.finecloud.ch/media/website/finecloud.png" type="image/png"><link rel="stylesheet" href="https://www.finecloud.ch/assets/css/style.css?v=39da73365516a098a9b73b721fc970e2"><script type="application/ld+json">{"@context":"http://schema.org","@type":"Article","mainEntityOfPage":{"@type":"WebPage","@id":"https://www.finecloud.ch/building-a-graphql-service.html"},"headline":"Building a GraphQL service","datePublished":"2024-01-22T21:19","dateModified":"2024-01-24T20:15","description":"Let's build a GraphQL Spring Application that will accept GraphQL requests at http://localhost:8080/graphql. First let's navigate to https://start.spring.io. This service pulls in all the dependencies you need for an application and does most of the setup for you. GraphQL is a query language to retrieve…","author":{"@type":"Person","name":"Finecloud","url":"https://www.finecloud.ch/authors/finecloud/"},"publisher":{"@type":"Organization","name":"Finecloud"}}</script><meta name="google-site-verification" content="seFY9U12uiEq5U3_MyZiX6XWzk0AVFl9zITr2ZKsytY"></head><body><div class="site-container"><header class="top" id="js-header"><a class="logo" href="https://www.finecloud.ch/">Finecloud</a><nav class="navbar js-navbar"><button class="navbar__toggle js-toggle" aria-label="Menu" aria-haspopup="true" aria-expanded="false"><span class="navbar__toggle-box"><span class="navbar__toggle-inner">Menu</span></span></button><ul class="navbar__menu"><li><a href="https://www.finecloud.ch/" target="_self">Blog</a></li><li><a href="https://www.finecloud.ch/tags/" target="_self">Tags</a></li></ul></nav><div class="search"><div class="search__overlay js-search-overlay"><div class="search__overlay-inner"><form action="https://www.finecloud.ch/search.html" class="search__form"><input class="search__input js-search-input" type="search" name="q" placeholder="search..." aria-label="search..." autofocus="autofocus"></form><button class="search__close js-search-close" aria-label="Close">Close</button></div></div><button class="search__btn js-search-btn" aria-label="Search"><svg role="presentation" focusable="false"><use xlink:href="https://www.finecloud.ch/assets/svg/svg-map.svg#search"/></svg></button></div></header><main><article class="post"><div class="hero"><figure class="hero__image hero__image--overlay"><img src="https://www.finecloud.ch/media/website/download.jpg" srcset="https://www.finecloud.ch/media/website/responsive/download-xs.jpg 300w, https://www.finecloud.ch/media/website/responsive/download-sm.jpg 480w, https://www.finecloud.ch/media/website/responsive/download-md.jpg 768w, https://www.finecloud.ch/media/website/responsive/download-lg.jpg 1024w, https://www.finecloud.ch/media/website/responsive/download-xl.jpg 1360w, https://www.finecloud.ch/media/website/responsive/download-2xl.jpg 1600w" sizes="100vw" loading="eager" alt=""></figure><header class="hero__content"><div class="wrapper"><div class="post__meta"><time datetime="2024-01-22T21:19">Januar 22, 2024</time></div><h1>Building a GraphQL service</h1></div></header></div><div class="wrapper post__entry"><div class="post__toc"><h3>Table of contents</h3><ul><li><a href="#preparation">Preparation</a></li><li><a href="#a-very-short-introduction-to-graphql">A very short introduction to GraphQL</a></li><li><a href="#our-example-api-getting-book-details">Our example API: getting book details</a></li><li><a href="#schema">Schema</a></li><li><a href="#source-of-the-data">Source of the data</a></li><li><a href="#create-the-book-and-author-data-sources">Create the Book and Author data sources</a></li><li><a href="#adding-code-to-fetch-data">Adding code to fetch data</a></li><li><a href="#running-our-first-query">Running our first query</a><ul><li><a href="#enable-the-graphiql-playground">Enable the GraphiQL Playground</a></li><li><a href="#boot-the-application">Boot the application</a></li><li><a href="#run-the-query">Run the query</a></li></ul></li><li><a href="#testing">Testing</a></li></ul></div><p>Let's build a GraphQL Spring Application that will accept GraphQL requests at http://localhost:8080/graphql.</p><h2 id="preparation">Preparation</h2><p>First let's navigate to https://start.spring.io. This service pulls in all the dependencies you need for an application and does most of the setup for you.<br></p><ul><li>Choose Maven and chose Java.Click </li><li>Dependencies and select Spring for GraphQL and Spring Web.</li><li><span style="color: var(--text-primary-color); font-family: var(--editor-font-family); font-size: 1em; font-weight: var(--font-weight-normal);">Click Generate.</span><br></li><li><span style="color: var(--text-primary-color); font-family: var(--editor-font-family); font-size: 1em; font-weight: var(--font-weight-normal);">Download the resulting ZIP file, which is an archive of a GraphQL application that is configured with your choices.</span><br></li><li>Unzip and open the Folder with your favorite IDE (IntelliJ recommended)</li></ul><h2 id="a-very-short-introduction-to-graphql">A very short introduction to GraphQL</h2><p>GraphQL is a query language to retrieve data from a server. It is an alternative to REST, SOAP, or gRPC. In the next Part we will query the details for a specific book from an online store backend.<br><br>This is an example request you can send to a GraphQL server to retrieve book details:</p><pre class="language-graphql"><code>query bookDetails {
bookById(id: "book-1") {
id
name
pageCount
author {
firstName
lastName
}
}
}</code></pre><p>This GraphQL request says:</p><ul><li>perform a query for a book with id "book-1"</li><li><span style="color: var(--text-primary-color); font-family: var(--editor-font-family); font-size: 1em; font-weight: var(--font-weight-normal);">for the book, return id, name, pageCount and author</span><br></li><li><span style="color: var(--text-primary-color); font-family: var(--editor-font-family); font-size: 1em; font-weight: var(--font-weight-normal);">for the author, return firstName and lastName</span><br></li></ul><p>The response is in JSON. For example:<br></p><pre class="language-json"><code>{
"bookById": {
"id":"book-1",
"name":"Effective Java",
"pageCount":416,
"author": {
"firstName":"Joshua",
"lastName":"Bloch"
}
}
}</code></pre><p>An important feature of GraphQL is that it defines a schema language, and that it is statically typed. The server knows exactly what types of objects requests can query and what fields those objects contain. Furthermore, clients can introspect the server to ask for schema details.</p><p class="msg msg--info">The word schema in this Post refers to a "GraphQL Schema", which is not related to other schemas like "JSON Schema" or "Database Schema".</p><p>The schema for the above query is:</p><pre class="language-graphql"><code>type Query {
bookById(id: ID): Book
}
type Book {
id: ID
name: String
pageCount: Int
author: Author
}
type Author {
id: ID
firstName: String
lastName: String
}</code></pre><p>This Post will focus on how to implement a GraphQL server with this schema in Java.<br><br>We’ve barely scratched the surface of what’s possible with GraphQL. Further information can be found on the official GraphQL page.</p><h2 id="our-example-api-getting-book-details">Our example API: getting book details</h2><p>These are the main steps to create a server with Spring for GraphQL:</p><ol><li>Define a GraphQL schema</li><li>Implement the logic to fetch the actual data for a query<br></li></ol><p>Our example app will be a simple API to get details for a specific book. It is not intended to be a comprehensive API.</p><h2 id="schema">Schema</h2><p>In your Spring for GraphQL application prepared earlier, add a new file <em>schema.graphqls</em> to the <em>src/main/resources/graphql</em> folder with the following content:</p><pre class="language-graphql"><code>type Query {
bookById(id: ID): Book
}
type Book {
id: ID
name: String
pageCount: Int
author: Author
}
type Author {
id: ID
firstName: String
lastName: String
}</code></pre><p>Every GraphQL schema has a top-level <em>Query</em> type, and the fields under it are the query operations exposed by the application. Here the schema defines one query called <em>bookById</em> that returns the details of a specific book.<br><br>It also defines the types <em>Book</em> with fields<em> id, name, pageCount</em> and <em>author</em>, and the type <em>Author</em> with fields <em>firstName</em> and <em>lastName</em>.<br></p><p class="msg msg--info">The Domain Specific Language used above to describe a schema is called the Schema Definition Language or SDL. For more details, see the GraphQL documentation.</p><h2 id="source-of-the-data">Source of the data</h2><p>A key strength of GraphQL is that data can be sourced from anywhere. Data can come from a database, an external service, or a static in-memory list.<br><br>To simplify the demo here, book and author data will come from static lists inside their respective classes.</p><h2 id="create-the-book-and-author-data-sources">Create the Book and Author data sources</h2><p>Let’s now create the <em>Book</em> and <em>Author</em> classes in the main application package, right next to <em>GraphQlServerApplication</em>. Use the following as their content:</p><pre class="language-java"><code>package com.example.graphqlserver;
import java.util.Arrays;
import java.util.List;
public record Book (String id, String name, int pageCount, String authorId) {
private static List<Book> books = Arrays.asList(
new Book("book-1", "Effective Java", 416, "author-1"),
new Book("book-2", "Hitchhiker's Guide to the Galaxy", 208, "author-2"),
new Book("book-3", "Down Under", 436, "author-3")
);
public static Book getById(String id) {
return books.stream()
.filter(book -> book.id().equals(id))
.findFirst()
.orElse(null);
}
}</code></pre><pre class="language-java"><code>package com.example.graphqlserver;
import java.util.Arrays;
import java.util.List;
public record Author (String id, String firstName, String lastName) {
private static List<Author> authors = Arrays.asList(
new Author("author-1", "Joshua", "Bloch"),
new Author("author-2", "Douglas", "Adams"),
new Author("author-3", "Bill", "Bryson")
);
public static Author getById(String id) {
return authors.stream()
.filter(author -> author.id().equals(id))
.findFirst()
.orElse(null);
}
}</code></pre><h2 id="adding-code-to-fetch-data">Adding code to fetch data</h2><p>Spring for GraphQL provides an annotation-based programming model. With controller annotated methods, we can declare how to fetch the data for specific GraphQL fields.<br><br>Add the following to <em>BookController.java</em> in the main application package, next to Book and Author:</p><pre class="language-java"><code>package com.example.graphqlserver;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.graphql.data.method.annotation.SchemaMapping;
import org.springframework.stereotype.Controller;
@Controller
public class BookController {
@QueryMapping
public Book bookById(@Argument String id) {
return Book.getById(id);
}
@SchemaMapping
public Author author(Book book) {
return Author.getById(book.authorId());
}
}</code></pre><p>By defining a method named <em>bookById</em> annotated with <em>@QuerMapping</em>, this controller declares how to fetch a <em>Book</em> as defined under the Query type. The query field is determined from the method name, but can also be declared on the annotation itself.</p><p class="msg msg--info">Spring for GraphQL uses RuntimeWiring.Builder that registers each such controller method as a GraphQL Java graphql.schema.DataFetcher. A DataFetcher provides the logic to fetch the data for a query or for any schema field. The Spring Boot starter for GraphQL has auto-configurations that automates this registration.</p><p>In the GraphQL Java engine, <em>DataFetchingEnvironment</em> provides access to a map of field-specific argument values. Use the <em>@Argument</em> annotation to have an argument bound to a target object and injected into the controller method. By default, the method parameter name is used to look up the argument, but can also be specified on the annotation itself.<br><br>This <em>bookById</em> method defines how to get a specific <em>Book</em>, but does not take care of fetching the related <em>Author</em>. If the request asks for the author information, GraphQL Java will need to fetch this field.<br><br>The <em>@SchemaMapping</em> annotation maps a handler method to a field in the GraphQL schema and declares it to be the <em>DataFetcher</em> for that field. The field name defaults to the method name, and the type name defaults to the simple class name of the source/parent object injected into the method. In this example, the field defaults to <em>author</em> and the type defaults to <em>Book</em>.<br><br>For more, see the <a href="https://docs.spring.io/spring-graphql/reference/controllers.html" target="_blank" rel="nofollow noopener">documentation for the Spring for GraphQL annotated controller feature</a>.<br><br>Now let’s run our first query.</p><h2 id="running-our-first-query">Running our first query</h2><h3 id="enable-the-graphiql-playground">Enable the GraphiQL Playground</h3><p>GraphiQL is a useful visual interface for writing and executing queries, and much more. Enable GraphiQL by adding this config to the <em>application.properties</em> file.</p><pre class="language-java"><code>spring.graphql.graphiql.enabled=true</code></pre><h3 id="boot-the-application">Boot the application</h3><p>Start your Spring application. Navigate to http://localhost:8080/graphiql.</p><h3 id="run-the-query">Run the query</h3><p>Type in the query and click the play button at the top of the window.<br></p><pre class="language-graphql"><code>query bookDetails {
bookById(id: "book-1") {
id
name
pageCount
author {
id
firstName
lastName
}
}
}</code></pre><p>You should see a response like this:</p><pre class="language-json"><code>{
"data": {
"bookById": {
"id": "book-1",
"name": "Effective Java",
"pageCount": 416,
"author": {
"id": "author-1",
"firstName": "Joshua",
"lastName": "Bloch"
}
}
}
}</code></pre><p>Congratulations, you have built a GraphQL service and executed your first query! With the help of Spring for GraphQL, you were able to achieve this with only a few lines of code.</p><h2 id="testing">Testing</h2><p>Spring for GraphQL provides helpers for GraphQL testing in the <em>spring-graphql-test</em> artifact. We have already included this artifact as part of the project generated by Spring Initializr.<br><br>Thoroughly testing a GraphQL service requires tests with different scopes. In this tutorial, we will write a <em>@GraphQlTest</em> slice test, which focuses on a single controller. There are other helpers to assist with full end-to-end integration tests and focused server side tests. For the full details, see the Spring for GraphQL Testing documentation and Auto-configured Spring for GraphQL tests in the Spring Boot documentation.<br><br>Let’s write a controller slice test that verifies the same <em>bookDetails</em> query requested in the GraphiQL playground a few moments ago.<br><br>Add the following to a test file <em>BookControllerTests.java</em>. Save this file in a location within the <em>src/test/java/com/example/graphqlserver/</em> folder.</p><pre class="language-java"><code>package com.example.graphqlserver;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.graphql.GraphQlTest;
import org.springframework.graphql.test.tester.GraphQlTester;
@GraphQlTest(BookController.class)
public class BookControllerTests {
@Autowired
private GraphQlTester graphQlTester;
@Test
void shouldGetFirstBook() {
this.graphQlTester
.documentName("bookDetails")
.variable("id", "book-1")
.execute()
.path("bookById")
.matchesJson("""
{
"id": "book-1",
"name": "Effective Java",
"pageCount": 416,
"author": {
"firstName": "Joshua",
"lastName": "Bloch"
}
}
""");
}
}
</code></pre><p>This test refers to a GraphQL query similar to what we used in the GraphiQL Playground. It’s parameterized with an <em>$id</em> to make it reusable. Add this query in a <em>bookDetails</em>.<em>graphql</em> file located in <em>src/test/resources/graphql-test.</em></p><pre class="language-graphql"><code>query bookDetails($id: ID) {
bookById(id: $id) {
id
name
pageCount
author {
id
firstName
lastName
}
}
}</code></pre><p>Run the test and verify that the result is identical to the GraphQL query manually requested in the GraphiQL Playground.<br><br>The <em>@GraphQlTest</em> annotation is useful for writing controller slice tests, which are focused on a single controller. <em>@GraphQlTest </em>auto-configures the Spring for GraphQL infrastructure, without any transport nor server being involved. Automatic configuration enables us to write tests faster by skipping boilerplate code. As this is a focused slice test, only a limited number of beans are scanned including <em>@Controller</em> and <em>RuntimeWiringConfigurer.</em><br><br><em>GraphQlTester</em> is a contract that declares a common workflow for testing GraphQL requests, independent of transport. In our test, we provide a document with <em>documentName</em> with the required variables, then <em>execute</em> the request. We then select a part of the response with its JSON path and assert that the JSON at this location matches the expected result.<br><br>Congratulations! In this tutorial you built a GraphQL service, ran your first query, and wrote your first GraphQL test!<br></p><p></p></div><footer class="wrapper post__footer"><p class="post__last-updated">This article was updated on Januar 24, 2024</p><ul class="post__tag"><li><a href="https://www.finecloud.ch/tags/api/">api</a></li><li><a href="https://www.finecloud.ch/tags/graphql/">graphql</a></li><li><a href="https://www.finecloud.ch/tags/java/">java</a></li><li><a href="https://www.finecloud.ch/tags/softwareentwicklung/">software development</a></li><li><a href="https://www.finecloud.ch/tags/spring/">spring</a></li><li><a href="https://www.finecloud.ch/tags/spring-framework/">spring-framework</a></li></ul><div class="post__share"></div></footer></article><nav class="post__nav"><div class="post__nav-inner"><div class="post__nav-prev"><svg width="1.041em" height="0.416em" aria-hidden="true"><use xlink:href="https://www.finecloud.ch/assets/svg/svg-map.svg#arrow-prev"/></svg> <a href="https://www.finecloud.ch/working-efficient-within-different-directories-in-a-linux-shell.html" class="post__nav-link" rel="prev"><span>Previous</span> Working efficient within different directories in a Linux Shell</a></div><div class="post__nav-next"><a href="https://www.finecloud.ch/how-to-do-a-code-review.html" class="post__nav-link" rel="next"><span>Next</span> How to do a code review </a><svg width="1.041em" height="0.416em" aria-hidden="true"><use xlink:href="https://www.finecloud.ch/assets/svg/svg-map.svg#arrow-next"/></svg></div></div></nav><div class="post__related related"><div class="wrapper"><h2 class="h5 related__title">You should also read:</h2><article class="related__item"><div class="feed__meta"><time datetime="2023-10-28T14:08" class="feed__date">Oktober 28, 2023</time></div><h3 class="h1"><a href="https://www.finecloud.ch/microservices.html">Microservices</a></h3></article><article class="related__item"><div class="feed__meta"><time datetime="2023-05-02T21:45" class="feed__date">Mai 2, 2023</time></div><h3 class="h1"><a href="https://www.finecloud.ch/building-spring-boot-docker-images.html">Building Spring Boot Docker Images</a></h3></article></div></div></main><footer class="footer"><div class="footer__copyright"><p>Powered by Publii</p></div><button onclick="backToTopFunction()" id="backToTop" class="footer__bttop" aria-label="Back to top" title="Back to top"><svg><use xlink:href="https://www.finecloud.ch/assets/svg/svg-map.svg#toparrow"/></svg></button></footer></div><script>window.publiiThemeMenuConfig = {
mobileMenuMode: 'sidebar',
animationSpeed: 300,
submenuWidth: 'auto',
doubleClickTime: 500,
mobileMenuExpandableSubmenus: true,
relatedContainerForOverlayMenuSelector: '.top',
};</script><script defer="defer" src="https://www.finecloud.ch/assets/js/scripts.min.js?v=6ca8b60e6534a3888de1205e82df8528"></script><script>var images = document.querySelectorAll('img[loading]');
for (var i = 0; i < images.length; i++) {
if (images[i].complete) {
images[i].classList.add('is-loaded');
} else {
images[i].addEventListener('load', function () {
this.classList.add('is-loaded');
}, false);
}
}</script><script defer="defer" src="https://www.finecloud.ch/media/plugins/syntaxHighlighter/prism.js"></script></body></html>