Class 1: Your First GraphQL Service
Duration: 30 minutes Difficulty: Beginner Prerequisites: Basic Spring Boot knowledge, Java 17+, IDE of choice
What You'll Learn
By the end of this class, you will:
- Understand what GraphQL is and why it matters
- Create a Spring Boot project with GraphQL support
- Write your first GraphQL schema
- Implement your first query resolver
- Test queries using GraphiQL
What is GraphQL?
Before we write code, let's understand what we're building.
GraphQL is a query language for your API. Unlike REST, where the server decides what data to return, GraphQL lets clients ask for exactly what they need.
┌─────────────────────────────────────────────────────────────────────┐
│ REST vs GraphQL │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ REST (Multiple endpoints, fixed responses): │
│ GET /movies/1 → { id, title, year, director, actors... } │
│ GET /movies/1/actors → [{ id, name, bio, awards... }] │
│ GET /directors/5 → { id, name, movies... } │
│ │
│ GraphQL (One endpoint, flexible responses): │
│ POST /graphql │
│ query { │
│ movie(id: 1) { │
│ title ← Only what you need │
│ director { name } ← Nested in one request │
│ } │
│ } │
│ │
└─────────────────────────────────────────────────────────────────────┘
Step 1: Create the Project
Let's create a new Spring Boot project. You have two options:
Option A: Spring Initializr (Recommended)
-
Go to start.spring.io
-
Configure your project:
- Project: Maven
- Language: Java
- Spring Boot: 3.2.x (or latest stable)
- Group: com.example
- Artifact: moviedb
- Name: moviedb
- Package name: com.example.moviedb
- Packaging: Jar
- Java: 17 or 21
-
Add dependencies:
- Spring Web
- Spring for GraphQL
-
Click "Generate" and extract the ZIP file
Option B: Add to Existing Project
If you have an existing Spring Boot project, add these dependencies to your pom.xml:
<dependencies>
<!-- Spring Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring GraphQL -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-graphql</artifactId>
</dependency>
<!-- For testing (optional but recommended) -->
<dependency>
<groupId>org.springframework.graphql</groupId>
<artifactId>spring-graphql-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Step 2: Enable GraphiQL
GraphiQL is an in-browser IDE for exploring GraphQL APIs. Let's enable it.
Open src/main/resources/application.properties (or create application.yml) and add:
# application.properties
spring.graphql.graphiql.enabled=true
spring.graphql.graphiql.path=/graphiql
Or in YAML format:
# application.yml
spring:
graphql:
graphiql:
enabled: true
path: /graphiql
Step 3: Create Your First Schema
GraphQL uses a Schema Definition Language (SDL) to define your API. Create this file:
📁 src/main/resources/graphql/schema.graphqls
type Query {
hello: String!
movie(id: ID!): Movie
movies: [Movie!]!
}
type Movie {
id: ID!
title: String!
releaseYear: Int!
genre: String!
}
Let's break this down:
| Element | Meaning |
|---|---|
type Query | The entry point for read operations |
hello: String! | A field that returns a non-null String |
movie(id: ID!) | A field that takes an ID argument |
[Movie!]! | A non-null list of non-null Movies |
type Movie | A custom object type |
! means non-null. String can be null, String! cannot.
[Movie]- nullable list of nullable movies[Movie!]- nullable list of non-null movies[Movie!]!- non-null list of non-null movies
Step 4: Create the Movie Model
Create a simple Java class to represent a Movie:
📁 src/main/java/com/example/moviedb/model/Movie.java
package com.example.moviedb.model;
public class Movie {
private String id;
private String title;
private int releaseYear;
private String genre;
// Constructor
public Movie(String id, String title, int releaseYear, String genre) {
this.id = id;
this.title = title;
this.releaseYear = releaseYear;
this.genre = genre;
}
// Getters
public String getId() {
return id;
}
public String getTitle() {
return title;
}
public int getReleaseYear() {
return releaseYear;
}
public String getGenre() {
return genre;
}
}
If you're using Java 16+, you can use a record instead:
public record Movie(String id, String title, int releaseYear, String genre) {}
Step 5: Create the Query Controller
Now let's implement the resolvers that handle our GraphQL queries:
📁 src/main/java/com/example/moviedb/controller/MovieController.java
package com.example.moviedb.controller;
import com.example.moviedb.model.Movie;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.stereotype.Controller;
import java.util.List;
@Controller
public class MovieController {
// Sample data - we'll use a database in later classes
private final List<Movie> movies = List.of(
new Movie("1", "The Shawshank Redemption", 1994, "Drama"),
new Movie("2", "The Godfather", 1972, "Crime"),
new Movie("3", "The Dark Knight", 2008, "Action"),
new Movie("4", "Pulp Fiction", 1994, "Crime"),
new Movie("5", "Forrest Gump", 1994, "Drama")
);
@QueryMapping
public String hello() {
return "Welcome to the Movie Database GraphQL API!";
}
@QueryMapping
public List<Movie> movies() {
return movies;
}
@QueryMapping
public Movie movie(@Argument String id) {
return movies.stream()
.filter(m -> m.getId().equals(id))
.findFirst()
.orElse(null);
}
}
Let's understand the annotations:
| Annotation | Purpose |
|---|---|
@Controller | Marks this as a Spring controller |
@QueryMapping | Maps a method to a GraphQL query field |
@Argument | Injects a GraphQL argument into the method |
By default, @QueryMapping uses the method name to match the schema field. So movies() maps to Query.movies. You can override this: @QueryMapping("allMovies")
Step 6: Run and Test
Start your application:
./mvnw spring-boot:run
Or run the main class from your IDE.
Open your browser and navigate to: http://localhost:8080/graphiql
You should see the GraphiQL interface!
Your First Query
In the left panel, type:
query {
hello
}
Click the Play button (▶️). You should see:
{
"data": {
"hello": "Welcome to the Movie Database GraphQL API!"
}
}
Query All Movies
query {
movies {
id
title
releaseYear
}
}
Response:
{
"data": {
"movies": [
{
"id": "1",
"title": "The Shawshank Redemption",
"releaseYear": 1994
},
{
"id": "2",
"title": "The Godfather",
"releaseYear": 1972
}
// ... more movies
]
}
}
Query a Single Movie
query {
movie(id: "3") {
title
genre
releaseYear
}
}
Response:
{
"data": {
"movie": {
"title": "The Dark Knight",
"genre": "Action",
"releaseYear": 2008
}
}
}
Try Requesting Different Fields
That's the power of GraphQL! Try this:
query {
movies {
title
}
}
You only get title back - nothing else. No over-fetching!
Understanding the Request/Response
Let's look at what's happening under the hood:
┌─────────────────────────────────────────────────────────────────────┐
│ GraphQL Request Flow │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 1. Client sends POST to /graphql with query in body │
│ │
│ POST /graphql │
│ Content-Type: application/json │
│ { │
│ "query": "{ movies { title } }" │
│ } │
│ │
│ 2. Spring GraphQL parses and validates the query │
│ │
│ 3. For each field, it calls the corresponding resolver │
│ Query.movies → MovieController.movies() │
│ │
│ 4. Response is returned as JSON │
│ { "data": { "movies": [...] } } │
│ │
└─────────────────────────────────────────────────────────────────────┘
Project Structure So Far
Your project should look like this:
moviedb/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/example/moviedb/
│ │ │ ├── MoviedbApplication.java
│ │ │ ├── controller/
│ │ │ │ └── MovieController.java
│ │ │ └── model/
│ │ │ └── Movie.java
│ │ └── resources/
│ │ ├── application.properties
│ │ └── graphql/
│ │ └── schema.graphqls
│ └── test/
├── pom.xml
└── mvnw
Exercises
Before moving to the next class, try these exercises:
Exercise 1: Add a New Field
Add a rating field (type Float) to the Movie type and model. Update your sample data with ratings.
Solution
schema.graphqls:
type Movie {
id: ID!
title: String!
releaseYear: Int!
genre: String!
rating: Float!
}
Movie.java: Add a rating field and update the constructor.
MovieController.java: Update sample data:
new Movie("1", "The Shawshank Redemption", 1994, "Drama", 9.3)
Exercise 2: Add a New Query
Add a query called moviesByGenre(genre: String!): [Movie!]! that filters movies by genre.
Solution
schema.graphqls:
type Query {
# ... existing queries
moviesByGenre(genre: String!): [Movie!]!
}
MovieController.java:
@QueryMapping
public List<Movie> moviesByGenre(@Argument String genre) {
return movies.stream()
.filter(m -> m.getGenre().equalsIgnoreCase(genre))
.toList();
}
Exercise 3: Explore GraphiQL
Use the "Docs" panel on the right side of GraphiQL to explore your schema. Click on types to see their fields.
Common Issues and Solutions
Issue: Schema file not found
Error: No schema found
Solution: Make sure your schema file is at src/main/resources/graphql/schema.graphqls (note the .graphqls extension)
Issue: Query returns null
Error: Field returns null unexpectedly
Solution: Check that your method name matches the schema field name, or use @QueryMapping("fieldName")
Issue: Port already in use
Error: Port 8080 already in use
Solution: Either stop the other application or change the port in application.properties:
server.port=8081
Summary
In this class, you learned:
✅ GraphQL is a query language that lets clients request exactly the data they need
✅ Spring GraphQL integrates seamlessly with Spring Boot
✅ The schema (.graphqls file) defines your API contract
✅ @QueryMapping connects schema fields to Java methods
✅ @Argument injects GraphQL arguments into your resolvers
✅ GraphiQL provides an interactive way to test your API
What's Next?
In Class 2: Schema Design Fundamentals, we'll dive deeper into:
- All GraphQL scalar types
- Enums and custom types
- Input types for complex arguments
- Schema design best practices
Your Movie API is about to get a lot more interesting!
Homework: Add at least 5 more movies to your sample data with various genres. We'll use them in the next class.