Contents

GraphQL Server using Spring Boot with Relational Mapping

Prerequisites

Step to build GraphQL server using Spring Boot with Relational Mapping

Note: You should have done the previous step GraphQL Server with Spring Boot

Setting Up Model

Create database migration resource/db/migration/V2_2__Book.sql see Flyway Documentation for versioning migration.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
SET AUTOCOMMIT = false;

START TRANSACTION;
    CREATE TABLE m_book (
        id                      BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
        created_at              DATETIME NOT NULL,
        created_by              BIGINT NOT NULL,
        updated_at              DATETIME,
        deleted_at              DATETIME,
        author_id               BIGINT NOT NULL,
        title                   VARCHAR(255) NOT NULL,
        publisher               VARCHAR(255) NOT NULL,
        description             VARCHAR(255),
        release_date            DATETIME NOT NULL,
        FOREIGN KEY (author_id) REFERENCES m_person(id)
    );
COMMIT;

Create book model com.maverick.graphql.model.BookModel which is inheritence from BaseModel.

 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
@Entity
@Table(name = "M_BOOK")
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Where(clause = "deleted_at is null")
public class BookModel extends BaseModel {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Getter
    @Setter
    private Long id;

    @ManyToOne
    @Getter
    @Setter
    private PersonModel author;

    @Column(name = "TITLE", nullable = false)
    @Getter
    @Setter
    private String title;

    @Column(name = "PUBLISHER", nullable = false)
    @Getter
    @Setter
    private String publisher;

    @Column(name = "DESCRIPTION")
    @Getter
    @Setter
    private String description;

    @Column(name = "RELEASE_DATE", nullable = false)
    @Getter
    @Setter
    private Timestamp releaseDate;

}

Edit person model com.maverick.graphql.model.PersonModel for mapping one to many from BookModel, add this line into PersonModel

1
2
3
4
5
@JsonIgnore
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "author")
@Getter
@Setter
private List<BookModel> authors;

Create repository com.maverick.graphql.repository.BookRepository extends from JpaRepository

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@Repository
@Transactional
public interface BookRepository extends JpaRepository<BookModel, Long> {

    @Query(value = "SELECT mb.* FROM m_book mb JOIN m_person mp ON mb.author_id = mp.id " +
            "WHERE UPPER(mp.first_name) = UPPER(?1)", nativeQuery = true)
    List<BookModel> findAllByAuthor(String name);

    @Query(value = "UPDATE m_book SET deleted_at = CURRENT_DATE WHERE id = ?1", nativeQuery = true)
    @Modifying
    void softDelete(Long id);

}

Create form com.maverick.graphql.form.BookForm for input request.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@Data
public class BookForm {

    private Long authorId;

    private String title;

    private String publisher;

    private String description;

    private LocalDate releaseDate;

}

Create service com.maverick.graphql.service.BookService to store book model into database using repository.

 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
@Service
public class BookService {

    private final BookRepository bookRepository;

    @Autowired
    public BookService(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }

    public BookModel save(BookModel book) {
        return bookRepository.save(book);
    }

    public List<BookModel> getAll() {
        return bookRepository.findAll();
    }

    public List<BookModel> getAllByAuthor(String name) {
        return bookRepository.findAllByAuthor(name);
    }

    public BookModel getById(Long id) {
        return bookRepository.findById(id).orElse(null);
    }

    public void delete(Long id) {
        bookRepository.softDelete(id);
    }

}

Mutation Resolver

Create mutation service com.maverick.graphql.mutation.BookMutation use for anychange data from GraphQL schema.

 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
@Service
public class BookMutation implements GraphQLMutationResolver {

    private final BookService bookService;
    private final PersonService personService;

    @Autowired
    public BookMutation(BookService bookService, PersonService personService) {
        this.bookService = bookService;
        this.personService = personService;
    }

    public BookModel addBook(BookForm form) {
        PersonModel author = personService.getById(form.getAuthorId()).orElse(null);

        if (author == null) throw new DataNotFoundException("author record: not found");
        BookModel book = BookModel.builder()
                .author(author)
                .description(form.getDescription())
                .publisher(form.getPublisher())
                .releaseDate(Timestamp.valueOf(form.getReleaseDate().atStartOfDay()))
                .title(form.getTitle())
                .build();
        return bookService.save(book);
    }

    public BookModel updateBook(BookForm form, Long id) {
        BookModel book = bookService.getById(id);
        if (book == null) throw new DataNotFoundException("book record: not found");

        PersonModel author = personService.getById(form.getAuthorId()).orElse(null);
        if (author == null) throw new DataNotFoundException("author record: not found");
        book.setAuthor(author);
        book.setDescription(form.getDescription());
        book.setPublisher(form.getPublisher());
        book.setReleaseDate(Timestamp.valueOf(form.getReleaseDate().atStartOfDay()));
        book.setTitle(form.getTitle());
        return bookService.save(book);
    }

