GraphQL - simple backend - SpringBoot application

mahendranv

Mahendran

Posted on May 17, 2021

GraphQL - simple backend - SpringBoot application

Cross-posting from my blog

In this post, I'll cover how to create a simple GraphQL server with Springboot and Netflix DGS. DGS is chosen because you start with GraphQL schema and then build server-side logic around it.

Prerequisite


Initial setup

To start with SpringBoot https://start.spring.io/ has set of templates. Head over there and add Spring Web dependency. Select — Kotlin, jar, Java 8 as project base in lefet pane. Hit Generate button and extract the downloaded zip to a project folder.

image-20210517200554913


Know the project structure

Our application is a single module project in the below tree format. Now it has bare minimum classes to just start the server.

├─build.gradle.kts
├─gradle
│  └─ wrapper
│      ├─ gradle-wrapper.jar
│      └─ gradle-wrapper.properties
├─gradlew
├─gradlew.bat
├─settings.gradle.kts
└─src
   ├─ main
   │   ├─ kotlin
   │   │   └─ com.ex2.gql.expense   //package
   │   │        └─ ExpenseApplication.kt
   │   └─ resources
   │       ├─ application.properties
   │       ├─ static
   │       └─ templates
   └─ test
       └─ kotlin
           └─ com.ex2.gql.expense
                └─ ExpenseApplicationTests.kt
Enter fullscreen mode Exit fullscreen mode

fig. Springboot - initial project structure

Know about few files from the project structure.

  1. build.gralde.kts - is where we add our project dependencies [database / logger / graphql]
  2. ExpenseApplication.kt is the starting point of our application. Java main method is placed here
  3. Files and folders of name gradle* are present here to build our app
  4. resources directory is where we place out graphql schemas
  5. src/main/kotlin is we actually code our business logic

How to run the project?

Start terminal at the project root and run.

./gradlew bootRun

Enter fullscreen mode Exit fullscreen mode

— or —

For development, we'll use Intellij. So, select the bootRun gradle task in the gradle tool window and start.

image-20210517213150720

On both cases, gradle wrapper will download all the dependencies (including the gradle distribution itself), and then start the application.

Netflix DGS framework setup

Netflix has released the dgs framework as open source on 2020. It powers the netflix API layer for over a year now. DGS plugin is available in mavencentral for us to use - Add the below dependency to your project.

// https://mvnrepository.com/artifact/com.netflix.graphql.dgs/graphql-dgs-spring-boot-starter
    implementation("com.netflix.graphql.dgs:graphql-dgs-spring-boot-starter:3.12.1")

Enter fullscreen mode Exit fullscreen mode

This plugin provides set of annotations that will connect our data fetchers / queries to our main application ExpenseApplication and generates graphiql playground for us to play around with our data.

GraphQL schema setup

schema is the contract between client and server. DGS is a schema first framework. i.e we define a schema and DGS generate model classes for us. Let's start by creating a schema file in resource directory.

└─resources
    ├─ application.properties
    ├─ schema
    │   └─ schema.graphqls
    ├─ static
    └─ templates

Enter fullscreen mode Exit fullscreen mode

Create file schema.graphqls in the resources/schema directory. And define an operation and a data model as below.

type Query {
    expenses : [Expense]
}

type Expense {
    id: ID
    remarks: String
    amount: Int
    isIncome: Boolean
}

Enter fullscreen mode Exit fullscreen mode

Mapping java model to the schema

To get basic understanding on how model/entity is connected to the schema, Let's create a data class for Expense and corresponding fetcher. Fetchers are the helper classes that acts as an adapter between schema and data model.

//File:"ExpenseDataFetcher.kt"

@DgsComponent
class ExpenseDataFetcher {

    private val expenses = mutableListOf(
        Expense(id = 1, amount = 10, remarks = "Expense 1", isIncome = false),
        Expense(id = 2, amount = 120, remarks = "Expense 2", isIncome = false),
        Expense(id = 3, amount = 110, remarks = "Income 3", isIncome = false),
    )

    @DgsQuery
    fun expenses(): List<Expense> {
        return expenses
    }


    data class Expense(
        val id: Int,
        val amount: Int,
        val remarks: String,
        val isIncome: Boolean
    )
}

Enter fullscreen mode Exit fullscreen mode

Tips: To fill in the data class, you can use the Fill data class plugin. It can generate default values for all the parameters. Inside the Expenses block invoke suggestions by Alt(Option) + Return(Enter) key.

image-20210517221425546

That's it!!

Now we have a working setup of a GraphQL query to play with. Annotations DgsQuery and DgsComponent will connect our model to the schema and register our component with main application.

GraphiQL playground

Now run the application and visit http://localhost:8080/graphiql. You should see the GraphiQL page — A visual tool where you construct your queries and verify the fetcher.

query MyQuery { 
  expenses {
    amount
    remarks
  }
}
Enter fullscreen mode Exit fullscreen mode

image-20210517223413283

fig. Query call to the server


Adding a mutation

To add a mutation, head over to the schema file and add input type and a mutation for expense.

type Mutation {
    createExpense(data: ExpenseInput) : Expense
}

input ExpenseInput {
    remarks: String
    amount: Int
    isIncome: Boolean
}

Enter fullscreen mode Exit fullscreen mode

Add a bit of code to insert an element into the list. Give matching names and a method to create field [auto generated id]. And an annotation to connect function to the mutation query.


 private val random = Random(10000)
  private fun randomInt() = random.nextInt()

  @DgsMutation
  fun createExpense(data: ExpenseInput): Expense {
      val expense = Expense(
          id = randomInt(),
          amount = data.amount,
          remarks = data.remarks,
          isIncome = data.isIncome,
      )
      expenses.add(0, expense)
      return expense
  }

  data class ExpenseInput(
      val amount: Int,
      val remarks: String,
      val isIncome: Boolean
  )


Enter fullscreen mode Exit fullscreen mode

Now try the mutation in GraphiQL playground. You should see the new Expense object is returned. Verify the entry added to the list by again running MyQuery in the playground.

mutation MyMutation {  createExpense(data: {remarks: "From GraphiQL", amount: 100, isIncome: true}) {    id    remarks    amount    isIncome  }}
Enter fullscreen mode Exit fullscreen mode

image-20210517231344271{:.lead loading="lazy"}

fig.Mutation call to the server

...


Accessing the GraphQL endpoint in client apps and Insomnia / Postman

If you're fond of a REST/GraphQL client or to use the API in your client already, use the following endpoint.

// Endpoint for Client
http://localhost:8080/graphql

// GraphiQL playground 
http://localhost:8080/graphiql

Enter fullscreen mode Exit fullscreen mode

Note there is no i in endpoint.

image-20210517235204232

fig. GraphQL query in Insomnia


Source code

Entire source code is available in Github.

GitHub logo mahendranv / spring-expenses

An expense manager backend using SpringBoot and Netflix DGS - GraphQL

spring-expenses

An expense manager backend using SpringBoot and Netflix DGS - GraphQL

Posts related to this repo







Endnote

This the the bare minimum setup to make Create / Read requests to the backend using GraphQL. It is a steep learning curve to write queries for nested objects and connecting to a persistent database. Also there are other components available in DGS framework for codegen and custom scalars. I'll explore and add each usecase to this series.

💖 💪 🙅 🚩
mahendranv
Mahendran

Posted on May 17, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related