A RESTful API built with ASP.NET Core (.NET 10) that allows users to manage and track their stock portfolio with real-time pricing and gain/loss calculations.
Built as a demonstration of .NET backend development skills including authentication, database management, external API integration, and caching.
- User Authentication — Register and login with JWT token-based auth
- Password Security — Passwords are hashed with BCrypt, never stored in plain text
- Portfolio Management — Create a portfolio and manage stock holdings
- Real-Time Stock Prices — Live prices fetched from Alpha Vantage API
- Gain/Loss Calculations — Automatically calculates current value, gain/loss and percentage per holding and for the overall portfolio
- In-Memory Caching — Stock prices are cached for 60 seconds to reduce external API calls and respect rate limits
- API Call Tracking — Every real call to Alpha Vantage is logged to the database so you can monitor daily usage
- Input Validation — All requests are validated before hitting the database
- Global Error Handling — Unhandled exceptions return clean error responses
| Technology | Purpose |
|---|---|
| ASP.NET Core (.NET 10) | Web API framework |
| Entity Framework Core | ORM for database access |
| SQLite | Database (easily swappable for SQL Server) |
| JWT Bearer Tokens | Authentication |
| BCrypt.Net | Password hashing |
| Alpha Vantage API | Real-time stock prices |
| IMemoryCache | In-memory caching |
The project follows a layered architecture to keep concerns separated:
Controllers → receive HTTP requests, return responses
Services → contain all business logic
Data → database context and access
Models → data shapes and request/response objects
Logs → temporary tracking models (e.g. API call logging)
User
└── Portfolio
└── Holdings (Symbol, Quantity, BuyPrice)
ApiCallLog (separate — tracks Alpha Vantage usage)
- A User has one Portfolio
- A Portfolio has many Holdings
- Each Holding tracks a stock symbol, quantity owned, and the price it was bought at
- ApiCallLog records every real call made to Alpha Vantage (cached calls are not logged)
| Method | Endpoint | Description | Auth Required |
|---|---|---|---|
| POST | /api/auth/register |
Register a new user, returns JWT token | No |
| POST | /api/auth/login |
Login, returns JWT token | No |
| Method | Endpoint | Description | Auth Required |
|---|---|---|---|
| GET | /api/portfolio |
Get raw portfolio and holdings | Yes |
| POST | /api/portfolio |
Create a portfolio | Yes |
| GET | /api/portfolio/summary |
Get portfolio with live prices and gain/loss | Yes |
| POST | /api/portfolio/holdings |
Add a stock holding | Yes |
| DELETE | /api/portfolio/holdings/{id} |
Remove a stock holding | Yes |
| Method | Endpoint | Description | Auth Required |
|---|---|---|---|
| GET | /api/logs/usage |
Check how many Alpha Vantage calls made today | Yes |
| GET | /api/logs |
View all API call logs, newest first | Yes |
| Method | Endpoint | Description | Auth Required |
|---|---|---|---|
| GET | /api/admin/portfolios |
All portfolios with owner info and holdings | Yes |
| GET | /api/admin/users |
All registered users (passwords never exposed) | Yes |
| Method | Endpoint | Description | Auth Required |
|---|---|---|---|
| DELETE | /api/admin/reset/holdings |
Clear all holdings | Yes |
| DELETE | /api/admin/reset/portfolios |
Clear all portfolios and holdings | Yes |
| DELETE | /api/admin/reset/users |
Clear all users, portfolios and holdings | Yes |
| DELETE | /api/admin/reset/logs |
Clear all API call logs | Yes |
| DELETE | /api/admin/reset/all |
Full wipe — clears everything | Yes |
- .NET 10 SDK
- An Alpha Vantage API key (free)
- Postman for testing
- Clone the repository
git clone <your-repo-url>
cd StockPortfolioTracker- Add your Alpha Vantage API key to
appsettings.json
"AlphaVantage": {
"ApiKey": "YOUR_API_KEY_HERE"
}- Apply database migrations
dotnet ef database update- Run the app
dotnet runThe API will be available at http://localhost:5104
- Method:
POST - URL:
http://localhost:5104/api/auth/register - Body → raw → JSON:
{
"username": "testuser",
"email": "test@example.com",
"password": "Password123!"
}Copy the token from the response — you'll need it for every request below.
- Method:
POST - URL:
http://localhost:5104/api/portfolio - Authorization tab → Bearer Token → paste your token
- Body → raw → JSON:
"My Portfolio"- Method:
POST - URL:
http://localhost:5104/api/portfolio/holdings - Authorization tab → Bearer Token → paste your token
- Body → raw → JSON:
{
"symbol": "TSLA",
"companyName": "Tesla Inc.",
"quantity": 5,
"buyPrice": 200.00
}- Method:
GET - URL:
http://localhost:5104/api/portfolio/summary - Authorization tab → Bearer Token → paste your token
Expected response:
{
"name": "My Portfolio",
"holdings": [
{
"symbol": "TSLA",
"quantity": 5,
"buyPrice": 200.00,
"currentPrice": 371.75,
"currentValue": 1858.75,
"costBasis": 1000.00,
"gainLoss": 858.75,
"gainLossPercent": 85.88
}
],
"totalCostBasis": 1000.00,
"totalCurrentValue": 1858.75,
"totalGainLoss": 858.75
}- Method:
GET - URL:
http://localhost:5104/api/logs/usage - Authorization tab → Bearer Token → paste your token
Expected response:
{
"callsToday": 1,
"remainingEstimate": 24,
"resetNote": "Alpha Vantage resets your 25 daily calls every 24 hours on their end."
}Note: Only calls that bypass the cache are logged. If you request the same stock twice within 60 seconds, only the first call is counted.
You can inspect the SQLite database visually using DB Browser for SQLite:
- Download from
https://sqlitebrowser.org/dl/ - Open the app and click Open Database
- Navigate to your project folder and select
portfolio.db:
/Users/omarwaseem/projects/Stock_Portfolio_Tracker_API/StockPortfolioTracker/portfolio.db
- Click the Browse Data tab
- Use the Table dropdown to switch between:
Users— all registered accountsPortfolios— all portfoliosHoldings— all stock holdingsApiCallLogs— every Alpha Vantage call made
You can also run raw SQL queries under the Execute SQL tab.
The free tier allows 25 API calls per day. This app handles this in two ways:
- Caching — prices are stored in memory for 60 seconds, so repeated requests don't use extra calls
- Call logging — every real API call is recorded in the database so you can track daily usage via
GET /api/logs/usage
If you exceed the limit, currentPrice will return null in the summary response. The limit resets every 24 hours on Alpha Vantage's end.
Why JWT? JWT tokens are stateless — the server doesn't need to store session data. Each token contains the user's identity as claims, which we read on every request to identify who is making it.
Why BCrypt? BCrypt is a one-way hashing algorithm designed specifically for passwords. Even if the database is compromised, passwords cannot be reversed.
Why caching? Alpha Vantage's free tier allows 25 requests per day. Caching prices for 60 seconds means repeated requests for the same stock don't burn through the limit. In a production environment this would be replaced with a distributed cache like Redis.
Why SQLite? SQLite requires zero setup and runs as a single file — ideal for development and demos. Since EF Core abstracts the database, switching to SQL Server for production requires only a configuration change.
Why a separate Logs folder?
The ApiCallLog model is intentionally kept in a Logs/ folder separate from core Models to signal that it is a utility/tracking concern rather than a core domain model. In production this would be replaced with a proper observability solution like Serilog or Application Insights.
| Method | URL | What it returns |
|---|---|---|
| GET | /api/admin/portfolios |
All portfolios with owner info and holdings |
| GET | /api/admin/users |
All registered users (passwords never exposed) |
| Method | URL | What it resets |
|---|---|---|
| DELETE | /api/admin/reset/holdings |
Holdings only |
| DELETE | /api/admin/reset/portfolios |
Portfolios + holdings |
| DELETE | /api/admin/reset/users |
Users + portfolios + holdings |
| DELETE | /api/admin/reset/logs |
API call logs only |
| DELETE | /api/admin/reset/all |
Everything — full wipe |
Note: Admin endpoints are currently protected by authentication only — any logged in user can access them. Role-based authorization (
[Authorize(Roles = "Admin")]) is planned for a future update so only designated admin accounts can access these endpoints.
Planned improvements for future versions:
- Role-based authorization — Assign
"Admin"and"User"roles at registration. Admin endpoints will be restricted to Admin role only via[Authorize(Roles = "Admin")] - Azure deployment — The API is planned to be hosted on Azure App Service, making it publicly accessible. EF Core migrations will be run against Azure SQL replacing the current SQLite database
- Redis for distributed caching instead of in-memory
- Rate limiting on API endpoints
- Refresh tokens alongside JWT for better session management
- Serilog or Application Insights for production-grade logging
Things that would be added in a real production environment:
- Redis for distributed caching instead of in-memory
- SQL Server or PostgreSQL instead of SQLite
- Rate limiting on API endpoints
- HTTPS enforcement in all environments
- Refresh tokens alongside JWT for better session management
- Serilog or Application Insights instead of the custom API call log