    public Map<String, String> deleteBook(Long id) {
        BookModel book = bookService.getById(id);
        if (book == null) throw new DataNotFoundException("book record: not found");

        bookService.delete(id);
        Map<String, String> ret = new HashMap<>();
        ret.put("message", "ok");

        return ret;
    }

}

QueryResolver

Create query resolver com.maverick.graphql.resolver.BookResolver to handle query request.

 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
@Service
public class BookResolver implements GraphQLQueryResolver {

    private final BookService bookService;

    @Autowired
    public BookResolver(BookService bookService) {
        this.bookService = bookService;
    }

    public List<BookModel> getAllBook() {
        return bookService.getAll();
    }

    public List<BookModel> getAllBookByAuthor(final String author) {
        return bookService.getAllByAuthor(author);
    }

    public BookModel getBookById(final Long id) {
        BookModel book = bookService.getById(id);
        if (book == null) throw new DataNotFoundException("book record: not found");
        return book;
    }

}

GraphQL Schema

Add book into schema, edit resource/schema.graphqls.

 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
scalar Date

type Query {
    getAllPerson: [Person!]
    getPersonById(id: Int!): Person
    getPersonByIdentityNumber(identityNumber: String!): Person

    getAllBook: [Book!]
    getAllBookByAuthor(author: String!): [Book!]
    getBookById(id: Int!): Book!
}

type Mutation {
    createPerson(input: PersonInput!): Person!
    updatePerson(input: PersonInput!, id: Int!): Person!
    deletePerson(id: Int!): OkMessage!

    addBook(input: BookInput!): Book!
    updateBook(input: BookInput!, id: Int!): Book
    deleteBook(id: Int!): OkMessage!
}

type Book {
    id: Int!,
    author: Person!,
    title: String!,
    publisher: String!,
    description: String,
    releaseDate: Date!,
    createdAt: Date,
    createdBy: Int,
    updatedAt: Date
}

input BookInput {
    authorId: Int!,
    title: String!,
    publisher: String!,
    description: String,
    releaseDate: Date!
}

Try to run by typing mvn spring-boot:run then open Postman like below. You can edit any field you want.

URL: http://localhost:8080/graphql (POST)

Add Book

Query

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
mutation AddBook($input: BookInput!){
    addBook(input: $input) {
        id
        title
        createdAt
        author {
            id
            firstName
            address
        }
    }
}

GraphQL Variables

1
2
3
4
5
6
7
8
9
{
    "input": {
        "authorId": 2,
        "title": "Tutorial GraphQL",
        "publisher": "Github",
        "description": "How to build GraphQL server using Spring Boot",
        "releaseDate": "05-01-2021"
    }
}

Response

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
    "data": {
        "addBook": {
            "id": 2,
            "title": "Tutorial GraphQL",
            "createdAt": "2021-01-05 15:06:15.846128",
            "author": {
                "id": 2,
                "firstName": "Maverick",
                "address": "Yogyakarta"
            }
        }
    }
}

Get Book

Query

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
query {
    getBookById(id: 1) {
        id
        title
        releaseDate
        author {
            id
            firstName
            address
        }
        createdAt
    }
}

Response

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{
    "data": {
        "getBookById": {
            "id": 1,
            "title": "Tutorial GraphQL Spring Boot",
            "releaseDate": "2021-01-05 00:00:00.0",
            "author": {
                "id": 1,
                "firstName": "Alvinditya",
                "address": "Yogyakarta"
            },
            "createdAt": "2021-01-05 15:04:21.0"
        }
    }
}

Update Book

Query

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
mutation UpdateBook($input: BookInput!){
    updateBook(input: $input, id: 2) {
        id
        title
        createdAt
        author {
            id
            firstName
            address
        }
    }
}

GraphQL Variables

1
2
3
4
5
6
7
8
9
{
    "input": {
        "authorId": 2,
        "title": "Tutorial GraphQL Updated",
        "publisher": "Github",
        "description": "How to build GraphQL server using Spring Boot",
        "releaseDate": "06-01-2021"
    }
}

Response

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
    "data": {
        "updateBook": {
            "id": 2,
            "title": "Tutorial GraphQL Updated",
            "createdAt": "2021-01-05 15:06:16.0",
            "author": {
                "id": 2,
                "firstName": "Maverick",
                "address": "Yogyakarta"
            }
        }
    }
}

Delete Book

Query

1
2
3
4
5
mutation {
    deleteBook(id: 1) {
        message
    }
}

Response

1
2
3
4
5
6
7
{
    "data": {
        "deleteBook": {
            "message": "ok"
        }
    }
}

Clone or Download

You can clone or download this project

1
[email protected]:piinalpin/graphql-spring-boot.git

Thankyou

Medium - GraphQL server using Spring Boot, Part I

Baeldung - Getting Started with GraphQL and Spring Boot

Github - Team Supercharge