🚀 Task API Demo – A fully featured Task Management API built with Spring Boot, supporting both REST and GraphQL.
- Task API Demo
- 🔹 Features
- 🏗️ How It Was Built
- 🔄 Auto-Generated vs. Manual Implementation
- 🛠️ Installation & Setup
- 📖 API Documentation
- Choosing the Right API Documentation Tool?
- 🔑 Authentication & Token Retrieval
- Using GraphQL
👉 REST & GraphQL API – Flexible data querying options
👉 OpenAPI & Swagger UI – Auto-generated API documentation
👉 JWT Authentication – Secure login with token-based auth
👉 Simulated Login – Test authentication easily in Swagger
👉 Validation & Error Handling – Standardized request validation
👉 Pagination & Filtering – Efficient data retrieval
👉 Embedded H2 Database with Spring Data JPA – Lightweight, in-memory persistence for easy testing
This project follows an API-first approach, where the API specification was defined first, and the code was auto-generated.
- The API was designed using OpenAPI 3.0.
- The spec (
task-api-spec.yaml
) defines endpoints, request/response structures, and validation rules. - Edited using Swagger Editor.
To generate code using OpenAPI:
PROJECT_NAME=taskapi
PACKAGE_NAME=com.camelcase
docker run --rm -v ${PWD}:/local -u $(id -u):$(id -g) openapitools/openapi-generator-cli generate \
-i /local/task-api-spec.yaml \
-g spring \
-o /local \
--additional-properties=library=spring-boot,useSpringBoot3=true,java17=true,dateLibrary=java8,interfaceOnly=true \
--api-package=${PACKAGE_NAME}.${PROJECT_NAME}.api \
--model-package=${PACKAGE_NAME}.${PROJECT_NAME}.model \
--group-id=${PACKAGE_NAME} \
--artifact-id=${PROJECT_NAME} \
--package-name=${PACKAGE_NAME}.${PROJECT_NAME}
👍 Uses the OpenAPI specification (task-api-spec.yaml
) created in the previous step as input.
👍 Keeps generated interfaces separate from business logic (interfaceOnly=true
).
- A service layer was added for clean separation of concerns.
- H2 database with Spring Data JPA was used for persistence and testing
- Spring Security & JWT authentication was implemented manually.
To generate the GraphQL schema from the existing OpenAPI spec, run:
docker run --rm -v $(pwd):/app -w /app -u $(id -u):$(id -g) node:22 npx openapi-to-graphql-cli ./task-api-spec.yaml \
--save schema.graphql
mv schema.graphql src/main/resources/graphql/task.graphqls
👍 What This Command Does:
- Converts the OpenAPI 3.0 specification into a GraphQL schema.
- Saves the schema to
schema.graphql
. - Moves it to the correct location (
src/main/resources/graphql/task.graphqls
).
🛠️ Additional Manual Steps Needed:
- Implement GraphQL resolvers using the existing service layer.
Feature | Auto-Generated? | Manual Implementation? |
---|---|---|
Controller interfaces | ✅ Yes (OpenAPI Generator) | 🔴 Need to implement methods |
GraphQL schema (.graphqls ) |
✅ Yes (openapi-to-graphql-cli ) |
❌ No manual work |
DTOs (Models) | ✅ Yes (OpenAPI Generator) | 🔴 Need to extend with Lombok |
Validation (@NotBlank ) |
✅ Yes (if defined in OpenAPI spec) | ❌ No manual work |
Exception Handling | ❌ No | ✅ Need GlobalExceptionHandler.java and GraphQLExceptionHandler.java |
Service Layer (TaskService ) |
❌ No | ✅ Need to implement logic |
Pagination for REST | ✅ Yes (via OpenAPI) | 🔴 Implement in service layer |
Pagination for GraphQL | ✅ Yes (via OpenAPI to GraphQL generator) | 🔴 Implement in service layer |
Database (H2, Repository) | ❌ No | ✅ Need to implement manually |
git clone https://github.com/your-username/task-api-demo.git
cd task-api-demo
mvn clean spring-boot:run
This project includes multiple API documentation tools for comparison.
Each documentation tool has its own strengths and trade-offs:
Documentation Tool | Usage | Supports Try It Out? | Authentication Support | Customization |
---|---|---|---|---|
Swagger UI | API testing + Docs | ✅ Yes | ✅ OAuth2, Basic, API Key | Moderate |
ReDoc | Clean API Docs | ❌ No | ❌ No built-in auth | High |
Stoplight Elements | Interactive Docs + Testing | ✅ Yes | ✅ OAuth2, Basic, API Key | High |
RapiDoc | Customizable API Docs | ✅ Yes | ✅ OAuth2, Basic, API Key | High |
GraphiQL | Interactive GraphQL UI | ✅ Yes | ❌ No built-in auth (manual token required) | Moderate |
📌 Best for: Developers who want "Try It Out" functionality.
🔗 Access: http://localhost:8080/swagger-ui.html
✅ Supports API testing, OAuth2, Basic Auth, and API Key authentication.
📌 Best for: Clean, professional API documentation without interactive testing.
🔗 Access: http://localhost:8080/redoc
❌ No "Try It Out" feature – for viewing API specs only.
📌 Best for: Interactive API documentation with testing and authentication.
🔗 Access: http://localhost:8080/stoplight
✅ Supports OAuth2, Basic Auth, and interactive API testing.
📌 Best for: Highly customizable API documentation with interactive testing.
🔗 Access: http://localhost:8080/rapidoc
✅ Supports API testing and authentication with custom themes.
📌 Best for: Exploring and testing GraphQL queries and mutations.
🔗 Access: http://localhost:8080/graphiql
✅ Supports "Try It Out" for GraphQL queries
❌ No built-in authentication – you must manually add the Authorization header.
🛠 How to Add Authentication:
- Open GraphiQL (http://localhost:8080/graphiql).
- Click on "HTTP Headers" (top-right button). Add the following:
{
"Authorization": "Bearer your-jwt-token"
}
🔹 Replace your-jwt-token with the actual token from the login API.
- Need API testing? → Use Swagger UI, Stoplight Elements, or RapiDoc.
- Need professional-looking static docs? → Use ReDoc.
- Want a customizable solution? → Use RapiDoc or Stoplight Elements.
- Working with GraphQL? → Use GraphiQL for interactive query execution or integrate GraphQL endpoints into Stoplight Elements.
- Need API documentation for both REST and GraphQL? → Use a combination of Swagger UI (REST) and GraphiQL (GraphQL).
- For this demo, authentication is handled via a local auth endpoint that issues tokens using the OAuth2 Password Flow.
- In a production environment, authentication would typically use the Authorization Code Flow with an external OAuth2 provider such as Keycloak, Okta, or AWS Cognito, ensuring enhanced security and proper identity federation.
The demo user credentials are configured in the application.yml
. You can find the username and password there for authentication.
curl -X POST http://localhost:8080/api/auth/login \
-H "Content-Type: application/json" \
-d '{"username": <USERNAME>, "password": <PASSWORD>}'
Example Response:
{
"access_token": "your-jwt-token",
"expires_in":"3600",
"token_type":"Bearer"
}
Include the token in the Authorization
header:
curl -X GET "http://localhost:8080/tasks" -H "Authorization: Bearer your-jwt-token"
Most of the API documentation tools used in this project support authentication, allowing users to securely test protected endpoints directly within the documentation interface.
- Swagger UI, Stoplight Elements, and RapiDoc support authentication via OAuth2, Basic Auth, and API Keys, enabling users to obtain tokens and include them in API requests.
- ReDoc, however, is view-only and does not support interactive authentication or API testing.
For this demo, authentication is handled using OAuth2 Password Flow, where users enter a username and password to receive a token. This token is then automatically included in API requests when testing endpoints.
In a production environment, an Authorization Code Flow with an external OAuth provider (e.g., Keycloak, Okta) would typically be used for improved security and identity management.
GraphQL is a query language for APIs that provides:
- Flexible data retrieval (fetch only the fields you need).
- Efficient performance (reduces over-fetching of data).
- Self-documenting schema (introspective API structure).
- Single endpoint (
/graphql
) instead of multiple REST endpoints.
query {
taskPage(page: 1, size: 5) {
tasks {
id
title
taskStatus
}
totalPages
totalItems
}
}
👍 Returns paginated task data.
query {
task(id: "1") {
id
title
taskStatus
}
}
👍 Returns a specific task by ID.
mutation {
create(taskCreateRequestInput: {
title: "New Task",
description: "A task for testing",
taskStatus: PENDING,
dueDate: "2025-04-01"
}) {
id
title
taskStatus
}
}
👍 Creates a new task.
mutation {
update(id: "1", taskUpdateRequestInput: {
title: "Updated Task",
taskStatus: COMPLETED
}) {
id
title
taskStatus
}
}
👍 Updates an existing task.
mutation {
deleteTask(id: "1")
}
👍 Deletes a task.