A rest api for tracking workouts. Built with go, gin and sqlite, trying to follow a clean repo -> service -> handler architecture, for maintainability, testing...
- User authentication with JWT access tokens
- Refresh token authentication (rotating refresh tokens stored hashed in DB)
- Create / Update / Delete workouts
- Attach exercises to workouts
- Workout reports and statistics
- Rate limiting (5 requests / second)
- Docker build
- runs as non root user
- sqlite db persists via Docker volume
- config with enviorenment variables (no secrets in image)
- Docker
- Docker compose
- Repo → raw DB access (SQL only)
- Service → business logic (auth, refresh, rotation)
- Handler → HTTP / JSON (Gin)
- Middleware → auth, rate limiting
- Clone the repository
git clone https://github.com/tianiste/workout-tracker-api
cd workout-tracker-api- Create a
.envfile with a JWT secret key (see.env.example)
JWT_KEY=your-secret-key- Build and run the container
docker compose up --build- Verify the API is running
curl http://localhost:8080/api/ping- for production cookies should be set to 'true' in
userHandler.goand the API should be served behind HTTPS
Sent on protected routes using:
Authorization: Bearer <access_token>- Short-lived (10 minutes)
- Used for all protected API routes
- Stored in an HttpOnly cookie
- Rotated on every
/refresh - Stored hashed (SHA-256) in the database
- Revoked on logout
Cookie settings:
HttpOnlySameSite=LaxSecure=true(production)Path=/
-
Login
- Returns access token
- Sets refresh token cookie
-
Refresh
- Validates refresh token from cookie
- Issues new access token
- Rotates refresh token
-
Logout
- Revokes refresh token in DB
- Clears cookie
GET /api/ping
POST /api/register
{
"name": "string",
"password": "string"
}POST /api/login
{
"name": "string",
"password": "string"
}POST /refresh
- Uses refresh token cookie
- Returns new access token
POST /logout
- Revokes refresh token
- Clears cookie
POST /api/workouts
{
"performedAt": "RFC3339 timestamp string",
"durationMinutes": 45,
"notes": "optional string"
}GET /api/workouts
GET /api/workouts/:id
GET /api/workouts/:id/details
PUT /api/workouts/:id
{
"performedAt": "RFC3339 timestamp string",
"durationMinutes": 50,
"notes": "optional string"
}DELETE /api/workouts/:id
GET /api/workouts/:id/report
Sample response:
{
"workoutId": 1,
"userId": 1,
"performedAt": "1",
"notes": "none",
"createdAt": "2026-01-09T03:13:28Z",
"totalExercises": 1,
"totalSets": 1,
"totalReps": 10,
"totalVolume": 600,
"exercises": [
{
"exerciseId": 1,
"exerciseName": "Bench Press",
"setsCount": 1,
"totalReps": 10,
"maxWeight": 60,
"totalVolume": 600
}
]
}POST /api/workouts/:id/exercises
{
"exerciseId": 1,
"exerciseOrder": 1,
"notes": "optional string"
}PUT /api/workout-exercises/:id
{
"exerciseOrder": 2,
"notes": "optional string"
}DELETE /api/workout-exercises/:id
POST /api/workout-exercises/:id/sets
{
"setNumber": 1,
"reps": 10,
"weight": 60.0
}PUT /api/sets/:id
{
"reps": 12,
"weight": 62.5
}DELETE /api/sets/:id
GET /api/exercises
- Workout scheduling
- Custom user-specific exercises
- Session/device management UI
- Token reuse detection alerts
- Production Docker setup