Skip to content

fikrimusa/PulseGuardAPI

Repository files navigation

PulseGuard API

Continuous Integration

PulseGuard API is an ASP.NET Core Web API portfolio project for monitoring website and API health endpoints. Its intended responsibility is to record uptime history and identify repeated check failures so they can become actionable alerts.

Current status: MVP backend stage. PulseGuard API includes Swagger, PostgreSQL persistence, JWT authentication, user-owned monitors, scheduled health checks, persisted alerts, a user-scoped dashboard, Docker Compose local development, automated tests, GitHub Actions CI, and a manual AWS EC2 deployment guide. External alert delivery and production deployment automation are not implemented yet.

Project overview

PulseGuard API provides a backend foundation for defining health monitors, executing checks, storing their outcomes, and exposing a service's current and historical status.

Tech stack

  • Framework: ASP.NET Core Web API (.NET 8)
  • Language: C#
  • API documentation: Swagger / OpenAPI via Swashbuckle
  • Persistence: Entity Framework Core 8 with the Npgsql PostgreSQL provider
  • Authentication: JWT bearer tokens with ASP.NET Core password hashing
  • Health checks: Hosted background service using HttpClientFactory
  • Containerisation: Docker and Docker Compose
  • Testing: xUnit, FluentAssertions, ASP.NET Core integration testing, and EF Core InMemory
  • Continuous integration: GitHub Actions
  • Architecture: Controller, service, repository, data, model, DTO, and configuration layers

The following are intentionally not part of the current implementation: Redis and external alert delivery.

Architecture overview

flowchart LR
    Client[Swagger or API client] --> Controllers[ASP.NET Core controllers]
    Controllers --> Services[Application services]
    Services --> Repositories[Repositories]
    Repositories --> DbContext[EF Core AppDbContext]
    DbContext --> Database[(PostgreSQL)]

    Worker[Health check background worker] --> HttpClient[HttpClientFactory]
    HttpClient --> Endpoints[Monitored websites and APIs]
    Worker --> AlertState[Alert state service]
    Worker --> DbContext
    AlertState --> DbContext
Loading

How the architecture works

The diagram has two main flows: requests made by a user and automatic checks made by the application.

API request flow

For example, when an authenticated user creates a monitor from Swagger:

  1. Swagger or an API client sends an HTTP request such as POST /api/monitors.
  2. An ASP.NET Core controller receives the request. Controllers are responsible for HTTP concerns: routes, request validation, response status codes, and authentication requirements.
  3. The controller passes the work to an application service. Services contain the business rules, such as ensuring that a user can access only their own monitors.
  4. The service uses a repository to read or write monitor, alert, or check data.
  5. The repository uses EF Core's AppDbContext, which converts C# queries and entities into SQL.
  6. PostgreSQL stores the persistent data: users, monitors, monitor checks, and alerts.

This separation keeps HTTP code, business rules, and database access from being mixed together in one controller.

Automatic health-check flow

The hosted health-check background worker starts when the API starts. It is not triggered by Swagger and does not need a user request.

  1. The worker finds enabled monitors whose checkIntervalSeconds is due.
  2. It uses HttpClientFactory to send an HTTP GET request to each monitored website or API URL.
  3. The monitored endpoint returns a response, such as HTTP 200, 404, 500, or a timeout/connection error.
  4. The worker compares the outcome with the monitor's expected status code and timeout settings.
  5. It saves a MonitorCheck result through AppDbContext, including success/failure, status code, response time, and any error message.

Alert state flow

After each health check, the worker passes the result to the alert-state service:

  1. The service counts the monitor's most recent consecutive failures.
  2. At three consecutive failures, it creates one OPEN alert. Further failures update that same alert instead of creating duplicates.
  3. A user can acknowledge an open alert, changing it to ACKNOWLEDGED.
  4. When a later health check succeeds, an OPEN or ACKNOWLEDGED alert changes to RESOLVED automatically.
  5. Each alert-state change is saved to PostgreSQL through AppDbContext.

In short: users manage monitoring settings through the API, while the background worker continuously checks those settings and records the system's health automatically.

Current API

