A full-stack legal case management platform for law firms and independent practitioners. CaseBox centralises case tracking, hearing session notes, internal comments, document storage, and strict role-based access — with a custom-designed frontend and a Django REST API backend.
NOTE: It is suggested to wait a minute or so for Backend to connect coz I'm burnin free tier.
Legal workflows are fragmented by default. Case files live in folders, hearing notes get lost in notebooks, and client visibility into their own cases is either nonexistent or completely uncontrolled. For small firms and solo practitioners, there is no lightweight tool that handles the full picture — case lifecycle with proper legal statuses, per-hearing session notes, internal advocate-only comments hidden from clients, document uploads, and role-gated access — without the overhead of enterprise software.
CaseBox solves this. Every case is tracked with court-specific metadata, assigned to the right people across four roles, and access is enforced at the database query level — not just the UI layer.
Key capabilities:
- Case management with 5 legal statuses, 8 case types, and 4 priority levels
- Per-hearing session notes with next hearing date tracking, added by advocates or admin
- Internal comments — visible only to admin and advocates, never to clients
- Client visibility toggle per case — admin controls exactly what each client sees
- Progress tracking (0–100%) per case, admin-controlled
- Role-based queryset filtering — each role's query is scoped at the ORM level
- Advocate coverage includes both client-side and opposition-side assignments
- Client accounts blocked at token generation until admin approves
- JWT authentication with automatic silent token refresh
- Admin dashboard stats — case counts by status/type, upcoming hearings, pending approvals
- Audit logging on case views and edits
- Document system with per-role visibility filtering
- Custom-designed frontend — no component library
Secure login/logout with JWT (access + refresh token pair). The custom CaseBoxTokenObtainPairSerializer extends SimpleJWT to embed role and is_approved directly into the token payload — the frontend renders the correct dashboard at login without an extra API call.
Four roles:
- Admin — full access; creates, edits, and deletes all cases; manages users; approves clients; accesses dashboard stats and audit log
- Advocate — read access to cases where assigned as
client_advocateoropposition_advocate; can add hearing notes and internal comments - Judge — read access to cases where assigned as
judge - Client — read access only to their own cases where
is_visible_to_client=True; documents filtered to client-visible ones only
The can_access property on the User model enforces the approval gate: non-client roles always pass, clients only pass if is_approved=True. This check runs inside the token serializer — unapproved clients are blocked at login, not just at the view layer.
Each case stores:
case_no(unique),case_title,case_type,priority,tags(comma-separated)court_name,court_city- Four role FKs:
client,judge,client_advocate,opposition_advocate - Dates:
filing_date,next_hearing_date,last_hearing_date - Text fields:
last_verdict,final_verdict,case_summary status— ongoing, adjourned, judgement_reserved, closed, disposedprogress— integer 0–100, admin-controlled via dedicated endpointis_visible_to_client— boolean, toggled by admin per case
Each case has multiple HearingNote records. Each stores the hearing date, next scheduled date, note text, and who added it. Only admin and advocates can POST — judges and clients are read-only, and clients don't see hearing notes at all in the detail response.
CaseComment is strictly internal. Comments are excluded from client-facing serializer responses at the data layer — not just hidden in the UI.
Documents attach to cases via a separate documents app. The detail serializer filters the document queryset at query time — clients only receive documents where is_visible_to_client=True.
The /cases/dashboard/ endpoint (admin only) returns:
- Total case count
- Breakdown by all 5 statuses
- Breakdown by case type (non-zero types only)
- Next 8 upcoming hearings within 30 days, ordered by date
- Total user count
- Count of pending unapproved client accounts
- Full-text search across
case_no,case_title,court_name, andtags - Filter by
status,case_type,priorityvia query params - Ordering by
created_at,next_hearing_date,status,priority
Backend:
- Python 3.10+
- Django 4.x
- Django REST Framework
- SimpleJWT — custom token serializer with role embedding
- Pillow — image processing for document uploads
- dj-database-url — environment-based database config
- Gunicorn — production WSGI server
- whitenoise — static file serving
Frontend:
- React 18
- Axios — HTTP client with JWT refresh interceptor
- Context API — global auth state management
- Custom CSS — no component library, built from scratch
Infrastructure:
- Frontend: Vercel
- Backend: Render
- Database: SQLite (dev), PostgreSQL-ready via
dj-database-url
Casebox/
├── backend/
│ ├── accounts/
│ │ ├── models.py # User model — roles, approval, can_access property
│ │ ├── serializers.py # JWT serializer — embeds role + is_approved in token
│ │ ├── permissions.py # IsAdmin, IsApprovedClient permission classes
│ │ ├── views.py # Auth views, user management
│ │ └── management/commands/
│ │ └── create_admin.py # Idempotent admin creation from .env
│ ├-─ cases/
│ │ ├── models.py # Case, HearingNote, CaseComment
│ │ ├── serializers.py # CaseListSerializer, CaseDetailSerializer,
│ │ │ # HearingNoteSerializer, CaseCommentSerializer
│ │ ├── views.py # CaseViewSet with all custom actions
│ │ └── urls.py # DefaultRouter registration
│ ├── documents/
│ │ ├── models.py # Document model with is_visible_to_client
│ │ └── serializers.py # DocumentSerializer
│ ├── logs/
│ │ └── utils.py # log_action — audit trail utility
│ ├── casebox/
│ │ ├── settings.py # Environment-driven Django config
│ │ ├── urls.py # Root URL routing
│ │ └── wsgi.py # Gunicorn entry point
│ ├── media/ # Uploaded files — gitignored
│ ├── requirements.txt
│ └── manage.py
├── frontend/
│ ├── src/
│ │ ├── api/
│ │ │ ├── axios.js # Axios instance + JWT refresh interceptor
│ │ │ └── index.js # API method exports
│ │ ├── components/
│ │ │ └── Layout.jsx # App shell, sidebar, navigation
│ │ ├── context/
│ │ │ └── AuthContext.jsx # Global user state, token management
│ │ └── pages/
│ │ ├── admin/
│ │ │ ├── Dashboard.jsx # Stats, upcoming hearings, pending approvals
│ │ │ ├── CasesPage.jsx
│ │ │ └── AdminPages.jsx # User management, approvals, audit log
│ │ ├── shared/
│ │ │ └── CaseDetail.jsx # Case detail — role-aware rendering
│ │ ├── LandingPage.jsx
│ │ ├── LoginPage.jsx
│ │ ├── AuthPages.jsx # Register, pending approval screen
│ │ └── RolePages.jsx # Advocate, Judge, Client views
│ ├── .env.example
│ └── package.json
└── README.md
- Python 3.10 or higher
- Node.js 16+ and npm
- Git
git clone https://github.com/thattimelessman/Casebox.git
cd Casebox/backend
python -m venv venv
venv\Scripts\activate # Windows
# source venv/bin/activate # macOS/Linux
pip install -r requirements.txtCreate backend/.env:
SECRET_KEY=your-secret-key-here
DEBUG=True
USE_SQLITE=True
ADMIN_USERNAME=admin
ADMIN_PASSWORD=your-password
ADMIN_EMAIL=admin@casebox.law
CORS_ALLOWED_ORIGINS=http://localhost:3000python manage.py migrate
python manage.py create_admin # reads from .env, safe to re-run (idempotent)
python manage.py runserverBackend runs at http://localhost:8000
cd Casebox/frontend
npm installCreate frontend/.env:
REACT_APP_API_URL=http://localhost:8000/apinpm startFrontend runs at http://localhost:3000
- User submits credentials to
POST /api/token/ CaseBoxTokenObtainPairSerializervalidates — checksis_activeandcan_access(unapproved clients are rejected here before any token is issued)- On success, returns access token (short-lived) and refresh token (long-lived) with
roleandis_approvedin the payload - Frontend attaches the access token as
Bearerheader on every request via axios interceptor - On 401, the interceptor silently calls
POST /api/token/refresh/and retries the original request — no visible interruption - On logout, tokens are cleared from state
http://localhost:8000/api
Obtain Token
POST /api/token/
Content-Type: application/json
{
"username": "admin",
"password": "your-password"
}Refresh Token
POST /api/token/refresh/
Content-Type: application/json
{
"refresh": "<refresh_token>"
}List Cases (queryset scoped by role automatically)
GET /api/cases/
Authorization: Bearer <access_token>
# Optional query params:
# ?search=smith — searches case_no, case_title, court_name, tags
# ?status=ongoing
# ?case_type=criminal
# ?priority=urgentCreate Case (admin only)
POST /api/cases/
Authorization: Bearer <access_token>
Content-Type: application/json
{
"case_no": "CIV/2026/001",
"case_title": "Smith vs. Johnson",
"case_type": "civil",
"priority": "high",
"court_name": "District Court",
"court_city": "Delhi",
"client": 3,
"client_advocate": 4,
"judge": 5,
"status": "ongoing"
}Get Case Detail
GET /api/cases/{id}/
Authorization: Bearer <access_token>Returns full case with hearing_notes, comments (admin/advocate only), and documents (filtered for clients)
Update / Delete Case (admin only)
PUT /api/cases/{id}/
DELETE /api/cases/{id}/
Authorization: Bearer <access_token>GET /api/cases/{id}/hearing-notes/
POST /api/cases/{id}/hearing-notes/
Authorization: Bearer <access_token>
Content-Type: application/json
{
"hearing_date": "2026-03-10",
"next_date": "2026-04-15",
"note": "Arguments heard. Next date for evidence."
}POST restricted to admin and advocates only
GET /api/cases/{id}/comments/
POST /api/cases/{id}/comments/
Authorization: Bearer <access_token>
Content-Type: application/json
{
"text": "Client has provided new documents. Review before next hearing."
}PATCH /api/cases/{id}/progress/
Authorization: Bearer <access_token>
Content-Type: application/json
{ "progress": 65 }PATCH /api/cases/{id}/visibility/
Authorization: Bearer <access_token>Toggles is_visible_to_client — no body required
GET /api/cases/dashboard/
Authorization: Bearer <access_token>Sample response:
{
"total_cases": 42,
"by_status": {
"ongoing": 18,
"adjourned": 7,
"judgement_reserved": 4,
"closed": 10,
"disposed": 3
},
"by_type": {
"civil": 15,
"criminal": 12
},
"pending_clients": 3,
"total_users": 20,
"upcoming_hearings": [...]
}| Action | Admin | Advocate | Judge | Client |
|---|---|---|---|---|
| List cases | All | Assigned (both sides) | Assigned | Own + visible only |
| View case detail | ✓ | ✓ | ✓ | ✓ (filtered) |
| Create case | ✓ | ✗ | ✗ | ✗ |
| Edit / Delete case | ✓ | ✗ | ✗ | ✗ |
| Add hearing note | ✓ | ✓ | ✗ | ✗ |
| Add internal comment | ✓ | ✓ | ✗ | ✗ |
| Update progress | ✓ | ✗ | ✗ | ✗ |
| Toggle client visibility | ✓ | ✗ | ✗ | ✗ |
| Dashboard stats | ✓ | ✗ | ✗ | ✗ |
| Approve client accounts | ✓ | ✗ | ✗ | ✗ |
- Media persistence on Render: Render's free tier has an ephemeral filesystem — uploaded documents are lost on redeploy. For production, integrate
django-storageswith S3 or Cloudinary. - SQLite concurrency: Fine for demos, not for concurrent writes. Set
USE_SQLITE=Falseand provideDATABASE_URLpointing to a PostgreSQL instance — Neon and Supabase both have free tiers. - Render cold starts: Free tier spins down after inactivity. First request after idle takes 30–60 seconds — Render infrastructure limitation.
- Cloud storage for documents (S3 / Cloudinary via django-storages)
- PostgreSQL setup guide and migration scripts
- Email notifications for hearing reminders and client approval events
- Case timeline view — visual history of status and progress changes
- Bulk document upload
- Export case detail to PDF
- Test coverage for all endpoints and the permission matrix
- Mobile-responsive layout improvements
This project is licensed under the MIT License.
Questions, ideas, or found a bug?
- Email: agrajsingh04@gmail.com
- LinkedIn: thattimelessman
- GitHub: thattimelessman
NOTE: Stealing or copying this project without consent & credit would be deeply unappreciated.
Built with intent for legal professionals who deserve better tooling.