Skip to content

ObscuraStudio/PokemonAPI_RecapProject

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

15 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Summary written by Claude!

Pokemon API Recap Project

CI Java Spring Boot MongoDB

A Spring Boot REST API that fetches Pokémon data from the PokéAPI and lets you manage a personal favorites collection stored in MongoDB.

Built as a recap project covering the core building blocks of a modern Spring Boot backend: an outbound HTTP client, layered architecture with DTO separation, bean validation, centralized exception handling, and full CRUD persistence.

Tech Stack

Area Technology
Language Java 25
Framework Spring Boot 4.0.x (Spring Framework 7)
HTTP client RestClient
Persistence Spring Data MongoDB
Validation Jakarta Bean Validation
JSON Jackson 3
Boilerplate Lombok
Testing JUnit 5, Mockito, MockMvc, MockRestServiceServer
Build Maven

Architecture

The app follows a clean, layered flow. Each layer talks only to the one below it:

Client ──▶ Controller ──▶ Service ──▶ Repository ──▶ MongoDB
                            │
                            └──▶ RestClient ──▶ PokéAPI
  • Controller – HTTP layer: routing, request/response (de)serialization, validation.
  • Service – business logic: calls the PokéAPI, maps data, and persists favorites.
  • Repository – Spring Data MongoDB; CRUD methods are generated automatically.

DTO separation

Two distinct DTO families keep external and internal concerns apart:

  • PokéAPI DTOs (ExternalPokemonDto and its nested records) mirror the raw PokéAPI JSON and are used only for mapping the external response.
  • Application DTOs (FavoriteRequest, NicknameUpdateRequest, PokemonResponse) define exactly what each endpoint accepts and returns — never exposing the external shape or the database entity directly.

API Endpoints

Pokémon lookup

Method Path Description
GET /api/pokemon/{name} Fetch simplified Pokémon data from the PokéAPI
GET /api/pokemon/random Fetch a random Pokémon
GET /api/pokemon/type/{type} List all Pokémon of a given type
GET /api/pokemon/compare/{name1}/{name2} Compare two Pokémon by height and weight

ExampleGET /api/pokemon/pikachu

{
  "pokemonId": 25,
  "pokemonName": "pikachu",
  "pictureUrl": "https://raw.githubusercontent.com/PokeAPI/sprites/.../25.png",
  "height": 4,
  "weight": 60,
  "types": ["electric"]
}

RandomGET /api/pokemon/random picks a random id and returns the same shape as above. Because /random is a literal path it always takes priority over /{name}, so it is never mistaken for a Pokémon called "random".

By typeGET /api/pokemon/type/fire

Fetches all Pokémon of a type from the PokéAPI's /type/{type} endpoint and returns a simplified list (a single API call — no per-Pokémon fan-out). The id is parsed from each entry's resource URL.

[
  { "pokemonId": 4, "pokemonName": "charmander" },
  { "pokemonId": 5, "pokemonName": "charmeleon" },
  { "pokemonId": 6, "pokemonName": "charizard" }
]

CompareGET /api/pokemon/compare/charizard/blastoise

Fetches both Pokémon and merges the results, reporting which is heavier/taller and by how much. The difference is absolute, so the argument order doesn't change the magnitude.

{
  "heavierPokemon": "charizard",
  "weightDifference": 50,
  "tallerPokemon": "charizard",
  "heightDifference": 1
}

Favorites collection

Method Path Description
POST /api/collection Add a Pokémon to favorites
GET /api/collection List all favorites
GET /api/collection/{id} Get a single favorite by id
GET /api/collection/search?name=... Search favorites by Pokémon name or nickname
PUT /api/collection/{id} Update a favorite's nickname
DELETE /api/collection/{id} Remove a favorite

CreatePOST /api/collection

{
  "pokemonName": "pikachu",
  "nickname": "My Starter"
}

The service verifies the Pokémon exists via the PokéAPI, copies the relevant fields, assigns a generated UUID, and stores the result:

{
  "id": "4b903fd9-93df-4fd9-a4fc-1b89b7d1b0f5",
  "pokemonId": "25",
  "nickname": "My Starter",
  "pokemonName": "pikachu",
  "pictureUrl": "https://raw.githubusercontent.com/...",
  "height": 4,
  "weight": 60,
  "types": ["electric"]
}

UpdatePUT /api/collection/{id} (only the nickname changes; Pokémon data stays intact)

{ "nickname": "Arena Champion" }

SearchGET /api/collection/search?name=pika

Matches the term against both pokemonName and nickname, case-insensitive and as a substring (so pika finds pikachu). Returns a list; an empty array if nothing matches.

[
  {
    "id": "4b903fd9-93df-4fd9-a4fc-1b89b7d1b0f5",
    "pokemonId": "25",
    "nickname": "My Starter",
    "pokemonName": "pikachu",
    "pictureUrl": "https://raw.githubusercontent.com/...",
    "height": 4,
    "weight": 60,
    "types": ["electric"]
  }
]

Implemented with a Spring Data derived query (findByPokemonNameContainingIgnoreCaseOrNicknameContainingIgnoreCase) — the method name itself defines the query, so no manual MongoDB code is needed.

Error Handling

A global @RestControllerAdvice translates exceptions into consistent JSON responses:

Exception HTTP status When
PokemonNotFoundException 404 The PokéAPI has no such Pokémon
CollectionEntryNotFoundException 404 No favorite with the requested id
MethodArgumentNotValidException 400 Request body fails validation

All errors share the same body:

{
  "message": "Pokemon not found: missingno",
  "timestamp": "2026-06-01T10:15:00"
}

Getting Started

Prerequisites

Configuration

The connection string is read from an environment variable, so no secrets live in the repo:

# src/main/resources/application.properties
spring.mongodb.uri=${MONGO_DB_URI}

Set MONGO_DB_URI before running, e.g.:

export MONGO_DB_URI="mongodb+srv://<user>:<password>@<cluster>.mongodb.net/pokedex?retryWrites=true&w=majority"

Include the database name (/pokedex above) in the URI, otherwise MongoDB defaults to test.

Run

./mvnw spring-boot:run

The API is then available at http://localhost:8080.

Test

./mvnw test

The suite includes unit tests for the service layer (repository mocked with Mockito, the PokéAPI faked with MockRestServiceServer — no real network or database) and web-layer integration tests for the controllers (@WebMvcTest + MockMvc, covering routing, validation, and exception mapping).

Project Structure

src/main/java/org/example/pokemonapi_recapproject
├── configuration   # RestClient bean
├── controller      # PokeController, CollectionController
├── dto             # external PokéAPI records + application DTOs
├── exception       # custom exceptions + global handler + ErrorResponse
├── model           # Favorite (@Document)
├── repository      # FavoriteRepository (MongoRepository)
└── service         # PokeService

Concepts Demonstrated

  • Consuming an external REST API with Spring's RestClient and per-call error translation via onStatus
  • Mapping deeply nested external JSON into clean DTOs (including @JsonProperty for non-Java-friendly keys)
  • Constructor-based dependency injection and a strict controller → service → repository layering
  • Bean Validation on request bodies
  • Centralized exception handling with @RestControllerAdvice
  • Full CRUD persistence with Spring Data MongoDB

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages