Skip to content

Crypto Portfolio API

By:  Al-Fahami Toihir   🏷️ Spring •  ⏱️ ~6 min read

"Built to learn, WebClient, reactive programming, and real API integration, not just theory. The kind of project you do on a job test."


What Is It

The Crypto Portfolio API lets users manage portfolios of cryptocurrency holdings. Users can create multiple portfolios, add holdings to each, and get their total valuation in different currencies (USD, MAD, EUR, etc.).

The app consists of two services:

  • Portfolio Service (port 8080) : manages users, portfolios, and holdings
  • Exchange Rate Service (port 8081) : fetches real-time crypto prices from CoinMarketCap

Architecture Note

While the services run on separate ports and follow a modular structure, this is not a full microservices architecture. It's a step toward understanding service separation and inter-service communication via WebClient.


Tech Stack

Layer Technology
Language Java 17
Framework Spring Boot 3.4.1
Build Maven
Database H2 (in-memory)
Persistence Hibernate / JPA
HTTP Client WebClient (reactive)
Testing JUnit, Mockito, WebTestClient, MockWebServer
External API CoinMarketCap

Architecture

crypto-portfolio-api/
├── exchangerateservice/    # Fetches crypto prices from CoinMarketCap
│   └── port: 8081
└── portfolioservice/       # Manages users, portfolios, holdings
    └── port: 8080

The Portfolio Service calls the Exchange Rate Service via WebClient to fetch live prices when calculating portfolio valuations.


API Endpoints

Exchange Rate Service

GET /exchange-rate/latest                        # latest rates for all supported cryptos
GET /exchange-rate?symbol={symbol}&base={base}   # price for a specific crypto in a base currency

Portfolio Service

POST   /users                                                          # create user
GET    /users/{userId}                                                 # get user
PATCH  /users/{userId}                                                 # update user
DELETE /users/{userId}                                                 # delete user
GET    /users/{userId}/portfolios/all                                  # all portfolios for a user

POST   /users/{userId}/portfolios                                      # create portfolio
GET    /users/{userId}/portfolios/{portfolioId}                        # get portfolio
PATCH  /users/{userId}/portfolios/{portfolioId}                        # update portfolio
DELETE /users/{userId}/portfolios/{portfolioId}                        # delete portfolio

POST   /users/{userId}/portfolios/{portfolioId}/holdings               # add holding
GET    /users/{userId}/portfolios/{portfolioId}/holdings/{symbol}      # get holding
PATCH  /users/{userId}/portfolios/{portfolioId}/holdings/{symbol}      # update holding
DELETE /users/{userId}/portfolios/{portfolioId}/holdings/{symbol}      # remove holding
GET    /users/{userId}/portfolios/{portfolioId}/holdings/all           # all holdings
GET    /users/{userId}/portfolios/{portfolioId}/valuation?base={base}  # portfolio value

Running It

# Clone
git clone git@github.com:alfahami/crypto-portfolio-api.git
cd crypto-portfolio-api

# Terminal 1: Exchange Rate Service
cd exchangerateservice
mvn clean install
mvn spring-boot:run

# Terminal 2: Portfolio Service
cd portfolioservice
mvn clean install
mvn spring-boot:run

Run tests:

cd exchangerateservice && mvn test
cd portfolioservice && mvn test

A Postman collection is included in the repo: crypto-portfolio.postman_collection.json


Key Challenges & Decisions

1. WebClient and Reactive Programming

The Exchange Rate Service uses WebClient instead of RestTemplate to call CoinMarketCap. This was the main learning objective of the project; understanding how reactive HTTP clients work, how to handle responses, and how to test them with WebTestClient and MockWebServer.

2. User → Portfolio → Holding Validation

When managing holdings, every operation needed to validate that the holding belongs to the right portfolio and the portfolio belongs to the right user. Three approaches were tried:

  • Injecting UserRepository directly into HoldingService ; works but mixes concerns
  • Injecting PortfolioServiceImp into HoldingService ; creates tight coupling
  • Creating a dedicated ValidationAuthorizationService ; cleanest approach, respects Single Responsibility Principle

3. Preventing ID Tampering on Updates

When updating an entity via PATCH, there's a risk of ID mismatch between the URI and the request body. The solution: ignore the ID from the request body entirely, retrieve the entity using the URI ID, validate, then update and save the retrieved object.

4. MockWebServer Management in Tests

Used MockWebServer from OkHttp to simulate the Exchange Rate Service in Portfolio Service integration tests. Key practices:

  • Start server once with @BeforeAll, shut down with @AfterAll
  • Each test enqueues its own response for predictable behavior
  • Only one test relied on the external service so no manual request clearing was needed

Key Learnings

  • WebClient : reactive HTTP client, how it differs from RestTemplate, and how to test it properly
  • MockWebServer : simulating external APIs in integration tests without hitting real endpoints
  • Inter-service communication : how two Spring Boot services talk to each other via REST
  • JPA relationships : designing User → Portfolio → Holding entity relationships with proper cascade and validation
  • Global exception handling : using @ControllerAdvice for consistent error responses across all endpoints
  • Transaction management : ensuring atomic updates when modifying related entities

While building this project I went deep on WebClient and reactive programming : a dedicated article on that is coming to the Spring section.



License

MIT - open source and free to use.

Categories