Basic Java GraphQL Tutorial

Basic Java GraphQL Tutorial

Creating a GraphQL API using the Spring Boot SPQR in a matter of minutes

Note: This is a basic example/use-case for GraphQL in Spring Boot/Java. More in-depth, advanced tutorials will be coming in the future. React to this post & comment below for any suggestions.


This tutorial regards that you already know the very basics of Java, Spring Boot and Gradle, as well as how to bootstrap a new Spring Boot Gradle Project. This project uses JDK 11.

Install Dependencies

These are the dependecies needed for your build.gradle

compileOnly "org.projectlombok:lombok:1.18.12"
annotationProcessor "org.projectlombok:lombok:1.18.12"
testCompileOnly "org.projectlombok:lombok:1.18.12"
testAnnotationProcessor "org.projectlombok:lombok:1.18.12"

/* ---------- This is what is needed for GraphQL ---------- */
implementation "io.leangen.graphql:graphql-spqr-spring-boot-starter:0.0.6"

implementation group: 'org.postgresql', name: 'postgresql', version: '42.2.23'
implementation "org.hibernate.validator:hibernate-validator:7.0.1.Final"
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
testImplementation 'org.springframework.boot:spring-boot-starter-test'

Building Entities

In this example, I will have an Employee Entity, which has a Many-To-One Relationship with the Company Entity. These are all standard JPA Entities, but with a few SPQR Annotations on them.

@Setter
@Getter
@Entity
@NoArgsConstructor
@AllArgsConstructor
public class Employee {

  @Id
  @GeneratedValue
  @Column(columnDefinition = "uuid", updatable = false)
  @GraphQLQuery(name = "id", description = "A Person's Id")
  private UUID id;

  @NotNull(message = "There must be a Person's Name!")
  @GraphQLQuery(name = "fullName", description = "A Person's Name")
  private String fullName;

  @NotNull(message = "A Person must have an Age!")
  @GraphQLQuery(name = "age", description = "A Person's Age")
  private int age;

  @ManyToOne(
    targetEntity = Company.class, 
    fetch = FetchType.LAZY, 
    cascade = CascadeType.ALL)
  @GraphQLQuery(
    name = "company", 
    description = "The Company a Person works for")
  private Company company;

}

And the Company:

@Setter
@Getter
@Entity
@NoArgsConstructor
@AllArgsConstructor
public class Company {

  @Id
  @GeneratedValue
  @Column(columnDefinition = "uuid", updatable = false)
  @GraphQLQuery(name = "id", description = "A Company's Id")
  private UUID id;

  @NotNull(message = "There must be a Company Name!")
  @GraphQLQuery(
    name = "name", 
    description = "A Company's Name")
  private String name;

  @Min(10000)
  @NotNull(message = "You gotta tell us how rich you are!")
  @GraphQLQuery(
    name = "balance", 
    description = "A Company's Dollar")
  private BigDecimal balance;

  @NotNull(message = "There must be a Company Type!")
  @GraphQLQuery(
    name = "type", 
    description = "The type of company")
  private CompanyType type;

  public enum CompanyType {
    PRIVATE_LIMITED,
    SOLE_TRADER,
    PUBLIC
  }
}

Next, for our JPA Entities to have Data Access Layers (for persisting into a DB), we must create DAOs for each Entity.

Here is the Company DAO:

@Repository
public interface CompanyDao extends JpaRepository<Company, UUID> {
    /* Any custom JPA methods here */
}

And the Employee DAO:

@Repository
public interface EmployeeDao extends JpaRepository<Employee, UUID> {
  List<Employee> findAllByCompanyIdIn(Set<UUID> companyIds);
}

Now here's the bit where SPQR really shines, what would be "Controllers" in a standard Spring Web REST API, are now replaced with neat little resolvers which are exposed as mutations/queries/subscriptions by simply annotating it with @GraphQLApi at class-level.

Here's what the resolvers for both Entities would look like:

@Service
@GraphQLApi
@Transactional
@RequiredArgsConstructor
public class CompanyResolver {

  private final CompanyDao companyDao;

  @GraphQLQuery(name = "getAllCompanies")
  public List<Company> getAllCompanies() {
    return companyDao.findAll();
  }

  @GraphQLMutation(name = "saveCompany")
  public Company saveCompany(
    @GraphQLArgument(name = "company") @GraphQLNonNull 
    @Valid Company company) {
    return companyDao.save(company);
  }

  @GraphQLMutation(name = "deleteCompany")
  public void deleteCompany(
    @GraphQLArgument(name = "id") @GraphQLNonNull
    @Valid UUID companyId) {
    companyDao.deleteById(companyId);
  }
}
@Service
@GraphQLApi
@Transactional
@RequiredArgsConstructor
public class EmployeeResolver {

  private final EmployeeDao employeeDao;

  @GraphQLQuery(name = "getAllEmployees")
  public List<Employee> getAllEmployees() {
    return employeeDao.findAll();
  }

  @GraphQLQuery(name = "getEmployeeById")
  public Optional<Employee> getEmployeeById(
      @GraphQLArgument(name = "id") @GraphQLNonNull @Valid UUID id) {
    return employeeDao.findById(id);
  }

  @GraphQLMutation(name = "saveEmployee")
  public Employee saveEmployee(@GraphQLArgument(name = "employee")
    @GraphQLNonNull @Valid Employee company) {
    return employeeDao.save(company);
  }

  @GraphQLMutation(name = "deleteEmployee")
  public void deleteEmployee(@GraphQLArgument(name = "id") @GraphQLNonNull 
    @Valid UUID companyId) {
    employeeDao.deleteById(companyId);
  }
}

Finally, we wanna make sure our application is configured properly before running, so lets add this to our /resources/application.yml. (Making sure to substitute the username/passwords for the DB, and the server port if needed.)

Note: If you have bootstrapped your project and this file either doesn't exist or has the wrong file extension, delete all files inside the resources directory and create an application.yml.
server:
  port: 9090
  http2:
    enabled: true

spring:
  jpa:
    open-in-view: true
    show_sql: true

    hibernate:
      ddl-auto: update

  datasource:
    url: "jdbc:postgresql://localhost:5432/spqr-test"
    username: "username"
    password: "password"
    driver-class-name: org.postgresql.Driver

graphql:
  spqr:
    gui:
      enabled: true

voyager:
  enabled: true

These properties will allow the GraphQL Playground to operate on http://localhost:9090/gui, where you can explore your newly-created GraphQL API and make some requests!

If you have any questions or errors, be sure to comment below!

Happy GraphQLing!!