Skip to content

Simple Event Handler is a small, opinionated Go application that accepts events over an HTTP API, performs light processing, and runs periodic aggregation tasks in the background.

License

Notifications You must be signed in to change notification settings

arimatakao/simple-events-handler

Repository files navigation

Simple Event Handler

Simple Event Handler is a small, opinionated Go application that accepts events over an HTTP API, performs light processing, and runs periodic aggregation tasks in the background.

Key characteristics:

  • HTTP API server that receives and handles event payloads.
  • Background aggregator (cron-like) that runs periodic jobs (start/stop controlled in code).
  • Structured JSON logging used throughout the server and background tasks.
  • Graceful shutdown support to let in-flight requests finish and to stop background jobs cleanly.

Also you can use frontend: https://github.com/arimatakao/simple-events-handler-client

Original repository: https://github.com/arimatakao/events-handler However, the code was written poorly, so I cleaned it up.

Clone this repository

With submodules:

git clone --recurse-submodules https://github.com/arimatakao/simple-events-handler.git

Without submodules:

git clone https://github.com/arimatakao/simple-events-handler.git

Environment variables

The application uses environment variables to configure the HTTP server, aggregation scheduler, time zone, and database connection. For development you can copy .env.example to .env and adjust values.

  • PORT (int, default: 8080)

    • TCP port the HTTP server will listen on.
  • BASE_PATH (string, default: /api)

    • Base route prefix for all HTTP endpoints (e.g. /api). If empty, routes are served from root.
  • AGGREGATION_INTERVAL_SECONDS (int, default: 30)

    • How often (in seconds) the background aggregator should run. Must be a positive integer. The aggregator will run approximately every N seconds.
  • IDLE_TIMEOUT_SECONDS (int, default: 60)

    • HTTP server idle timeout in seconds (max time to keep idle connections open).
  • READ_TIMEOUT_SECONDS (int, default: 10)

    • Maximum duration in seconds for reading the entire request, including the body.
  • WRITE_TIMEOUT_SECONDS (int, default: 30)

    • Maximum duration in seconds before timing out writes of the response.
  • TZ (string, example: Europe/Kiev)

    • Time zone used by containers / scripts that respect TZ. Not strictly required by the app, but useful in Docker setups and examples.

Database connection variables (used by the app and docker-compose):

  • DB_HOST (string, default: localhost)

    • Hostname or IP of the Postgres server.
  • DB_PORT (int, default: 5432)

    • Port on which Postgres is listening. In docker-compose this maps host port to container 5432.
  • DB_DATABASE (string, example: events)

    • Name of the Postgres database to connect to.
  • DB_USERNAME (string)

    • Database username.
  • DB_PASSWORD (string)

    • Database password.
  • DB_SCHEMA (string, default: public)

    • Postgres search_path/schema to use (the code appends this to the connection string).

Notes and behavior:

  • The application reads values with os.Getenv and falls back to simple defaults where appropriate. Numeric values are parsed with strconv.Atoi; invalid numeric values will typically fall back to the default or log a warning (see source).
  • For local development you can populate a .env file from .env.example. When running in Docker, docker-compose reads environment variables or uses the values from an .env file in the compose directory.
  • Keep credentials (DB_USERNAME, DB_PASSWORD) out of version control; use environment-specific secrets or a vault in production.

Docker compose services

The repository provides a docker-compose.yml that starts several services useful for development and local testing. Running docker compose up (or docker-compose up) from the project root will create and start the following containers:

  • simple-events-handler — the main Go application, built from the project's Dockerfile; it listens on the port configured by the PORT env var (mapped to host 8080:8080 in the compose file).
  • react-client — a static React frontend (from other/react-client) served by an nginx container; the compose file builds this image and maps it to host port 3000.
  • db (postgres_db) — a Postgres 15 database used by the application. The compose file initializes the database using other/init_tables.sql and exposes the container port so you can connect using the host port defined in your .env (default 5432).
  • pgadmin (pgadmin4) — pgAdmin web UI (default mapped to host port 8081) for managing the Postgres instance.
  • prometheus — Prometheus server (mapped to host port 9090) using the bundled other/prometheus.yml for scraping metrics.
  • grafana — Grafana server (mapped to host port 3001) for dashboards and visualizing Prometheus metrics.

