RabbitHR now features a professional-grade backend API built with modern TypeScript technologies, replacing the localStorage-based data persistence with a real SQLite database.
- Hono - Ultra-fast, lightweight web framework
- Drizzle ORM - TypeScript-first ORM with excellent type safety
- SQLite (better-sqlite3) - Zero-config file-based database
- JWT (jsonwebtoken) - Secure authentication
- bcryptjs - Password hashing
- Zod - Runtime type validation
- Axios - HTTP client with interceptors
- TanStack Query - Data fetching and caching
- TypeScript - End-to-end type safety
/api
├── /db
│ ├── schema.ts # Drizzle schema definitions
│ ├── client.ts # Database connection
│ └── seed.ts # Initial data seeding
├── /middleware
│ ├── auth.ts # JWT authentication
│ └── error.ts # Error handling
├── /routes
│ ├── auth.ts # Authentication endpoints
│ ├── feedback.ts # Feedback CRUD
│ ├── absences.ts # Absences management
│ └── employees.ts # Employee directory
└── server.ts # Main Hono app
/src/services
└── api.ts # Axios client & API methods
npm installCreate .env file in project root (use .env.example as template):
API_PORT=3001
JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
DATABASE_PATH=./data/database.dbCreate .env.local for frontend:
VITE_API_URL=http://localhost:3001# Push schema to database and seed initial data
npm run db:setupThis creates:
- 3 users (admin, manager, employee) - all with password
password123 - Sample feedback entries
- Sample absence requests
# Starts both API server and frontend concurrently
npm run devThe backend API runs on http://localhost:3001
The frontend runs on http://localhost:8081 (or 8080 if available)
Login with email and password, returns JWT token
Request:
{
"email": "admin@company.com",
"password": "password123"
}Response:
{
"user": {
"id": "uuid",
"email": "admin@company.com",
"name": "Admin User",
"role": "admin",
"department": "Management",
"position": "System Administrator",
"avatar": "...",
"managerId": null
},
"token": "jwt-token-here"
}Get current authenticated user (requires JWT)
Headers:
Authorization: Bearer <jwt-token>
Get all feedback (sent or received by current user)
Create new feedback
Request:
{
"toUserId": "uuid",
"content": "Great work!",
"polishedContent": "Excellent work on the project...",
"type": "positive",
"status": "sent"
}Update feedback (only if you're the sender)
Delete feedback (only if you're the sender)
Get absences (employees see own, managers/admins see all)
Create absence request
Request:
{
"type": "vacation",
"startDate": "2024-12-20T00:00:00.000Z",
"endDate": "2024-12-27T00:00:00.000Z",
"reason": "Holiday vacation"
}Update pending absence (only your own)
Approve/reject absence (managers/admins only)
Request:
{
"status": "approved" // or "rejected"
}Delete pending absence (only your own)
Get all employees (managers/admins only)
Get single employee (managers/admins only)
Get direct reports of a manager
- id (UUID primary key)
- email (unique)
- password (bcrypt hashed)
- name
- role (admin | manager | employee)
- department
- position
- avatar
- managerId (foreign key to users)
- createdAt, updatedAt
- id (UUID primary key)
- fromUserId (foreign key to users)
- toUserId (foreign key to users)
- content
- polishedContent (optional)
- type (positive | constructive | development)
- status (draft | sent)
- createdAt, updatedAt
- id (UUID primary key)
- userId (foreign key to users)
- type (vacation | sick | personal | other)
- startDate, endDate (ISO strings)
- reason
- status (pending | approved | rejected)
- approvedBy (foreign key to users, nullable)
- createdAt, updatedAt
- User logs in via
/api/auth/login - Server validates credentials, returns JWT token
- Frontend stores token in localStorage
- All subsequent API requests include token in
Authorization: Bearer <token>header - Backend middleware validates token and extracts user info
- Routes enforce role-based access control
- Users can create feedback for any other user
- Users can read feedback they sent or received
- Users can only edit/delete feedback they created
- Employees can create, view, edit, and delete their own pending absences
- Managers can view all team absences and approve/reject them
- Admins can view all absences and approve/reject them
- Only managers and admins can access employee directory
- Managers see their direct reports
- Admins see all employees
npm run dev # Start both API and frontend
npm run dev:api # Start API server only
npm run dev:frontend # Start Vite frontend only
npm run db:generate # Generate migration files
npm run db:push # Push schema to database
npm run db:seed # Seed database with initial data
npm run db:setup # Push schema + seed (full setup)
npm run db:studio # Open Drizzle Studio GUI
npm run build # Build for production
npm run preview # Preview production buildAll users have password: password123
-
Admin User
- Email:
admin@company.com - Role: admin
- Can access all features
- Email:
-
Manager User
- Email:
manager@company.com - Role: manager
- Can approve absences, view team members
- Email:
-
Employee User
- Email:
employee@company.com - Role: employee
- Can submit feedback and absences
- Email:
You can test endpoints using:
- The frontend UI at http://localhost:8081
- cURL or Postman for direct API calls
- Drizzle Studio for database inspection:
npm run db:studio
Example cURL:
# Login
curl -X POST http://localhost:3001/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"admin@company.com","password":"password123"}'
# Get feedback (with token from login)
curl http://localhost:3001/api/feedback \
-H "Authorization: Bearer <your-token>"- Password Hashing: bcrypt with salt rounds
- JWT Authentication: Secure token-based auth with 7-day expiration
- Role-Based Access Control: Enforced at API level
- Input Validation: Zod schemas for all inputs
- SQL Injection Prevention: Drizzle ORM parameterized queries
- CORS Protection: Whitelisted origins only
All data is stored in ./data/database.db SQLite file. This file persists across server restarts, demonstrating real backend functionality to examiners.
To reset the database:
rm -rf data/
npm run db:setupThe frontend uses TanStack Query for data fetching with automatic caching, refetching, and optimistic updates:
// Example: Fetching feedback
const { data: feedbacks, isLoading } = useQuery({
queryKey: ["feedback"],
queryFn: feedbackAPI.getAll,
});
// Example: Creating feedback
const mutation = useMutation({
mutationFn: feedbackAPI.create,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["feedback"] });
},
});For production:
- Change
JWT_SECRETto a strong random value - Use environment-specific
.envfiles - Consider using PostgreSQL instead of SQLite for scalability
- Add rate limiting and additional security headers
- Enable HTTPS
- Set up proper error monitoring (Sentry, etc.)
- Configure database backups
If port 3001 or 8080 is busy, change in .env:
API_PORT=3002If you see "database is locked" errors:
# Stop all processes
pkill -f tsx
pkill -f vite
# Restart
npm run devEnsure frontend URL is in CORS whitelist in api/server.ts
Check that JWT token is being sent in headers and hasn't expired (7 days)
When adding new features:
- Define database schema in
api/db/schema.ts - Create migrations with
npm run db:generate - Add API routes in
api/routes/ - Add validation schemas (Zod)
- Create frontend API methods in
src/services/api.ts - Update components to use TanStack Query
- Test thoroughly with all user roles
Built with professional-grade patterns for demonstration to examiners 🚀