A modern, multi-tenant CRM application built with TypeScript, React, Express, and PostgreSQL.
- Multi-tenant SaaS architecture with complete data isolation
- Complete CRM entities: Organizations, Contacts, Deals, Activities, Notes
- CSV import with web-based UI for bulk data import
- Deal Pipeline with drag-and-drop stage management
- Advanced deduplication for Organizations and Contacts
- Comprehensive reporting including win rate, cycle time, forecasting
- Audit logging for all critical operations
- Role-based access control (Admin & User roles)
- RESTful API with JWT authentication
- Modern React frontend with TypeScript and Tailwind CSS
- Production-ready with health check endpoints
- Node.js 20.x + Express.js
- TypeScript
- PostgreSQL
- Prisma ORM
- JWT authentication with refresh tokens
- Bcrypt password hashing
- Helmet.js security
- Rate limiting
- React 18.x
- TypeScript
- Vite
- TanStack Query (React Query)
- React Router v7
- Tailwind CSS
- Axios
- Node.js 20+
- PostgreSQL 16+
- npm or yarn
# Backend
cd specter-crm/backend
npm install
# Frontend
cd ../frontend
npm installBackend - Copy .env.example to .env and configure:
cd backend
cp .env.example .envEdit .env with your database credentials and secrets. See backend/.env.example for all available options.
Frontend - For development, create .env.local:
cd frontend
echo "VITE_API_URL=http://localhost:3000/api" > .env.localcd backend
# Generate Prisma Client
npm run db:generate
# Push schema to database
npm run db:push
# Seed demo data
npm run db:seedThis will create:
- Default tenant: "Demo Company" (slug: demo)
- Admin user:
admin@demo.com/Admin123! - Sales user:
sales@demo.com/Sales123! - Sample organizations, contacts, deals, and activities
Customize Seed Data (optional):
You can configure the seed by setting environment variables in .env:
SEED_TENANT_NAME="Your Company"
SEED_TENANT_SLUG="yourcompany"
SEED_ADMIN_EMAIL="admin@yourcompany.com"
SEED_ADMIN_PASSWORD="YourSecurePassword123!"
SEED_SALES_EMAIL="sales@yourcompany.com"
SEED_SALES_PASSWORD="SalesPassword123!"IMPORTANT: Change these passwords after first login!
Backend:
cd backend
npm run devServer runs on: http://localhost:3000
Frontend:
cd frontend
npm run devFrontend runs on: http://localhost:5173
POST /api/auth/login- Login with email/passwordPOST /api/auth/register- Register new user (admin only)POST /api/auth/refresh- Refresh access tokenPOST /api/auth/logout- LogoutGET /api/auth/me- Get current user
GET /api/organizations- List organizationsPOST /api/organizations- Create organizationGET /api/organizations/:id- Get organization detailsPATCH /api/organizations/:id- Update organizationDELETE /api/organizations/:id- Delete organizationGET /api/organizations/:id/contacts- Get related contactsGET /api/organizations/:id/deals- Get related dealsGET /api/organizations/:id/activities- Get related activitiesGET /api/organizations/:id/notes- Get notesPOST /api/organizations/:id/notes- Add note
GET /api/contacts- List contactsPOST /api/contacts- Create contactGET /api/contacts/:id- Get contact detailsPATCH /api/contacts/:id- Update contactDELETE /api/contacts/:id- Delete contactGET /api/contacts/:id/notes- Get notesPOST /api/contacts/:id/notes- Add note
GET /api/deals- List dealsPOST /api/deals- Create dealGET /api/deals/:id- Get deal detailsPATCH /api/deals/:id- Update dealPATCH /api/deals/:id/stage- Update deal stageDELETE /api/deals/:id- Delete dealGET /api/deals/pipeline/summary- Get pipeline summaryGET /api/deals/:id/notes- Get notesPOST /api/deals/:id/notes- Add note
GET /api/activities- List activitiesPOST /api/activities- Create activityGET /api/activities/:id- Get activity detailsPATCH /api/activities/:id- Update activityPATCH /api/activities/:id/complete- Toggle complete statusDELETE /api/activities/:id- Delete activity
GET /api/reports/pipeline- Pipeline by stageGET /api/reports/win-rate- Win rate metricsGET /api/reports/cycle-time- Average cycle timeGET /api/reports/activity-volume- Activity volume metricsGET /api/reports/top-accounts- Top accounts by revenueGET /api/reports/forecast- Monthly forecast
GET /api/duplicates?entityType=ORGANIZATION|CONTACT- Get duplicate suggestionsPOST /api/duplicates/merge- Merge duplicate recordsPOST /api/duplicates/dismiss- Dismiss suggestionPOST /api/duplicates/detect/organizations- Detect organization duplicates (admin)POST /api/duplicates/detect/contacts- Detect contact duplicates (admin)
POST /api/import/start- Start CSV import job (admin only)GET /api/import/jobs- List import jobsGET /api/import/jobs/:id- Get import job statusGET /api/import/jobs/:id/logs- Get import job logs
specter-crm/
├── backend/
│ ├── prisma/
│ │ ├── schema.prisma # Database schema
│ │ └── seed.ts # Seed data
│ ├── src/
│ │ ├── config/ # Database config
│ │ ├── controllers/ # Request handlers
│ │ ├── middleware/ # Auth, validation, error handling
│ │ ├── routes/ # API routes
│ │ ├── services/ # Business logic
│ │ ├── utils/ # Helpers, validation schemas
│ │ ├── app.ts # Express app setup
│ │ └── index.ts # Entry point
│ ├── .env # Environment variables
│ └── package.json
└── frontend/
├── src/
│ ├── components/ # Reusable components
│ ├── contexts/ # React contexts (Auth)
│ ├── lib/ # API client
│ ├── pages/ # Route pages
│ ├── App.tsx # Main app component
│ └── main.tsx # Entry point
├── index.html
├── tailwind.config.js
└── package.json
- Password hashing with bcrypt (cost factor 12)
- JWT authentication with access and refresh tokens
- Token rotation on refresh
- Rate limiting (configurable, production defaults: 1000 req/15min, 10 auth attempts/15min)
- CSRF protection via Helmet.js
- Security headers (XSS, CSP, HSTS, X-Frame-Options)
- Input validation with Zod
- SQL injection prevention via Prisma
- Multi-tenant isolation enforced at database level
- CORS configuration with multiple origin support
Every database record includes a tenant_id field. Prisma middleware automatically filters all queries by the current user's tenant, ensuring complete data isolation between tenants.
The system logs:
- Record creation, updates, deletions
- Owner changes
- Deal stage changes
- Deal amount changes
- Activity status changes
- User creation/role changes
- Merge operations
Audit logs include:
- User who performed the action
- Entity type and ID
- Action type
- Before/after data (JSON)
- Timestamp
The application provides a web-based CSV import tool for bulk data migration:
- Organizations
- Contacts
- Deals
- Activities
- Web-based file upload with drag-and-drop
- Automatic field mapping with customization
- UTF-8 and Latin-1 encoding support (for international characters)
- Background processing with real-time status updates
- Optional "clear existing data" with confirmation
- Detailed import logs
- Validation and error reporting
- Navigate to Settings → Import Data (admin only)
- Upload CSV files for each entity type
- Review and adjust field mappings
- Optionally clear existing data
- Start import and monitor progress
- Organizations: Matches by exact domain OR fuzzy name matching (Levenshtein distance)
- Contacts: Matches by exact email OR fuzzy name within same organization
- Similarity threshold: 85%
- Detect duplicates and create suggestions
- Review suggestions in UI
- Select primary record
- System automatically:
- Updates all foreign key references
- Merges related records (deals, activities, notes)
- Deletes duplicate record
- Creates audit log entry
# Backend tests
cd backend
npm test
npm run test:coverage
# Frontend tests
cd frontend
npm test- Build the application:
cd backend
npm run build-
Set production environment variables in
.env:- Change JWT secrets to cryptographically random values
- Set
NODE_ENV=production - Update
CORS_ORIGINto production frontend URL - Configure database connection with connection pooling
-
Run database migrations:
npm run db:migrate- Start the server:
npm start- Build for production:
cd frontend
npm run build- Serve the
distfolder with a static file server (nginx, Apache, etc.) or CDN
The application provides health check endpoints for monitoring:
GET /health- Basic application healthGET /health/ready- Database connectivity checkGET /health/startup- Startup probe
curl http://localhost:3000/health
curl http://localhost:3000/health/ready
curl http://localhost:3000/health/startup# Open Prisma Studio (database GUI)
npm run db:studio
# Create a new migration
npm run db:migrate
# Reset database (WARNING: deletes all data)
npx prisma migrate reset- Verify database credentials in
.env - Ensure PostgreSQL instance is running and accessible
- Check
DATABASE_URLformat and connection parameters - Test connection:
psql $DATABASE_URL -c "SELECT 1"
- Verify backend is running on port 3000
- Check CORS settings match your frontend URL
- Verify
VITE_API_URLenvironment variable - Check browser console for CORS errors
- Check JWT secrets are set in
.env - Verify token expiration times are reasonable
- Clear browser localStorage and login again
- Check rate limiting isn't blocking requests
- Ensure CSV files use UTF-8 or Latin-1 encoding
- Verify file names match expected pattern (organizations.csv, contacts.csv, etc.)
- Check import logs for detailed error messages
- Ensure sufficient disk space for file uploads
Apache-2.0 license
For issues and questions, please create an issue in the project repository.