Notes:

  • The compose file expects environment variables (database credentials, ports, time zone, etc.) — see .env.example for recommended values. Copy it to .env and adjust as needed for docker compose up to pick them up.
  • simple-events-handler depends on db and pgadmin in the compose file; react-client depends on simple-events-handler so the frontend can call the API by service name when running together in the compose network.
  • To start only the database for integration testing you can run: docker compose up db.

Web UI credentials

The compose file includes two web UIs (pgAdmin and Grafana). Default credentials exposed in the compose file are:

Postgres credentials (used by the db container and by the application) come from environment variables defined in your .env file (see .env.example). By default in .env.example the values are:

  • DB_DATABASE=events
  • DB_USERNAME=username
  • DB_PASSWORD=password

Make sure to update .env with secure values in any environment that is not local development.

Examples usage

You can use the Postman collection located at ./other/postman_collection.json

Below are simple examples showing how to send events to the API and how to query them. These examples assume the server is running locally on port 8080 and the BASE_PATH is /api.

  1. Create an event (POST /api/events)

Request:

curl -i -X POST "http://localhost:8080/api/events" \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": 123,
    "action": "login",
    "metadata": {"page":"/home"}
  }'

Successful response (status only, empty body):

HTTP/1.1 201 Created
Content-Type: application/json
...headers...

Notes:

  • The server returns 201 Created with an empty body on success (the handler sets StatusCreated).
  • If the JSON is invalid or required fields are missing you'll get a 400 response with details.

Example error (invalid JSON):

HTTP/1.1 400 Bad Request
Content-Type: application/json

{"error":"invalid request","details":"invalid character '...' looking for beginning of object key string"}

Example error (validation failed):

HTTP/1.1 400 Bad Request
Content-Type: application/json

{"error":"validation failed","details":"user_id must be a positive integer"}
  1. Query events (GET /api/events)

Basic query (time range required):

curl -i "http://localhost:8080/api/events?user_id=123&from=2025-01-01T00:00:00Z&to=2025-01-02T00:00:00Z"

Successful response (200 OK, JSON array of events):

HTTP/1.1 200 OK
Content-Type: application/json

[
  {
    "id": 1,
    "user_id": 123,
    "action": "login",
    "metadata": {"page":"/home"},
    "created_at": "2025-01-01T12:34:56Z"
  },
  {
    "id": 2,
    "user_id": 123,
    "action": "purchase",
    "metadata": {"item":"/home"},
    "created_at": "2025-01-01T13:00:00Z"
  }
]

Notes:

  • The from and to parameters accept multiple common time formats (RFC3339, "2006-01-02 15:04:05", date-only etc.).
  • The server also attempts to unescape URL-encoded timestamps (useful if your client double-encodes query params).

Example error (missing/invalid times):

HTTP/1.1 400 Bad Request
Content-Type: application/json

{"error":"invalid time format","details":"invalid from parameter: unrecognized time format: \"...\""}

Example error (server/database issue):

HTTP/1.1 500 Internal Server Error
Content-Type: application/json

{"error":"failed to fetch events"}

MakeFile

Run build make command with tests

make all

Build the application

make build

Run the application

make run

Create DB container

make docker-run

Shutdown DB Container

make docker-down

DB Integrations Test:

make itest

Run the test suite:

make test

Quick start (local)

Prerequisites:

  • Go 1.18+ (see go.mod for exact version used by the project)
  • Docker & docker-compose (optional, used for starting a local Postgres DB)

Start a local Postgres instance using the provided docker-compose file (recommended for development):

# start DB container in the background (uses docker-compose.yml)
make docker-run

# run the server locally
make run

Or run directly with go run (ensure env vars are set or .env is present):

go run ./cmd/api

The server listens on the port configured by the PORT environment variable (default 8080). Use the examples above to POST events or query them.

Clean up binary from the last build:

make clean

Development notes

Main components (high level):

  • cmd/api — program entrypoint that sets up logging, starts the HTTP server and the aggregator, and handles graceful shutdown.

  • internal/server — HTTP server and routes/handlers that accept events.

  • internal/aggregator — periodic job scheduler/worker that performs aggregation or background processing.

  • Tests and integration tests exercised via Makefile targets.

  • The server uses structured JSON logging to stdout for easy consumption by log collectors or local debugging.

  • The aggregator exposes Start and Stop so the application can control its lifecycle (see cmd/api for graceful shutdown handling).

  • The application is intentionally small and modular to make it easy to extend (add handlers, persistence, metrics, etc.).

About

Simple Event Handler is a small, opinionated Go application that accepts events over an HTTP API, performs light processing, and runs periodic aggregation tasks in the background.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published