A professional tax office management system featuring a comprehensive appointment booking platform. This project demonstrates modern web development practices with Node.js, including secure authentication, real-time availability management, and automated email workflows.
Last Updated: December 3, 2025
main- Stable production-ready codetesting- Active test development and implementation (comprehensive test suite with 100+ tests)
Note: Full test suite documentation and implementation is available on the
testingbranch. Switch to that branch to access test utilities, coverage reports, and GitHub Actions workflows.
- Branch Structure
- Overview
- Key Features
- Architecture & Design Principles
- Technology Stack
- Quick Start with Docker
- Manual Installation
- Project Structure
- Configuration
- API Endpoints
- Admin Panel
- Development
- Testing
- Deployment
- Common Pitfalls
- Contributing
- License
NT TaxOffice Node is a full-stack web application built to modernize appointment scheduling for professional tax services. The system handles the complete booking lifecycle—from client-facing appointment requests to admin approval workflows and automated email notifications.
Traditional appointment systems often rely on phone calls or manual email exchanges, leading to:
- Double bookings and scheduling conflicts
- Time wasted on coordination
- Poor user experience for clients
This application solves these problems by providing:
- Real-time availability based on configurable business hours
- Automated email workflows that keep all parties informed
- Admin dashboard for centralized appointment management
- Conflict prevention through database-level locking mechanisms
- 📅 Intuitive Booking Interface - Select services, dates, and times through a clean, responsive UI
- 📧 Email Notifications - Automatic confirmations, status updates, and reminders
- 🔗 Cancellation Links - One-click appointment cancellation via secure tokens
- 📱 Mobile-Friendly - Fully responsive design works on all devices
- 🎛️ Dashboard - View, filter, and manage all appointments from a centralized interface
- ⚙️ Availability Management - Configure per-day working hours and block specific dates
- ✅ Appointment Approval - Review and approve/decline booking requests
- 📊 Status Tracking - Monitor appointments across pending, confirmed, declined, and completed states
- 🔒 Security First - CSP-compliant code, rate limiting, input sanitization, bcrypt password hashing
- ⚡ Performance - Connection pooling, database indexes, optimistic locking
- 🐳 Docker Ready - Full containerization with docker-compose for easy deployment
- 📮 Email Queue - Reliable email delivery with retry logic
The project follows a service-oriented architecture that separates concerns into distinct layers:
Client Request → Routes (HTTP) → Services (Business Logic) → Database
Benefits:
- Testability - Each layer can be tested independently
- Maintainability - Changes in one layer don't cascade to others
- Scalability - Services can be extracted into microservices if needed
Instead of calculating availability on-the-fly, we store availability settings in MySQL. This allows:
- Consistent logic across admin panel and booking interface
- Fast queries for available time slots
- Historical tracking of availability changes
We use Express sessions (not JWT) for admin authentication because:
- Simplicity - No token refresh logic needed
- Server control - Sessions can be invalidated server-side instantly
- Security - Session cookies are HTTP-only and secure
Instead of sending emails synchronously, we use a queue (email_queue table):
- Reliability - Failed emails are automatically retried
- Performance - Email sending doesn't block HTTP responses
- Monitoring - Failed emails are easily identified
All JavaScript uses event delegation instead of inline handlers:
- Security - Prevents XSS attacks
- Performance - Fewer event listeners in memory
- Best Practice - Follows modern web standards
| Layer | Technology | Why We Chose It |
|---|---|---|
| Runtime | Node.js 18+ | Modern JavaScript features, excellent async support |
| Web Framework | Express 4.x | Battle-tested, extensive middleware ecosystem |
| Database | MySQL 8.0 | ACID compliance, mature tooling, proven reliability |
| Templating | EJS | Server-side email templates with logic |
| Nodemailer + Gmail | Free, reliable, easy setup for small deployments | |
| Date Picker | Flatpickr | Lightweight, accessible, locale support |
| Containerization | Docker + Docker Compose | Reproducible environments, easy deployment |
Key Dependencies:
{
"express": "^4.21.2",
"mysql2": "^3.11.5",
"express-session": "^1.18.1",
"bcrypt": "^5.1.1",
"nodemailer": "^6.9.16",
"express-rate-limit": "^7.4.1",
"helmet": "^8.0.0"
}Prerequisites: Docker and Docker Compose installed
git clone https://github.com/itheCreator1/nt-taxoffice-node.git
cd nt-taxoffice-node
# Copy and edit environment variables
cp .env.example .env
nano .env # Edit database credentials, email config, and secretsdocker-compose up -dThis starts:
- MySQL 8.0 on port 3306
- Node.js app on port 3000 (http://localhost:3000)
# Check logs
docker-compose logs -f app
# Check database connection
docker-compose exec mysql mysql -uroot -p -e "SHOW DATABASES;"- Main site: http://localhost:3000
- Appointment booking: http://localhost:3000/appointments.html
- Admin panel: http://localhost:3000/admin/setup.html (first-time setup)
Stopping the stack:
docker-compose downLearn More: See docs/guides/deployment.md for production deployment.
If you prefer running without Docker:
- Node.js 18+ and npm
- MySQL 8.0 server running
# 1. Clone repository
git clone https://github.com/itheCreator1/nt-taxoffice-node.git
cd nt-taxoffice-node
# 2. Install dependencies
npm install
# 3. Configure environment
cp .env.example .env
# Edit .env with your database credentials and email config
# 4. Initialize database
mysql -u root -p < database/schema.sql
# 5. Start the server
npm run dev # Development mode with nodemon
# OR
npm start # Production modeThe application will be available at http://localhost:3000
Understanding the codebase organization:
nt-taxoffice-node/
├── database/
│ ├── init.js # Database connection and initialization
│ ├── schema.sql # MySQL schema (6 tables)
│ └── migrations/ # Future schema migrations
├── docs/
│ ├── API.md # Complete API documentation
│ ├── DEPLOYMENT.md # Production deployment guide
│ ├── ADMIN_GUIDE.md # Admin panel user guide
│ └── claude.md # Development context for Claude Code
├── middleware/
│ ├── auth.js # Session-based authentication
│ ├── errorHandler.js # Centralized error handling
│ ├── rateLimiter.js # IP-based rate limiting
│ └── setupCheck.js # First-time setup redirect
├── public/
│ ├── admin/ # Admin panel HTML pages
│ │ ├── dashboard.html # Appointment management
│ │ ├── availability.html # Hours & blocked dates config
│ │ ├── login.html # Admin authentication
│ │ └── setup.html # First-time setup wizard
│ ├── css/
│ │ ├── pages/ # Page-specific styles
│ │ └── vendor/ # Third-party CSS (Flatpickr)
│ ├── js/
│ │ ├── admin/ # Admin panel JavaScript
│ │ ├── vendor/ # Third-party JS (Flatpickr)
│ │ ├── appointments.js # Booking interface logic
│ │ └── cancel-appointment.js # Cancellation logic
│ ├── appointments.html # Client booking page
│ ├── cancel-appointment.html # Cancellation page
│ └── index.html # Homepage
├── routes/
│ ├── admin/
│ │ ├── appointments.js # Admin appointment CRUD
│ │ ├── auth.js # Login, logout, setup
│ │ └── availability.js # Availability configuration
│ ├── api/
│ │ ├── appointments.js # Client booking endpoints
│ │ └── availability.js # Slot availability queries
│ └── index.js # Route registration
├── scripts/
│ └── init-db.js # Database initialization script
├── services/
│ ├── appointments.js # Appointment business logic
│ ├── availability.js # Slot calculation logic
│ ├── database.js # MySQL connection pool
│ ├── email.js # Email sending (Nodemaaler)
│ └── emailQueue.js # Email queue processor
├── utils/
│ ├── logger.js # Colored console logging
│ ├── sanitization.js # Input sanitization (XSS prevention)
│ ├── timezone.js # Timezone conversion utilities
│ └── validation.js # Input validation rules
├── views/
│ └── emails/ # EJS email templates (HTML + text)
├── .env.example # Environment variable template
├── .dockerignore # Docker build exclusions
├── docker-compose.yml # Multi-container Docker setup
├── Dockerfile # Node.js container image
├── package.json # Dependencies and scripts
└── server.js # Application entry point
server.js - Application entry point that:
- Configures Express middleware (Helmet, sessions, rate limiting)
- Registers all routes
- Initializes database connection
- Starts email queue processor
- Handles graceful shutdown
services/availability.js - Core business logic for:
- Calculating available time slots based on working hours
- Excluding booked slots
- Handling blocked dates
- Timezone conversions
services/emailQueue.js - Processes the email queue:
- Runs every 30 seconds
- Retries failed emails (max 3 attempts)
- Logs errors for manual review
middleware/auth.js - Protects admin routes:
- Checks for valid session
- Redirects to login if not authenticated
- Provides
requireAuthmiddleware
All configuration is done through environment variables (.env file).
Copy from template:
cp .env.example .envRequired before starting:
| Variable | Description | Example |
|---|---|---|
SESSION_SECRET |
Secret for signing session cookies | Generate with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" |
DB_PASSWORD |
MySQL root password | mySecurePassword123 |
GMAIL_USER |
Gmail address for sending emails | youremail@gmail.com |
GMAIL_APP_PASSWORD |
Gmail app-specific password | Get from Google |
ADMIN_EMAIL |
Email for admin notifications | admin@example.com |
- Enable 2-Factor Authentication on your Google account
- Go to Google App Passwords
- Select "Mail" → "Other (Custom name)" → Generate
- Copy the 16-character code to
GMAIL_APP_PASSWORD
Common Pitfall: Don't use your regular Gmail password—it won't work. You must use an app-specific password.
When using Docker Compose, set:
DB_HOST=mysql # Must match service name in docker-compose.ymlWhen running locally without Docker:
DB_HOST=localhostLearn More: See .env.example for all available options.
The application exposes two sets of APIs:
These endpoints are used by the booking interface:
- GET
/api/availability/slots- Get available time slots for a date - POST
/api/appointments- Create a new appointment request - DELETE
/api/appointments/cancel/:token- Cancel an appointment
Protected by session authentication:
- GET
/api/admin/appointments- List all appointments (with filters) - GET
/api/admin/appointments/:id- Get appointment details - PUT
/api/admin/appointments/:id/status- Update appointment status - DELETE
/api/admin/appointments/:id- Delete an appointment - GET
/api/admin/availability/settings- Get working hours configuration - PUT
/api/admin/availability/settings- Update working hours - GET
/api/admin/availability/blocked-dates- List blocked dates - POST
/api/admin/availability/blocked-dates- Add a blocked date - DELETE
/api/admin/availability/blocked-dates/:id- Remove a blocked date
Learn More: See docs/api/endpoints.md for complete API documentation with examples.
- Navigate to http://localhost:3000/admin/setup.html
- Create your admin account (username + password)
- You'll be redirected to the login page
Security Note: The setup page is only accessible when no admin account exists. Once created, attempting to access /admin/setup.html will redirect to the dashboard.
Dashboard (/admin/dashboard.html):
- View all appointments in a filterable table
- Filter by status (pending, confirmed, declined, completed)
- Filter by date range
- View appointment details (client info, service type, notes)
- Approve/decline pending appointments
- Delete appointments
- Real-time status updates
Availability Management (/admin/availability.html):
- Configure working hours for each day of the week
- Mark days as working/non-working
- Set different hours for different days (e.g., shorter hours on Friday)
- Block specific dates (holidays, closures)
- Changes take effect immediately for new bookings
| Status | Meaning | Transitions |
|---|---|---|
| Pending | Client submitted, awaiting admin review | → Confirmed or Declined |
| Confirmed | Admin approved, client notified | → Completed |
| Declined | Admin rejected, client notified | (Final state) |
| Completed | Appointment occurred (manual update) | (Final state) |
| Cancelled | Client cancelled before appointment | (Final state) |
Learn More: See docs/guides/admin-panel.md for detailed admin documentation.
npm run dev # Uses nodemon for auto-restart on file changesCheck logs:
- Server logs are color-coded (see
utils/logger.js) - Email queue logs show sent/failed messages
- Database query errors are logged with full SQL
Debug mode:
Set NODE_ENV=development for verbose logging.
Testing email locally:
Set GMAIL_USER and GMAIL_APP_PASSWORD in .env to see real emails.
- Async/await preferred over callbacks
- Error handling - All async route handlers wrapped in
asyncHandler - Input validation - Use
utils/validation.jsfunctions - Input sanitization - Use
utils/sanitization.jsbefore database storage - Logging - Use
utils/logger.jsinstead ofconsole.log - Comments - Explain WHY, not WHAT (code should be self-explanatory)
6 Core Tables:
admin_users- Admin authentication (bcrypt hashed passwords)appointments- Appointment records with versioning (versioncolumn for optimistic locking)availability_settings- Per-day working hours (7 rows, one per day)blocked_dates- Specific dates when bookings are unavailableemail_queue- Queued emails with retry logicsecurity_audit_log- Security event tracking (logins, changes)
Learn More: Database schema is documented in the migration files at database/schema.sql.
Note: A comprehensive test suite with 100+ tests is under active development on the
testingbranch.
The testing branch includes:
- Unit Tests - Service layer and utility functions
- Integration Tests - API endpoints with real database
- Admin Tests - Complete admin panel functionality
- Test Utilities - Data builders, seeders, and custom matchers
- GitHub Actions - Automated CI/CD pipeline
- Coverage Reports - Detailed test coverage analysis
To access the full test suite:
git checkout testing
npm testLearn More: Switch to the testing branch and see tests/README.md for the complete testing guide including test utilities, performance optimizations, and CI/CD integration
Before deploying to production:
- Generate a strong
SESSION_SECRET(64+ random characters) - Use a strong
DB_PASSWORD(16+ characters, mixed case, symbols) - Set
NODE_ENV=production - Configure SSL/TLS for HTTPS
- Set up regular database backups
- Configure email monitoring (check
email_queuetable for failures) - Set up log rotation
- Test email delivery
- Review rate limiting settings (
RATE_LIMIT_*variables)
Docker (Recommended):
docker-compose -f docker-compose.prod.yml up -dManual (VPS/Dedicated Server):
- Clone repository
- Install Node.js 18+ and MySQL 8.0
- Configure
.envwith production values - Run
npm install --production - Initialize database:
mysql < database/schema.sql - Start with PM2:
pm2 start server.js --name nt-taxoffice
Learn More: See docs/guides/deployment.md for complete deployment guide.
Problem: Emails stuck in queue with "Authentication failed" errors
Solutions:
- Verify you're using an app-specific password, not your regular Gmail password
- Check that 2FA is enabled on your Google account
- Ensure
GMAIL_USERmatches the account where you generated the app password - Check Gmail security settings: https://myaccount.google.com/security
Problem: ECONNREFUSED or Access denied for user
Solutions:
- Docker: Set
DB_HOST=mysql(service name fromdocker-compose.yml) - Local: Set
DB_HOST=localhostor127.0.0.1 - Verify MySQL is running:
docker-compose psorsystemctl status mysql - Check database credentials match
.envfile
Problem: "Refused to execute inline event handler" errors
This shouldn't happen - The codebase uses event delegation to be CSP-compliant. If you see this:
- Check if you added inline
onclickhandlers (don't do this) - Use event delegation pattern instead (see
public/js/admin/dashboard.jsfor examples)
Problem: Booking calendar shows "No available slots"
Solutions:
- Check availability settings in admin panel (
/admin/availability.html) - Ensure at least one day is marked as "working day"
- Verify working hours are set (e.g., 09:00 - 17:00)
- Check that the date isn't in "blocked dates"
- Verify
DEFAULT_SLOT_DURATIONin.envis reasonable (e.g., 30 minutes)
Learn More: See docs/guides/admin-panel.md for troubleshooting.
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Test your changes manually
- Commit with clear messages
- Push and open a pull request
Code Review Checklist:
- Code follows existing style conventions
- All input is validated and sanitized
- Error handling is comprehensive
- Documentation is updated
- Manual testing completed
Learn More: See CONTRIBUTING.md for detailed guidelines.
- Documentation: See the docs/README.md for all available guides
- Issues: Report bugs or request features on GitHub Issues
- Email: For deployment support, contact the development team