This Spring Boot GraphQL API powers the Beyond Footsteps project by exposing migration and refugee indicators (derived from UNHCR data) via a strongly-typed schema. It is built using resolvers, services, repositories, DTOs, and mappers and is intended for educational and portfolio purposes only.
Note: This project is not affiliated with UNHCR.
- Overview
- Tech Stack
- Architecture
- Project Structure
- GraphQL API
- Configuration
- Build & Run
- Docker
- Testing
- CORS & Error Handling
- Related Repositories
- License
- Purpose: Expose migration and refugee indicators through a GraphQL API.
- Data Sources: Uses UNHCR-derived data to power various indicators on asylum decisions, requests, resettlement summaries, IDP displacements, refugee returnees, and naturalizations.
- Design: The API uses a layered architecture that separates concerns among resolvers (GraphQL endpoints), services (business logic), repositories (data access), models (JPA entities), and mappers for DTO mapping.
- Programming Language: Java (version 17+)
- Frameworks/Libraries:
- Spring Boot
- Spring Data JPA
- Spring for GraphQL
- JUnit 5, Mockito
- Database: PostgreSQL (production) with H2 for testing
- Build Tool: Maven
- Containerization: Docker & Docker Compose
flowchart LR
A([UNHCR Statistics Portal]) --> B{{ETL: ingest • clean • normalize • derive metrics}}
B --> C[(DB)]
C --> D([GraphQL API<br/>Typed schema & resolvers])
D --> E([Client React<br/>Maps • Dashboards • Modals])
The API layer abstracts data retrieval and aggregation (e.g., grouping by country and year) to support the visualization of migration and refugee trends.
src/
├─ main/
│ ├─ java/com/beyondfootsteps/beyondfootsteps/
│ │ ├─ BeyondfootstepsApplication.java # Application entry-point
│ │ ├─ configuration/
│ │ │ └─ CorsConfig.java # CORS configuration
│ │ ├─ dto/
│ │ │ ├─ internal/ # Internal DTOs (e.g., for grouped responses)
│ │ │ └─ response/ # API response DTOs
│ │ ├─ exceptions/
│ │ │ ├─ CustomExceptionHandler.java # Global exception handler (@ControllerAdvice)
│ │ │ └─ InvalidParamException.java # Custom exception for invalid parameters
│ │ ├─ mappers/
│ │ │ └─ ResettlementSummaryOriginGroupedMapper.java # Mapper for entity-to-DTO conversion
│ │ ├─ models/ # JPA entity
│ │ │ ├─ AsylumDecision.java
│ │ │ ├─ AsylumRequest.java
│ │ │ ├─ DashboardSummary.java
│ │ │ ├─ IdpDisplacement.java
│ │ │ ├─ IdpReturnees.java
│ │ │ ├─ RefugeeNaturalization.java
│ │ │ └─ ResettlementSummary.java
│ │ ├─ repositories/ # Spring Data JPA repositories
│ │ │ ├─ AsylumDecisionRepository.java
│ │ │ ├─ AsylumRequestRepository.java
│ │ │ ├─ DashboardSummaryRepository.java
│ │ │ ├─ IdpDisplacementRepository.java
│ │ │ ├─ IdpReturneesRepository.java
│ │ │ ├─ RefugeeNaturalizationRepository.java
│ │ │ └─ ResettlementSummaryRepository.java
│ │ ├─ resolvers/ # GraphQL resolvers
│ │ │ ├─ AsylumDecisionResolver.java
│ │ │ ├─ AsylumRequestResolver.java
│ │ │ ├─ DashboardSummaryResolver.java
│ │ │ ├─ IdpDisplacementResolver.java
│ │ │ ├─ IdpReturneesResolver.java
│ │ │ ├─ RefugeeNaturalizationResolver.java
│ │ │ └─ ResettlementSummaryResolver.java
│ │ └─ services/ # Business logic (Services)
│ │ ├─ AsylumDecisionService.java
│ │ ├─ AsylumRequestService.java
│ │ ├─ DashboardSummaryService.java
│ │ ├─ IdpDisplacementService.java
│ │ ├─ IdpReturneesService.java
│ │ ├─ RefugeeNaturalizationService.java
│ │ └─ ResettlementSummaryService.java
│ └─ resources/
│ ├─ application.properties # General application configuration
│ ├─ graphql/
│ │ └─ schema.graphqls # GraphQL schema definition
│ ├─ static/
│ └─ templates/
└─ test/
├─ java/com/beyondfootsteps/beyondfootsteps/
│ ├─ BeyondfootstepsApplicationTests.java # Spring Boot context test
│ ├─ mapper/
│ │ └─ ResettlementSummaryOriginGroupedMapperTest.java
│ ├─ repository/ # Repository tests with H2
│ │ ├─ AsylumDecisionRepositoryTest.java
│ │ ├─ AsylumRequestRepositoryTest.java
│ │ ├─ DashboardSummaryRepositoryTest.java
│ │ ├─ RefugeeNaturalizationRepositoryTest.java
│ │ └─ ResettlementSummaryRepositoryTest.java
│ ├─ resolvers/ # GraphQL resolver tests
│ │ ├─ AsylumDecisionResolverTest.java
│ │ ├─ AsylumRequestResolverTest.java
│ │ ├─ DashboardSummaryResolverTest.java
│ │ ├─ IdpDisplacementResolverTest.java
│ │ ├─ IdpReturneesResolverTest.java
│ │ ├─ RefugeeNaturalizationResolverTest.java
│ │ └─ ResettlementSummaryResolverTest.java
│ └─ services/ # Service layer tests; business logic validation
│ ├─ AsylumDecisionServiceTest.java
│ ├─ AsylumRequestServiceTest.java
│ ├─ DashboardSummaryServiceTest.java
│ ├─ IdpDisplacementServiceTest.java
│ ├─ IdpReturneesServiceTest.java
│ ├─ RefugeeNaturalizationServiceTest.java
│ └─ ResettlementSummaryServiceTest.java
- Endpoint:
/graphql(HTTP POST) - Schema: Located in
src/main/resources/graphql/schema.graphqls - GraphiQL: Optionally available if enabled (e.g., at
/graphiql)
query GetAsylumDecisionsByYearAndCountry(
$year: Int!
$countryOfOriginIso: String
$countryOfAsylumIso: String
) {
asylumDecisionsByYearAndCountry(
year: $year
countryOfOriginIso: $countryOfOriginIso
countryOfAsylumIso: $countryOfAsylumIso
) {
id
year
countryOfOrigin
countryOfOriginIso
countryOfAsylum
countryOfAsylumIso
decRecognized
decOther
decRejected
decClosed
decTotal
acceptanceRate
decPc
}
}query getResettlementSummariesByYearGroupedBy($year: Int!, $grouping: String!) {
resettlementSummariesByYearGroupedBy(year: $year, grouping: $grouping) {
countriesIso
countriesNames
totalCases
totalDepartures
totalPersons
totalNeeds
totalSubmissions
coverageRate
resettlementGap
requestVsNeedsRatio
submissionsEfficiency
realizationRate
}
}- General Config: Located in
src/main/resources/application.properties - Test Config: Use
src/test/resources/application-test.properties(for example, setspring.jpa.hibernate.ddl-auto=createto auto-generate database schema in H2)
If a column name conflicts with reserved keywords (e.g., year), escape it in your entities:
@Column(name = "\"year\"")
private Integer year;This ensures compatibility with both H2 (for tests) and PostgreSQL.
-
Run in Development Mode:
.\mvnw.cmd spring-boot:run
-
Package the Application:
.\mvnw.cmd package java -jar target\beyondfootsteps-<version>.jar
The project includes a Dockerfile to containerize the application.
-
Test Execution:
It is recommended to remove the-DskipTestsparameter so that tests run during the image build. -
Building with Docker:
RUN --mount=type=bind,source=pom.xml,target=pom.xml \ --mount=type=cache,target=/root/.m2 \ ./mvnw package && \ mv target/$(./mvnw help:evaluate -Dexpression=project.artifactId -q -DforceStdout)-$(./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout).jar target/app.jar
docker compose up --buildThe project includes a comprehensive suite of tests across different layers:
- Service Tests: Validate business logic (e.g.,
AsylumDecisionServiceTest.java,ResettlementSummaryServiceTest.java). - Resolver Tests: Test GraphQL endpoint integration (e.g.,
RefugeeNaturalizationResolverTest.java,ResettlementSummaryResolverTest.java). - Repository Tests: Use H2 with
@DataJpaTestto verify repository queries, including handling of null parameters.
Example:AsylumDecisionRepositoryTest.javaensures queries correctly return results when filter parameters are null. - Mapper Tests: Ensure entities are correctly mapped to response DTOs (e.g.,
ResettlementSummaryOriginGroupedMapperTest.java). - Application Context Tests: Check that the Spring Boot context loads without issues (e.g.,
BeyondfootstepsApplicationTests.java).
Run all tests with:
.\mvnw.cmd test- CORS: Configured via
configuration/CorsConfig.java. Adjust allowed origins, methods, and headers as needed. - Global Error Handling: Implemented in
exceptions/CustomExceptionHandler.java, ensuring smooth error responses for GraphQL and REST endpoints.
- Frontend Client: beyondfootsteps-client
- ETL Pipeline: beyondfootsteps-etl
MIT License – see the LICENSE file for details.