A comprehensive Node.js/Express REST API for managing pets, tracking their body condition scores (BCS), and organizing them into groups. The system includes user authentication with refresh token rotation, Google OAuth integration, image upload capabilities, comprehensive logging, and integrated Swagger documentation.
This API provides a complete pet management system with the following features:
-
User Management
- User registration with privacy consent tracking
- JWT-based authentication with access and refresh tokens
- Google OAuth 2.0 Login (Flutter/Mobile compatible)
- Token refresh mechanism with automatic rotation
- Role-based access control (pet-owner/expert)
- User profile management
-
Pet Management
- Create and manage pet profiles
- Track pet information (name, breed, age, gender, spay/neuter status, species)
- Store detailed health records with BCS tracking
- Authorization checks ensuring users can only access their own pets
-
Group Management
- Organize pets into custom groups
- Manage multiple pet groups per user
- Duplicate group name prevention per user
-
Health Record Tracking
- Record body condition scores (BCS)
- Track weight changes over time
- Store multi-view images (front, back, left, right, top)
- Historical record keeping with timestamps
- Retrieve latest records
-
Image Upload & Management
- Secure image upload with validation
- File type restrictions (JPEG, JPG, PNG, GIF only)
- File size limit (5MB)
- Random filename generation for security
- Path traversal attack prevention
- Authenticated image retrieval
-
Security & Logging
- Comprehensive request/response logging
- Security event tracking
- Error logging with stack traces
- Global rate limiting on all API endpoints
- Specific rate limiting on login endpoint
- NoSQL injection protection
- Helmet.js security headers
- Privacy policy version tracking
- Runtime: Node.js
- Framework: Express.js
- Database: MongoDB with Mongoose ODM
- Authentication: JWT (JSON Web Tokens) with dual-token system
- OAuth: Google OAuth 2.0 (google-auth-library)
- File Upload: Multer with diskStorage
- Validation: express-validator
- Security:
- bcryptjs for password hashing
- crypto for random generation
- express-mongo-sanitize (NoSQL injection protection)
- helmet (HTTP security headers)
- express-rate-limit (rate limiting)
- Logging: Winston (logger)
- Documentation: Swagger UI
- Node.js 14.x or higher
- MongoDB 4.x or higher (local or cloud instance)
- npm or yarn package manager
- Google Cloud Console project (for OAuth login)
git clone <repository-url>
cd <project-directory>npm installRequired packages:
- express
- mongoose
- jsonwebtoken
- bcryptjs
- multer
- dotenv
- express-validator
- winston (for logging)
- swagger-ui-express
- swagger-jsdoc
- express-rate-limit (for rate limiting)
- express-mongo-sanitize (NoSQL injection protection)
- helmet (security headers)
- google-auth-library (Google OAuth)
- Go to Google Cloud Console
- Create a new project or select existing project
- Enable Google+ API
- Go to Credentials → Create Credentials → OAuth 2.0 Client ID
- Configure OAuth consent screen
- Create OAuth 2.0 Client ID for your application:
- Application type: Web application (for testing) or Android/iOS (for mobile)
- Add authorized redirect URIs
- Copy the Client ID
Create a .env file in the root directory:
# Server Configuration
PORT=3000
NODE_ENV=development
# MongoDB Connection
MONGODB_URI=mongodb://localhost:27017/pet-management
# JWT Secrets (MUST be different and strong in production)
ACCESS_TOKEN_SECRET=your-access-token-secret-change-this-min-32-chars
REFRESH_TOKEN_SECRET=your-refresh-token-secret-change-this-min-32-chars
# Google OAuth Configuration
GOOGLE_CLIENT_ID=your-google-client-id-from-console.apps.googleusercontent.com
# Privacy Policy
PRIVACY_POLICY_VERSION=v1.0
# CORS Configuration
ALLOWED_ORIGINS=list-of-frontend-urls-all-allowed-to-access-apiImportant Security Notes:
- All JWT secrets must be at least 32 characters in production
- Use different secrets for ACCESS_TOKEN_SECRET and REFRESH_TOKEN_SECRET
- Never commit
.envfile to version control - Keep GOOGLE_CLIENT_ID secure
Ensure MongoDB is running on your system:
# If using local MongoDB
mongod
# Or if using MongoDB as a service
sudo systemctl start mongod# Development mode
npm start
# Or with nodemon (auto-restart)
npm run devThe server will start at http://localhost:3000
.
├── config/
│ └── logger.js # Winston logger configuration
├── middleware/
│ ├── auth.js # JWT authentication middleware
│ ├── validators.js # Input validation rules
│ └── rateLimiter.js # Rate limiting configuration
├── models/
│ ├── User.js # User schema with privacy consent
│ ├── Pet.js # Pet schema and model
│ └── Group.js # Group schema and model
├── routes/
│ ├── user.js # User auth & profile routes
│ ├── pet.js # Pet management routes
│ ├── group.js # Group management routes
│ └── upload.js # File upload routes
├── uploads/ # Uploaded images directory (auto-created)
├── index.js # Main application entry point
├── swagger.js # Swagger configuration
├── package.json # Project dependencies
├── .env # Environment variables (not in git)
├── .gitignore # Git ignore rules
└── README.md # This file
POST /api/users/signup
Content-Type: application/json
{
"firstname": "John",
"lastname": "Doe",
"username": "johndoe",
"email": "john@example.com",
"password": "securePassword123",
"phone": "1234567890",
"role": "pet-owner"
}Response:
{
"message": "User created successfully",
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": "507f1f77bcf86cd799439011",
"firstname": "John",
"lastname": "Doe",
"username": "johndoe",
"email": "john@example.com",
"phone": "1234567890",
"role": "pet-owner"
}
}POST /api/users/login
Content-Type: application/json
{
"email": "john@example.com",
"password": "securePassword123"
}Response:
{
"message": "Login successful",
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}Note: Login endpoint is rate-limited to prevent brute force attacks (5 requests per 15 minutes per IP).
POST /api/users/google-login
Content-Type: application/json
{
"idToken": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjE4MmU0MjM..."
}Response:
{
"message": "Login successful",
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": "507f1f77bcf86cd799439011",
"firstname": "John",
"lastname": "Doe",
"email": "john@example.com",
"username": "john@example.com",
"role": "pet-owner"
}
}How it works:
- User signs in with Google on mobile app (Flutter)
- Google returns
idTokento your app - Send
idTokento this endpoint - Server verifies token with Google
- If valid, creates/finds user and returns JWT tokens
- New users are automatically registered with:
- Email from Google account
- Name split into firstname/lastname
- Username = email
- Default role: "pet-owner"
- No password (OAuth user)
Important:
idTokenmust be valid and not expired- Audience must match your
GOOGLE_CLIENT_ID - Works seamlessly with Flutter
google_sign_inpackage
POST /api/users/refresh
Content-Type: application/json
{
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}Response:
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}Note: Refresh token is automatically rotated on each use for enhanced security.
POST /api/users/logout
Authorization: Bearer <accessToken>
Content-Type: application/json
{}Response:
{
"message": "Logout successful"
}GET /api/users/me
Authorization: Bearer <access-token>Response:
{
"id": "507f1f77bcf86cd799439011",
"firstname": "John",
"lastname": "Doe",
"username": "johndoe",
"email": "john@example.com",
"phone": "1234567890",
"role": "pet-owner",
"privacyConsent": {
"accepted": true,
"acceptedAt": "2025-01-15T10:30:00.000Z",
"policyVersion": "v1.0"
},
"createdAt": "2025-01-15T10:30:00.000Z",
"updatedAt": "2025-01-15T10:30:00.000Z"
}Note: All group endpoints require authentication. Include JWT access token in header:
Authorization: Bearer <your-access-token>
POST /api/groups
Authorization: Bearer <token>
Content-Type: application/json
{
"group_name": "My Dogs"
}Response:
{
"message": "Group created successfully",
"group": {
"_id": "507f1f77bcf86cd799439011",
"group_name": "My Dogs",
"user_id": "507f1f77bcf86cd799439012",
"pets": []
}
}Validation:
- Group name is required
- Duplicate group names per user are not allowed
- User ID is automatically extracted from JWT token
GET /api/groups
Authorization: Bearer <token>Response:
{
"groups": [
{
"_id": "507f1f77bcf86cd799439011",
"group_name": "My Dogs",
"user_id": "507f1f77bcf86cd799439012",
"pets": [
{
"_id": "507f1f77bcf86cd799439013",
"name": "Buddy",
"breed": "Golden Retriever",
"species": "Dog"
}
]
}
]
}POST /api/pets
Authorization: Bearer <token>
Content-Type: application/json
{
"name": "Buddy",
"breed": "Golden Retriever",
"age_years": 3,
"age_months": 6,
"gender": "Male",
"spay_neuter_status": "Neutered",
"species": "Dog",
"group_id": "507f1f77bcf86cd799439011"
}Response:
{
"message": "Pet created and added to group",
"pet": {
"_id": "507f1f77bcf86cd799439013",
"name": "Buddy",
"breed": "Golden Retriever",
"age_years": 3,
"age_months": 6,
"gender": "Male",
"spay_neuter_status": "Neutered",
"species": "Dog",
"group_id": "507f1f77bcf86cd799439011",
"user_id": "507f1f77bcf86cd799439012",
"records": []
}
}Validation:
- All required fields must be provided
- Group must exist and belong to the authenticated user
- ObjectId format validation
GET /api/pets/:petId
Authorization: Bearer <token>Response:
{
"pet": {
"_id": "507f1f77bcf86cd799439013",
"name": "Buddy",
"breed": "Golden Retriever",
"age_years": 3,
"age_months": 6,
"gender": "Male",
"spay_neuter_status": "Neutered",
"species": "Dog",
"group_id": "507f1f77bcf86cd799439011",
"user_id": "507f1f77bcf86cd799439012",
"records": [...]
}
}Authorization: Returns 403 if pet doesn't belong to authenticated user.
POST /api/pets/:petId/records
Authorization: Bearer <token>
Content-Type: application/json
{
"date": "2025-01-15",
"type": "checkup",
"bcs_score": 4,
"weight": 30.5,
"front_image_url": "http://localhost:3000/api/upload/abc123.jpg",
"back_image_url": "http://localhost:3000/api/upload/def456.jpg",
"left_image_url": "http://localhost:3000/api/upload/ghi789.jpg",
"right_image_url": "http://localhost:3000/api/upload/jkl012.jpg",
"top_image_url": "http://localhost:3000/api/upload/mno345.jpg"
}Response:
{
"message": "Record added successfully",
"record": {
"date": "2025-01-15",
"type": "checkup",
"bcs_score": 4,
"weight": 30.5,
"front_image_url": "http://localhost:3000/api/upload/abc123.jpg",
"back_image_url": "http://localhost:3000/api/upload/def456.jpg",
"left_image_url": "http://localhost:3000/api/upload/ghi789.jpg",
"right_image_url": "http://localhost:3000/api/upload/jkl012.jpg",
"top_image_url": "http://localhost:3000/api/upload/mno345.jpg"
}
}Authorization: Only pet owner can add records.
GET /api/pets/:petId/records/latest
Authorization: Bearer <token>Response:
{
"record": {
"_id": "507f1f77bcf86cd799439014",
"date": "2025-01-15",
"type": "checkup",
"bcs_score": 4,
"weight": 30.5,
"front_image_url": "http://localhost:3000/api/upload/abc123.jpg",
"back_image_url": "http://localhost:3000/api/upload/def456.jpg",
"left_image_url": "http://localhost:3000/api/upload/ghi789.jpg",
"right_image_url": "http://localhost:3000/api/upload/jkl012.jpg",
"top_image_url": "http://localhost:3000/api/upload/mno345.jpg"
}
}Note: Returns 404 if no records exist for the pet.
POST /api/upload
Authorization: Bearer <token>
Content-Type: multipart/form-data
image: <file>Response:
{
"message": "File uploaded successfully",
"filename": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6.jpg"
}Restrictions:
- Allowed formats: JPEG, JPG, PNG, GIF
- Maximum file size: 5MB
- Filename is randomly generated (32-character hex + extension)
- Authentication required
Error Responses:
// File too large
{
"error": "File too large (max 5MB)"
}
// Invalid file type
{
"error": "Only images (jpg, png, gif) are allowed"
}
// No file provided
{
"error": "No file uploaded"
}GET /api/upload/:filename
Authorization: Bearer <token>Returns: The image file directly
Security Features:
- Filename format validation (must match: 32-hex-chars.ext)
- Path traversal attack prevention
- User authentication required
- Proper error handling for missing/inaccessible files
- Dual-token system: Short-lived access tokens (15min) + long-lived refresh tokens (7d)
- Refresh token rotation: Each refresh generates new tokens and invalidates old ones
- Google OAuth 2.0: Secure third-party authentication
- Password hashing: bcryptjs with salt rounds (traditional login only)
- JWT validation: Token signature verification and expiration checks
- Role-based access control: Routes protected by user roles (pet-owner/expert)
- Resource ownership verification: Users can only access their own pets and groups
- express-validator: All inputs validated before processing
- NoSQL injection protection: express-mongo-sanitize removes
$and.operators - ObjectId validation: Ensures valid MongoDB ObjectID format
- Duplicate prevention: Checks for existing usernames/emails/group names
- File type validation: Restricted to specific image formats
- Global rate limiting: 100 requests per 15 minutes per IP (all
/api/*routes) - Login rate limiting: 5 requests per 15 minutes per IP (login endpoint)
- Path traversal protection: File access validated against allowed directory
- NoSQL injection protection: Automatic sanitization of user inputs
- Helmet.js: Secure HTTP headers (XSS, clickjacking, MIME sniffing protection)
- CORS: Configurable cross-origin resource sharing
- Error handling: Generic error messages in production mode
- Random filenames: Crypto-generated names prevent guessing
- File size limits: 5MB maximum
- File type restrictions: Only JPEG, JPG, PNG, GIF allowed
- Secure storage: Files stored outside web root
- Authenticated access: File retrieval requires valid token
- Comprehensive logging: Winston logger for all operations
- Security event tracking: Failed logins, unauthorized access attempts
- Error logging: Stack traces for debugging (not exposed to clients)
- Audit trail: User actions logged with IP addresses and timestamps
- Uncaught exception handling: Global error handlers prevent crashes
- Environment-based error messages: Generic errors in production
- JWT secret validation: Minimum length requirements enforced
- Configuration validation: Required env vars checked at startup
- Privacy policy versioning: Tracks user consent to terms
- Process error handlers: Catches uncaught exceptions and unhandled rejections
-
Register: User creates account via
/api/users/signup- Receives both access token (15min) and refresh token (7d)
- Privacy consent is automatically recorded
-
Login: User authenticates via
/api/users/login- Receives fresh access and refresh tokens
- Rate limited to prevent brute force attacks
-
Access Protected Routes: Include access token in
Authorization: Bearer <token>header -
Token Refresh: When access token expires:
- Use
/api/users/refreshwith refresh token - Receive new access token AND new refresh token
- Old refresh token is invalidated (rotation)
- Use
-
Token Validation:
- Middleware validates token signature and expiration
- Extracts user ID and role from token payload
- Verifies user authorization for requested resources
- All
/api/*routes: 100 requests per 15 minutes per IP - Purpose: Prevent API abuse and DDoS attacks
- Response when exceeded:
{
"error": "Too many requests, please try again later."
}POST /api/users/login: 5 requests per 15 minutes per IP- Purpose: Prevent brute force password attacks
- Response when exceeded:
{
"error": "Too many login attempts, please try again after 15 minutes."
}All endpoints return appropriate HTTP status codes:
200- Success201- Created successfully400- Bad request (validation errors, invalid data)401- Unauthorized (missing or invalid token)403- Forbidden (valid token but insufficient permissions)404- Resource not found429- Too many requests (rate limit exceeded)500- Internal server error
Error Response Format:
{
"error": "Descriptive error message"
}Validation Error Format:
{
"errors": [
{
"msg": "Email is required",
"param": "email",
"location": "body"
}
]
}Interactive API documentation is available via Swagger UI:
Access: http://localhost:3000/api-docs
Features:
- Complete endpoint documentation
- Request/response schemas
- Try-it-out functionality
- Authentication handling
- Google OAuth documentation