A comprehensive multi-tenant SaaS platform for managing Islamic schools and madrasas
Features | Installation | Usage | Documentation | Support
- About
- Features
- Tech Stack
- Prerequisites
- Installation
- Configuration
- Running the Application
- Project Structure
- Key Concepts
- Usage Guide
- Development
- API Documentation
- Troubleshooting
- Contributing
- License
Nazim is a modern, full-featured school management system specifically designed for Islamic schools, madrasas, and religious educational institutions. Built with scalability and multi-tenancy in mind, Nazim helps educational institutions manage students, staff, academic programs, timetables, and comprehensive reporting—all in one unified platform.
- 🏢 Multi-Tenant Architecture: Supports multiple organizations with complete data isolation
- 🔒 Security First: Organization-scoped permissions, role-based access control
- 🌍 Internationalization: Built-in support for Arabic, English, Pashto, and Farsi
- 📱 Responsive Design: Works seamlessly on desktop, tablet, and mobile devices
- ⚡ High Performance: Optimized queries, caching, and lazy loading
- 🎨 Modern UI: Beautiful, intuitive interface built with shadcn/ui
- Complete student profiles with photos and documents
- Admission management with multiple statuses
- Educational history tracking
- Discipline records management
- Guardian and emergency contact information
- Orphan status and financial aid tracking
- Advanced search and filtering
- Bulk import/export capabilities
- Comprehensive staff profiles
- Multiple staff types (teachers, admin, accountant, librarian)
- Religious and modern education background tracking
- Document management
- Assignment to schools and subjects
- Timetable preferences
- Academic year management with current year tracking
- Class management with grade levels
- Subject management and curriculum planning
- Class sections with capacity management
- Teacher-subject assignments
- Room and building management
- Residency type management (day scholar, boarder)
- Flexible timetable generation
- Schedule slot management
- Teacher availability preferences
- Conflict detection and resolution
- Multiple timetable types
- Multi-organization support
- School branding and customization
- Organization-specific settings
- User assignment to organizations
- Super admin with cross-organization access
- Student statistics and demographics
- Staff distribution reports
- Admission trends
- Custom report templates
- Print-ready formats with Arabic support
- Role-based access control (RBAC) with Spatie Laravel Permission
- Organization-scoped permissions
- JWT token-based authentication (Laravel Sanctum)
- Audit logging
- Data encryption
- Dark mode support
- RTL (Right-to-Left) language support
- Keyboard shortcuts
- Context menus
- Real-time notifications
- Responsive data tables with sorting and filtering
| Technology | Version | Purpose |
|---|---|---|
| Laravel | 12.x | PHP framework for robust API development |
| PostgreSQL | 15+ | Primary database with UUID support |
| Sanctum | 4.x | API authentication with Bearer tokens |
| Spatie Permissions | 6.x | Organization-scoped role/permission system |
| PHP | 8.2+ | Server-side scripting language |
| Technology | Version | Purpose |
|---|---|---|
| React | 18.x | UI library for building interactive interfaces |
| TypeScript | 5.x | Type-safe JavaScript |
| Vite | 5.x | Fast build tool and dev server |
| TanStack Query | 5.x | Data fetching and state management |
| React Router | 6.x | Client-side routing |
| shadcn/ui | Latest | Beautiful UI components (Radix + Tailwind) |
| Tailwind CSS | 3.x | Utility-first CSS framework |
| Zod | 3.x | Schema validation |
- Git - Version control
- Composer - PHP dependency manager
- npm - JavaScript package manager
- Docker (optional) - Containerization
Before you begin, ensure you have the following installed:
-
PHP >= 8.2 with extensions:
- BCMath
- Ctype
- cURL
- DOM
- Fileinfo
- JSON
- Mbstring
- OpenSSL
- PDO
- Tokenizer
- XML
- pgsql
-
Composer >= 2.6
-
Node.js >= 18.x
-
npm >= 9.x or pnpm >= 8.x
-
PostgreSQL >= 15
- Git >= 2.x
- Docker & Docker Compose (for containerized setup)
- Redis (for caching and queues)
git clone https://github.com/yourusername/nazim-web.git
cd nazim-web# Navigate to backend directory
cd backend
# Install PHP dependencies
composer install
# Copy environment file
cp .env.example .env
# Generate application key
php artisan key:generate
# Configure your .env file with database credentials
# Edit .env and set:
# DB_CONNECTION=pgsql
# DB_HOST=127.0.0.1
# DB_PORT=5432
# DB_DATABASE=nazim
# DB_USERNAME=postgres
# DB_PASSWORD=your_password
# Create database
# In PostgreSQL, run: CREATE DATABASE nazim;
# Run migrations
php artisan migrate
# Seed database with initial data (optional)
php artisan db:seed
# Create storage symlink
php artisan storage:link
# Start Laravel development server
php artisan serve
# Backend will run on http://localhost:8000# Open a new terminal and navigate to frontend directory
cd frontend
# Install JavaScript dependencies
npm install
# or if using pnpm:
# pnpm install
# Copy environment file (if exists)
cp .env.example .env 2>/dev/null || true
# Start Vite development server
npm run dev
# Frontend will run on http://localhost:5173Open your browser and navigate to:
- Frontend: http://localhost:5173
- Backend API: http://localhost:8000/api
# Clone repository
git clone https://github.com/yourusername/nazim-web.git
cd nazim-web
# Build and start containers
docker-compose up -d
# Run migrations
docker-compose exec backend php artisan migrate --seed
# Access application at http://localhost:3000Edit backend/.env:
# Application
APP_NAME=Nazim
APP_ENV=local
APP_DEBUG=true
APP_URL=http://localhost:8000
# Database
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=nazim
DB_USERNAME=postgres
DB_PASSWORD=your_secure_password
# CORS
FRONTEND_URL=http://localhost:5173
# Cache & Queue (Optional)
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
# For production, use:
# CACHE_DRIVER=redis
# QUEUE_CONNECTION=redis
# Mail Configuration (for notifications)
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="hello@nazim.local"
MAIL_FROM_NAME="${APP_NAME}"
# File Storage
FILESYSTEM_DISK=local
# For production with S3:
# FILESYSTEM_DISK=s3
# AWS_ACCESS_KEY_ID=
# AWS_SECRET_ACCESS_KEY=
# AWS_DEFAULT_REGION=
# AWS_BUCKET=Create frontend/.env (optional, uses defaults):
# API Configuration
VITE_API_URL=/api # Uses Vite proxy in development
# Feature Flags
VITE_ENABLE_PERF_MONITORING=false
# Environment
VITE_APP_ENV=developmentEnsure backend/config/cors.php includes your frontend URL:
'allowed_origins' => [
env('FRONTEND_URL', 'http://localhost:5173'),
'http://localhost:5173',
'http://127.0.0.1:5173',
],Terminal 1 - Backend:
cd backend
php artisan serve
# Runs on http://localhost:8000Terminal 2 - Frontend:
cd frontend
npm run dev
# Runs on http://localhost:5173Backend:
cd backend
composer install --optimize-autoloader --no-dev
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan migrate --forceFrontend:
cd frontend
npm run build
# Output in frontend/dist/
# Deploy dist/ folder to your web servernazim-web/
├── backend/ # Laravel Backend
│ ├── app/
│ │ ├── Http/
│ │ │ ├── Controllers/ # API Controllers
│ │ │ ├── Middleware/ # Custom Middleware
│ │ │ └── Requests/ # Form Requests
│ │ ├── Models/ # Eloquent Models
│ │ └── Services/ # Business Logic
│ ├── config/ # Configuration Files
│ ├── database/
│ │ ├── migrations/ # Database Migrations
│ │ └── seeders/ # Database Seeders
│ ├── routes/
│ │ ├── api.php # API Routes
│ │ └── web.php # Web Routes
│ └── storage/ # File Storage
│
├── frontend/ # React Frontend
│ ├── public/ # Static Assets
│ ├── src/
│ │ ├── components/ # React Components
│ │ │ ├── layout/ # Layout Components
│ │ │ ├── students/ # Student Components
│ │ │ ├── staff/ # Staff Components
│ │ │ ├── settings/ # Settings Components
│ │ │ └── ui/ # UI Components (shadcn)
│ │ ├── hooks/ # Custom React Hooks
│ │ ├── lib/ # Utility Functions
│ │ │ ├── api/ # API Client
│ │ │ └── utils.ts # Helper Functions
│ │ ├── mappers/ # API ↔ Domain Mappers
│ │ ├── pages/ # Page Components
│ │ ├── types/ # TypeScript Types
│ │ │ ├── api/ # API Types (snake_case)
│ │ │ └── domain/ # Domain Types (camelCase)
│ │ ├── App.tsx # Main App Component
│ │ └── main.tsx # Entry Point
│ ├── vite.config.ts # Vite Configuration
│ └── package.json # Dependencies
│
├── .claude.md # AI Development Guide
└── README.md # This File
Nazim uses organization-based multi-tenancy where:
- Organizations are the primary tenant boundary
- All data (students, staff, classes) is scoped to an organization
- Users belong to one organization
- Users can change school but NOT organization
- Super admins with
organization_id = nullcan access all organizations
Example:
// ✅ User can do this:
updateStudent({
id: 'student-123',
schoolId: 'new-school-456', // Allowed
});
// ❌ User cannot do this:
updateStudent({
id: 'student-123',
organizationId: 'different-org', // Forbidden!
});- User logs in with email/password
- Backend returns JWT bearer token
- Token stored in localStorage
- Token sent with every API request in
Authorization: Bearer {token}header - Backend validates token and checks organization access
Nazim uses Spatie Laravel Permission with organization scoping:
- super_admin: Access to all organizations
- admin: Organization administrator
- teacher: Teaching staff with limited access
- accountant: Financial data access
- librarian: Library management
Each permission is scoped to an organization for data isolation.
After installation, you need to create your first organization:
# Using Laravel Tinker
cd backend
php artisan tinker
# Create organization
$org = \App\Models\Organization::create([
'name' => 'مدرسة الناظم', // Your school name
'slug' => 'nazim-school',
'settings' => [],
]);
# Create super admin user
$user = \App\Models\User::create([
'id' => \Illuminate\Support\Str::uuid(),
'email' => 'admin@nazim.local',
'password' => bcrypt('password'),
]);
# Create profile
$profile = \App\Models\Profile::create([
'id' => $user->id,
'organization_id' => null, // null for super admin
'role' => 'super_admin',
'full_name' => 'Super Administrator',
'email' => 'admin@nazim.local',
'is_active' => true,
]);
exit- Navigate to http://localhost:5173
- Click Login
- Enter credentials:
- Email:
admin@nazim.local - Password:
password
- Email:
- You'll be redirected to the dashboard
- Go to Settings → Schools Management
- Click Add School
- Fill in school details:
- School name (Arabic, English, Pashto)
- Contact information
- Logo and branding
- Calendar preferences
- Go to Settings → Academic Years
- Click Add Academic Year
- Enter year details:
- Name (e.g., "2024-2025")
- Start date
- End date
- Mark as current year
- Go to Settings → Classes Management
- Click Add Class
- Create classes (e.g., Grade 1, Grade 2, etc.)
- Assign classes to the current academic year
- Go to Staff → Staff List
- Click Add Staff Member
- Fill in staff details:
- Personal information
- Staff type (teacher, admin, etc.)
- Educational background
- Upload photo and documents
- Go to Students
- Click Add Student
- Fill in student information:
- Personal details
- Guardian information
- Admission number
- Upload photo
- After creating, go to Admissions to enroll in classes
- Navigate to Students page
- Click "Add Student" button
- Fill in required fields:
- Full name (Arabic)
- Father's name
- Admission number (unique)
- Gender
- Birth date
- Fill in optional fields:
- Guardian information
- Address details
- Previous school
- Upload student photo
- Click "Save"
- Navigate to Admissions to enroll in class
- Ensure you have:
- Academic year set up
- Classes created and assigned to year
- Subjects created
- Teachers added
- Schedule slots defined
- Go to Timetable Generation
- Select academic year and class
- Assign subjects to teachers
- Assign schedule slots
- Click "Generate Timetable"
- Review and save
- Go to Reports section
- Select report type:
- Student list
- Staff list
- Attendance report
- Academic performance
- Choose filters:
- Academic year
- Class/section
- Date range
- Select format (PDF, Excel)
- Click "Generate Report"
- Download or print
Backend (PHP/Laravel):
- Follow PSR-12 coding standards
- Use type hints for all parameters and return types
- Use dependency injection
- Write descriptive method and variable names
Frontend (TypeScript/React):
- Use TypeScript for all components
- Follow React hooks best practices
- Use functional components with hooks
- Implement proper error boundaries
Endpoints:
GET /api/students # List all students
GET /api/students/{id} # Get single student
POST /api/students # Create student
PUT /api/students/{id} # Update student
DELETE /api/students/{id} # Delete student
GET /api/students/stats # Get statistics
Request/Response Format:
Request (snake_case):
{
"full_name": "محمد أحمد",
"father_name": "أحمد علي",
"admission_no": "2024001",
"organization_id": "uuid-here"
}Response (snake_case):
{
"id": "uuid",
"full_name": "محمد أحمد",
"father_name": "أحمد علي",
"admission_no": "2024001",
"organization_id": "uuid-here",
"created_at": "2024-01-01T00:00:00.000000Z",
"updated_at": "2024-01-01T00:00:00.000000Z"
}See .claude.md for detailed instructions on:
- Creating new modules
- Backend controller patterns
- Frontend hook patterns
- Type mapping between API and domain models
- Testing guidelines
Backend Tests:
cd backend
# Run all tests
php artisan test
# Run specific test suite
php artisan test --testsuite=Feature
# Run with coverage
php artisan test --coverageFrontend Tests:
cd frontend
# Run tests
npm run test
# Run with coverage
npm run test:coverage
# Run in watch mode
npm run test:watchCreating a migration:
cd backend
php artisan make:migration create_students_tableMigration template:
Schema::create('students', function (Blueprint $table) {
$table->uuid('id')->primary()->default(DB::raw('gen_random_uuid()'));
$table->uuid('organization_id')->index();
$table->string('full_name');
$table->timestamps();
$table->softDeletes();
$table->foreign('organization_id')
->references('id')
->on('organizations')
->onDelete('cascade');
});Login:
POST /api/auth/login
Content-Type: application/json
{
"email": "user@example.com",
"password": "password"
}
Response:
{
"user": { "id": "uuid", "email": "user@example.com" },
"token": "jwt-token-here"
}Get User:
GET /api/auth/user
Authorization: Bearer {token}
Response:
{
"user": { "id": "uuid", "email": "user@example.com" },
"profile": { "id": "uuid", "organization_id": "uuid", "role": "admin", ... }
}Logout:
POST /api/auth/logout
Authorization: Bearer {token}List Students:
GET /api/students?organization_id={uuid}&search=محمد
Authorization: Bearer {token}Create Student:
POST /api/students
Authorization: Bearer {token}
Content-Type: application/json
{
"full_name": "محمد أحمد",
"father_name": "أحمد علي",
"admission_no": "2024001",
"gender": "male",
"school_id": "uuid"
}Update Student:
PUT /api/students/{id}
Authorization: Bearer {token}
Content-Type: application/json
{
"school_id": "new-school-uuid"
}For complete API documentation, visit /api/documentation (coming soon with Swagger/OpenAPI).
Solution:
# Check if Laravel is running
cd backend
php artisan serve
# Check if database is accessible
php artisan db:showSolution:
# Verify PostgreSQL is running
# Windows:
services.msc # Check PostgreSQL service
# Linux/Mac:
sudo systemctl status postgresql
# Check .env database credentials
cat backend/.env | grep DB_Solution:
cd backend
composer dump-autoload
php artisan clear-compiled
php artisan cache:clear
php artisan config:clearSolution:
- Open Chrome DevTools (F12)
- Go to Network tab
- Click "Request blocking" icon
- Disable all blocking rules
- Refresh page
Solution:
# Ensure backend is running on port 8000
curl http://localhost:8000/api/organizations
# Check Vite proxy configuration in vite.config.ts
# Should proxy /api to http://localhost:8000Solution:
cd frontend
rm -rf node_modules package-lock.json
npm install
npm run devSolution:
- Disable browser extensions (especially ad blockers)
- Check DevTools Network tab for slow requests
- Clear browser cache (Ctrl+Shift+Delete)
- Ensure
refetchOnWindowFocus: falsein React Query hooks
Causes:
- Token expired or invalid
- Token not sent in Authorization header
- User account disabled
Solution:
// Check token in localStorage
console.log(localStorage.getItem('api_token'));
// If invalid, clear and re-login
localStorage.removeItem('api_token');
// Navigate to login pageCauses:
- User not assigned to organization
- Organization was deleted
- Cross-organization access attempt
Solution:
-- Check user's profile in database
SELECT * FROM profiles WHERE id = 'user-uuid';
-- Assign user to organization
UPDATE profiles
SET organization_id = 'org-uuid'
WHERE id = 'user-uuid';Causes:
- CORS middleware not configured
- Frontend URL not in allowed origins
Solution:
// In backend/config/cors.php
'allowed_origins' => [
env('FRONTEND_URL', 'http://localhost:5173'),
'http://localhost:5173',
],
// In backend/.env
FRONTEND_URL=http://localhost:5173If the application is slow:
- Enable caching:
cd backend
php artisan config:cache
php artisan route:cache
php artisan view:cache- Check database queries:
# Enable query logging in .env
DB_LOG_QUERIES=true
# Check logs
tail -f backend/storage/logs/laravel.log- Optimize frontend:
cd frontend
npm run build # Production build with optimizationsWe welcome contributions! Please follow these steps:
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Make your changes following our coding standards
- Write/update tests
- Commit your changes:
git commit -m 'feat: add amazing feature' - Push to the branch:
git push origin feature/amazing-feature - Open a Pull Request
We follow Conventional Commits:
feat: add student photo upload
fix: resolve organization scoping issue
docs: update API documentation
style: format code with prettier
refactor: simplify authentication logic
test: add tests for student creation
chore: update dependencies
This project is licensed under the MIT License - see the LICENSE file for details.
- Laravel - PHP Framework
- React - UI Library
- shadcn/ui - UI Components
- TanStack Query - Data Fetching
- Tailwind CSS - CSS Framework
- GitHub Issues: Report a Bug
- Discussions: Ask Questions
- Email: support@nazim.local
- Website: https://nazim.local
