A self-hosted fitness tracking web application built with NestJS and Vue 3.
Grindify is a full-stack workout application designed for users who want full ownership of their data without subscription fees or connectivity requirements. It offers a complete suite of tools to plan routines, log sessions in real-time, and visualize progress over time. The platform is designed to be mobile-first for gym usage while providing a robust desktop interface for planning and analysis.
- Workout Management: Create and organize custom workout routines with specific exercises, sets, and targets.
- Session Tracking: Log workouts in real-time with an interface optimized for mobile devices.
- Exercise Library: Manage a database of exercises with support for custom images and muscle group categorization.
- Progress Analytics: View detailed statistics including volume, frequency, and personal records per exercise.
- Body Metrics: Track weight logs and upload progress photos to monitor physical changes.
- Privacy Focused: Complete data ownership with no third-party tracking or external dependencies.
- Framework: NestJS (v11)
- Database: PostgreSQL 17
- ORM: TypeORM
- Authentication: Passport.js (JWT & Local Strategies)
- Validation: class-validator & class-transformer
- Media: Sharp (Image processing) & Multer (File uploads)
- Documentation: Swagger/OpenAPI
- Framework: Vue 3 (Composition API)
- Build Tool: Vite
- UI Library: Vuetify 3
- State Management: Pinia (with persistence)
- Visualization: Chart.js & Vue-Chartjs
- Routing: Vue Router
- Docker & Docker Compose (Recommended)
- OR
- Node.js v18+
- PostgreSQL v15+
-
Clone the repository
git clone https://github.com/FalkenDev/Grindify.git cd Grindify -
Start the application
docker compose up -d --build
The database migrations will run automatically on startup.
-
Seed initial data (optional) Population of default exercises and muscle groups:
docker exec -it grindify_backend npm run seed -
Access the application
- Frontend: http://localhost:3000
- API Documentation: http://localhost:1337/api
- Backend API: http://localhost:1337
-
Clone the repository
git clone https://github.com/FalkenDev/Grindify.git cd Grindify -
Configure Environment Copy the example environment file and configure your database credentials:
cp .env.example .env
-
Backend Setup
cd backend npm install # Ensure PostgreSQL is running and update .env with credentials npm run build npm run start:prod
-
Frontend Setup
cd frontend npm install npm run build npm run preview
Grindify/
├── backend/ # NestJS API application
│ ├── src/ # Source code
│ └── test/ # E2E tests
├── frontend/ # Vue 3 application
│ └── src/ # Source code
├── docker-compose.yml # Development orchestration
└── Dockerfile.* # Container definitions
To start both services with hot-reload enabled:
docker compose -f docker-compose.yml up- Backend changes will trigger a transparent restart.
- Frontend changes will be reflected instantly via Vite HMR.
Grindify supports optional GitHub sign-in. Leave GITHUB_CLIENT_ID blank to disable it entirely — the app works fine without it.
- Go to github.com/settings/developers → OAuth Apps → New OAuth App
- Fill in:
- Application name: Grindify
- Homepage URL:
http://localhost:3000(or your domain) - Authorization callback URL:
http://localhost:1337/v1/auth/github/callback
- Copy the Client ID and generate a Client Secret
- Add to your
.env:
GITHUB_CLIENT_ID=your_client_id
GITHUB_CLIENT_SECRET=your_client_secret
BACKEND_URL=http://localhost:1337 # must match what GitHub redirects to- Restart the backend
If GITHUB_CLIENT_ID is not set, the GitHub button on the login page still appears but the strategy is not loaded — set it to enable the full flow.
Account linking: If a GitHub email matches an existing password-based account, the accounts are automatically linked. The user can then sign in with either method.
Grindify supports optional email-based verification and password reset using Resend.
Set these variables in your .env file:
# Enable email verification (false by default — users can log in immediately after registration)
REQUIRE_EMAIL_VERIFICATION=false
# Resend API key — required only when REQUIRE_EMAIL_VERIFICATION=true
RESEND_API_KEY=re_your_api_key_here
# The "From" address for outgoing emails
EMAIL_FROM=noreply@yourdomain.com
# The public URL of your frontend (used in email links)
FRONTEND_URL=https://yourdomain.com- Create a free account at resend.com.
- Go to API Keys and create a new key.
- Add a verified sending domain under Domains (required to send from your own domain).
- Set
RESEND_API_KEYandEMAIL_FROMin your.env. - Set
REQUIRE_EMAIL_VERIFICATION=trueto enforce verification.
REQUIRE_EMAIL_VERIFICATION |
Behaviour |
|---|---|
false (default) |
Users are auto-verified on registration. No email is sent. Password reset still works if Resend is configured. |
true |
Users receive a 6-digit code by email after registration and must verify before logging in. Login is blocked until verified. |
Password reset (available regardless of the verification toggle):
- User clicks "Forgot password?" on the login page.
- They enter their email — a 6-digit reset code is sent.
- They enter the code + new password on the reset page.
- Codes expire after 15 minutes.
Note: If
REQUIRE_EMAIL_VERIFICATION=falseand no Resend credentials are set, password reset emails will fail silently. Configure Resend if you want password reset to work.
Grindify includes a Version History view in Settings. It compares:
installedVersion: the build currently installed on the device or cached by the PWAdeployedVersion: the version served by the live frontend at/version.jsonlatestReleaseVersion: the latest official GitHub Release returned by the backend proxy
The frontend image bakes a /version.json file that includes:
versiongitShabuiltAtchannel
Status interpretation:
installedVersion == deployedVersion: this device is current for the deployed server buildinstalledVersion < deployedVersion: this device still has an older cached PWA build and should updatedeployedVersion < latestReleaseVersion: a newer official release exists but the server has not deployed it yetinstalledVersion > latestReleaseVersion: the device is on a development or pre-release build; this is shown as a neutral mismatch
The backend exposes release history through GET /v1/releases. By default it resolves to the FalkenDev/Grindify repository unless you override these variables:
GITHUB_RELEASES_OWNER=FalkenDev
GITHUB_RELEASES_REPO=Grindify
GITHUB_RELEASES_TOKEN=Use GITHUB_RELEASES_TOKEN if you want higher GitHub API limits for self-hosted deployments.
The tag/release/deploy workflow is maintainer-specific and is documented alongside the Grindify stack in the Homelab repository. Contributors and normal self-hosted users do not need to create GitHub Releases to run Grindify locally.
Contributions are welcome. Please read our Contributing Guidelines before submitting a Pull Request.
By contributing to Grindify, you agree to our Contributor License Agreement (CLA).
This project is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0). See the LICENSE file for details.
This software is provided "as is", without warranty of any kind.