Node.js + Express + TypeScript API for the Applied Strength & Advancement Platform. A self-hosted, data-driven workout tracker built for serious lifters.
- Node.js - Runtime environment
- Express - Web framework
- TypeScript - Type safety
- Prisma - ORM and database toolkit
- PostgreSQL - Database
- JWT - Authentication
- bcrypt - Password hashing
src/
├── controllers/ # Request handlers
│ ├── auth.controller.ts
│ ├── exercise.controller.ts
│ ├── session.controller.ts
│ └── weight.controller.ts
├── routes/ # API routes
│ ├── auth.route.ts
│ ├── exercise.route.ts
│ ├── session.route.ts
│ └── weight.route.ts
├── middleware/ # Custom middleware
│ └── auth.middleware.ts
├── utils/ # Utilities
│ ├── prisma.ts # Prisma client instance
│ └── seed.ts # Database seeding
└── index.ts # Application entry point
prisma/
├── schema.prisma # Database schema
└── migrations/ # Database migrations
data/
└── exercises.json # Exercise library data
- Node.js 18+
- PostgreSQL 14+
- Yarn
- Install dependencies
yarn install- Set up environment variables
Create a .env file in the server directory:
DATABASE_URL=postgresql://postgres:12345@localhost:5432/asap
JWT_SECRET=change_this_to_a_long_random_secret
PORT=3000
NODE_ENV=development
TOKEN_EXP=7d
FRONTEND_DOMAIN=http://localhost:5173- Set up database
Run Prisma migrations:
npx prisma migrate devGenerate Prisma Client:
npx prisma generate- Seed the database (optional)
npx prisma db seed- Start development server
yarn devThe API will be available at http://localhost:3000
yarn dev- Start development server with hot reloadyarn build- Build for productionyarn start- Start production servernpx prisma db seed- Seed database with exercise datanpx prisma studio- Open Prisma Studio (database GUI)npx prisma migrate dev- Create and apply migrations
User
- Authentication and profile data
- Email, password (hashed), username
- Links to sessions, exercises, and weight logs
UserProfile
- Extended user information
- Height, weight, fitness goals
- One-to-one with User
WorkoutSession
- Individual workout records
- Name, date, duration, total volume
- Contains multiple exercise entries
ExerciseEntry
- Exercise performed in a session
- Links to GlobalExercise or custom exercise
- Contains multiple sets
Set
- Individual set data
- Weight, reps, RPE, rest time
- Belongs to ExerciseEntry
GlobalExercise
- Exercise library
- Name, category, muscle groups, equipment
- Can be used across all users
WeightLog
- Body weight tracking over time
- Weight in kg, date, optional notes
User
├── UserProfile (1:1)
├── WorkoutSessions (1:many)
├── ExerciseEntries (1:many - custom exercises)
├── WeightLogs (1:many)
└── Routines (1:many)
WorkoutSession
└── ExerciseEntries (1:many)
└── Sets (1:many)
GlobalExercise
└── ExerciseEntries (1:many - references)
POST /api/auth/signup- Register new userPOST /api/auth/signin- Login userPOST /api/auth/logout- Logout user
GET /api/exercises- List all exercisesGET /api/exercises/:id- Get exercise by IDPOST /api/exercises- Create custom exercise (protected)PUT /api/exercises/:id- Update custom exercise (protected)DELETE /api/exercises/:id- Delete custom exercise (protected)
GET /api/sessions- List user sessions (protected)GET /api/sessions/:id- Get session details (protected)POST /api/sessions- Create new session (protected)PUT /api/sessions/:id- Update session (protected)DELETE /api/sessions/:id- Delete session (protected)GET /api/sessions/stats/calendar- Calendar stats (protected)
POST /api/weights- Log body weight (protected)GET /api/weights/history- Get weight history (protected)
GET /api/profile- Get user profile (protected)PUT /api/profile- Update profile (protected)PUT /api/profile/username- Update username (protected)
GET /api/progress/heatmap- Workout activity heatmap by year / trailing 365 daysGET /api/progress/metrics- KPI card metrics (consistency, active days, streak, volume)GET /api/progress/volume- Volume progression or weight tracking time-series
GET /api/routines- List routinesGET /api/routines/:id- Get routine detailsPOST /api/routines- Create routinePUT /api/routines/:id- Update routineDELETE /api/routines/:id- Delete routine
GET /api/pbs- List PB recordsGET /api/pbs/:exerciseId- PBs for an exercisePOST /api/pbs/sync- Full PB syncPOST /api/pbs/check-session/:sessionId- Check one session for new PBsDELETE /api/pbs/:exerciseId- Delete all PBs for exerciseDELETE /api/pbs/:exerciseId/:metric- Delete one PB metric
See API Documentation for detailed endpoint specifications.
The API uses JWT (JSON Web Tokens) for authentication.
- User registers via
/api/auth/register - User logs in via
/api/auth/login- receives JWT token - Client includes token in
Authorizationheader for protected routes - Middleware validates token and attaches user to request
All routes except authentication (/api/auth/signup, /api/auth/signin, /api/auth/logout) require authentication.
Include the token in the Authorization header:
Authorization: Bearer <token>
The authenticateToken middleware in middleware/auth.middleware.ts:
- Validates JWT token
- Extracts user ID from token
- Attaches user data to request object
- Returns 401 if token is invalid or missing
npx prisma migrate dev --name migration_nameThis will:
- Create a new migration SQL file
- Apply it to the database
- Regenerate Prisma Client
npx prisma migrate deployUse this in production environments.
npx prisma migrate resetThe seed script (prisma/seed.ts) populates the database using Faker:
- Users: Creates
dev(dev@asap.local) anddevadmin(devadmin@asap.local) with passwordpassword123. - Exercises: Ensures foundational exercise library exists.
- Workouts: ~110 realistic workout sessions across the last 365 days (Push, Pull, Legs, Full Body) with progressive overload.
- Weight Logs: ~50 body weight logs over the year.
Run seeding manually:
yarn seedNote: Seeding is never run automatically when the server connects or starts up. It only runs when you explicitly execute
yarn seed.
Visual database browser for development:
npx prisma studioOpens at http://localhost:5555
docker build -t asap-server .
docker run \
--name asap-server \
--link asap-db:db \
-e DATABASE_URL="postgresql://postgres:postgres@db:5432/asap" \
-e JWT_SECRET="change_this_to_a_long_random_secret" \
-e TOKEN_EXP="7d" \
-e FRONTEND_DOMAIN="http://localhost" \
-e NODE_ENV="production" \
-e PORT=3000 \
-p 3000:3000 \
asap-serveryarn build
yarn startThe built files will be in the dist/ directory.
| Variable | Description | Default |
|---|---|---|
DATABASE_URL |
PostgreSQL connection string | Required |
JWT_SECRET |
Secret key for JWT signing | super-secret-key-change-this |
PORT |
Server port | 3000 |
NODE_ENV |
Environment mode used by config/build behavior | development |
TOKEN_EXP |
JWT token expiration | 7d |
FRONTEND_DOMAIN |
Allowed CORS origin(s), comma-separated if multiple | Required for browser clients |
The API uses standard HTTP status codes:
200- Success201- Created400- Bad Request (validation error)401- Unauthorized (missing/invalid token)403- Forbidden (insufficient permissions)404- Not Found500- Internal Server Error
Error responses follow this format:
{
"error": "Error message here"
}Request validation is handled in controllers:
- Required fields are checked
- Data types are validated
- Business logic constraints are enforced
Example validations:
- Email format and uniqueness
- Positive numbers for weight/reps
- Valid date formats
- User ownership of resources
- Use Prisma's
selectto fetch only needed fields - Implement pagination for large result sets
- Use
includecarefully to avoid N+1 queries
const sessions = await prisma.workoutSession.findMany({
where: { userId },
select: {
id: true,
name: true,
performedAt: true,
_count: {
select: { exerciseEntries: true },
},
},
orderBy: { performedAt: "desc" },
take: 20, // Pagination
});Use tools like:
- Postman - Import API collection
- curl - Command-line testing
- Thunder Client - VS Code extension
Register
curl -X POST http://localhost:3000/api/auth/register \
-H "Content-Type: application/json" \
-d '{"email":"test@example.com","password":"password123","username":"testuser"}'Login
curl -X POST http://localhost:3000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"test@example.com","password":"password123"}'Get Exercises (requires token)
curl http://localhost:3000/api/exercises \
-H "Authorization: Bearer YOUR_TOKEN_HERE"The server is automatically deployed with the full stack:
docker-compose up -d- Set environment variables
- Run migrations:
yarn prisma migrate deploy - Build:
yarn build - Start:
yarn start
Regular backups recommended:
pg_dump -U postgres workout_db > backup.sql- ✅ Passwords are hashed with bcrypt
- ✅ JWT tokens for stateless authentication
- ✅ Environment variables for secrets
- ✅ Input validation on all endpoints
- ✅ CORS configured for frontend origin
This project is licensed under the GPLv3 License — see LICENSE for details.
See the CONTRIBUTING for contribution guidelines.
MIT