Method Endpoint Description
GET /api/health Returns the API status and the current UTC timestamp.
POST /api/auth/register Creates a user account and returns a JWT.
POST /api/auth/login Authenticates a user and returns a JWT.
POST /api/monitors Creates a persisted monitor.
GET /api/monitors Lists persisted monitors.
GET /api/monitors/{id} Retrieves a monitor by ID.
PUT /api/monitors/{id} Replaces a monitor's editable settings.
DELETE /api/monitors/{id} Deletes a monitor.
GET /api/monitors/{id}/checks Retrieves health-check history for an owned monitor.
GET /api/alerts Lists alerts for monitors owned by the authenticated user.
GET /api/alerts/{id} Retrieves an owned alert.
PUT /api/alerts/{id}/acknowledge Acknowledges an open owned alert.
GET /api/dashboard/summary Returns the authenticated user's monitor, alert, and recent-check summary.

Example response:

{
  "status": "Healthy",
  "timestampUtc": "2026-06-20T12:34:56.789Z"
}

Swagger UI is available at /swagger while the API is running.

Deployment documentation

See the AWS EC2 deployment plan for a beginner-friendly, manual Docker Compose deployment guide. It documents the required AWS resources, environment variables, security-group rules, migrations, and a future RDS path; it does not add deployment automation.

Features

Implemented MVP features

  • Register users and issue JWT bearer tokens using hashed passwords.
  • Create, list, retrieve, update, and delete user-owned monitors persisted in PostgreSQL.
  • Run due checks for enabled monitors and persist status, latency, and failure details.
  • Open, acknowledge, and automatically resolve monitor failure alerts.
  • Summarise monitor health, alert counts, and recent activity for the authenticated user.
  • Configure a monitor name, endpoint URL, check interval, and active state.
  • Run automated unit and integration tests for authentication, ownership, alert state changes, and dashboard summaries.
  • Build and test the solution automatically on pushes and pull requests to main.
  • Document a manual AWS EC2 deployment using Docker Compose, with a future RDS migration path.

Future features

  • Alert delivery through email, webhooks, Slack, or Discord.
  • Redis-backed caching or job coordination where justified.
  • HTTPS through a reverse proxy and production deployment automation.
  • Managed AWS database infrastructure with Amazon RDS.
  • Uptime reporting, performance summaries, teams, and role-based access.

How to run locally

Prerequisites

  • .NET 8 SDK
  • PostgreSQL 14 or later

Configure PostgreSQL

Create a local database and user:

sudo -u postgres psql
CREATE USER pulseguard_user WITH PASSWORD 'replace-with-a-local-password';
CREATE DATABASE pulseguard OWNER pulseguard_user;
\q

Set the connection string for the current terminal. This overrides the placeholder value in appsettings.json and avoids committing a real password:

export ConnectionStrings__PulseGuardDatabase='Host=localhost;Port=5432;Database=pulseguard;Username=pulseguard_user;Password=replace-with-a-local-password'

On Ubuntu, PostgreSQL's default local peer authentication can be used instead of a password. After creating a matching PostgreSQL role and database, use the Unix socket connection string:

sudo -u postgres createuser --login "$USER"
sudo -u postgres createdb --owner="$USER" pulseguard
export ConnectionStrings__PulseGuardDatabase="Host=/var/run/postgresql;Port=5432;Database=pulseguard;Username=$USER"

Configure JWT

appsettings.json contains a development-only JWT key so the project can run locally. Override it for any environment that is shared or deployed:

export Jwt__Key="$(openssl rand -base64 48)"

Do not commit a real JWT signing key.

Install the EF Core CLI tool once (the major version must match EF Core 8):

dotnet tool install --global dotnet-ef --version 8.0.11

Apply the committed database migration:

dotnet ef database update

Create a new migration after changing an EF Core model:

dotnet ef migrations add DescriptiveMigrationName

Run with Docker

Prerequisites

  • Docker Engine with either the Docker Compose plugin (docker compose) or the legacy CLI (docker-compose)

Start the API and PostgreSQL containers:

docker compose up --build

For Docker Compose 1.x, use the equivalent command:

docker-compose up --build

The API uses the Compose service name postgres to connect to PostgreSQL. PostgreSQL is exposed on host port 5433 to avoid conflicts with a local PostgreSQL instance on port 5432.

Apply EF Core migrations from the host after the containers are running:

ConnectionStrings__PulseGuardDatabase='Host=localhost;Port=5433;Database=pulseguard;Username=pulseguard;Password=pulseguard_dev_password' dotnet ef database update

Open Swagger at http://localhost:8080/swagger.

Stop the containers:

docker compose down

For Docker Compose 1.x:

docker-compose down

To also delete the Docker PostgreSQL volume and all local container data:

docker compose down --volumes

For Docker Compose 1.x:

docker-compose down -v

Run the API

dotnet restore
dotnet build
dotnet run

Open the following URLs after the server starts:

  • Swagger UI: http://localhost:5054/swagger
  • Health endpoint: http://localhost:5054/api/health

To call the health endpoint from a terminal:

curl http://localhost:5054/api/health

Run automated tests

Run the backend test suite from the repository root:

dotnet test PulseGuard.sln

The tests use an in-memory database and cover authentication, monitor ownership, alert state transitions, dashboard aggregation, and basic API behavior without requiring PostgreSQL or Docker.

The suite currently validates:

  • GET /api/health returns a successful response.
  • Registering and logging in a user returns a JWT access token.
  • Protected monitor endpoints reject requests without a JWT token.
  • A user cannot retrieve a monitor owned by another user.
  • Three consecutive failed checks create one OPEN alert; a later successful check resolves it.
  • Acknowledging an alert changes its status to ACKNOWLEDGED and records the acknowledgement time.
  • Dashboard totals and average response time are calculated from the current user's monitors, checks, and alerts only.

For the name and result of each test, run:

dotnet test PulseGuard.sln --logger "console;verbosity=detailed"

Authenticate in Swagger

  1. Open http://localhost:5054/swagger.
  2. Call POST /api/auth/register with an email and a password of at least eight characters.
  3. Copy accessToken from the response.
  4. Click Authorize, enter the token, and confirm.
  5. Call the protected /api/monitors endpoints. Each user can access only their own monitors.

Health checks

The background worker starts with the API. It polls every 10 seconds by default and performs a check only when an enabled monitor's checkIntervalSeconds is due. Each check sends an HTTP GET request and compares its response status with expectedStatusCode.

Monitor create and update requests support these health-check settings:

{
  "checkIntervalSeconds": 60,
  "timeoutSeconds": 10,
  "expectedStatusCode": 200,
  "isActive": true
}

Set HealthCheckWorker__PollingIntervalSeconds to change the local polling frequency. This does not override the per-monitor checkIntervalSeconds setting.

Alerts

An alert opens after three consecutive failed checks for the same monitor. Only one active alert can exist per monitor: additional failures update its failureCount instead of creating duplicates. A successful check automatically changes an OPEN or ACKNOWLEDGED alert to RESOLVED.

Alert statuses are OPEN, ACKNOWLEDGED, and RESOLVED. Alert endpoints return only alerts that belong to the authenticated user's monitors.

Dashboard summary

GET /api/dashboard/summary returns counts for the authenticated user's monitors and alerts, the latest status of each monitor, average response time from the latest checks, and up to ten recent alerts and checks. A monitor is UNKNOWN until it has a check result.

Project roadmap

  • Create ASP.NET Core Web API foundation and health endpoint.
  • Enable Swagger / OpenAPI documentation.
  • Define the initial monitor model and in-memory CRUD endpoints.
  • Add PostgreSQL persistence and the initial EF Core migration.
  • Add JWT authentication and user-owned monitor access.
  • Add scheduled health checks and persisted check history.
  • Add consecutive-failure alerts with acknowledgement and automatic resolution.
  • Add a user-scoped dashboard summary API.
  • Add Docker support for local development.
  • Add automated tests for services and API endpoints.
  • Add GitHub Actions CI pipeline.
  • Add AWS deployment documentation.

Portfolio purpose

PulseGuard API demonstrates backend engineering skills relevant to Backend Engineer, Software Engineer, and C# .NET Developer roles. The project covers REST API design, layered architecture, PostgreSQL data modelling, JWT security, background processing, reliability-oriented alert workflows, testing, CI, Docker-based local development, and production-minded deployment planning.

About

PulseGuard API is an ASP.NET Core Web API portfolio project for monitoring website and API health endpoints. Its intended responsibility is to record uptime history and identify repeated check failures so they can become actionable alerts.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors