An intuitive web-based scheduler for managing multi-location events with participant tracking and export capabilities, now full-stack with an optional NestJS + PostgreSQL backend for accounts, database persistence, and real-time collaboration.
Features โข Architecture โข Getting Started โข Scripts โข Testing โข Tech Stack
- Create and manage multiple locations/rooms/stages (columns)
- Drag-and-drop reordering of locations
- Visual organization of concurrent events
- Add and manage participants across all events
- Assign multiple participants to each task
- Track participant schedules and workload
- Prevent scheduling conflicts for the same participant
- Detailed participant statistics with task breakdown
- Multiple projects (boards)
- Drag-and-drop task creation and repositioning
- Resize tasks to adjust duration
- Visual time-based calendar view
- Adjustable interval precision
- Time conflict detection for participants
- Screenshot Export: Capture entire schedule as PNG
- Bulk Export: Download all participant schedules as ZIP of individual PDFs with task details
- Share Configuration: Export/import via base64-encoded JSON, no server required
- Sign in with Google to unlock accounts and persistence; the app stays fully usable anonymously
- A built-in impersonation/test-mode path (
AUTH_TEST_MODE) provides a local login flow for development and automated testing without the real Google consent screen
- Anonymous users: schedules are saved to
localStorage, exactly as before, with no backend calls - Logged-in users: boards persist to PostgreSQL and survive reloads and devices
- First-login migration: existing local boards are imported into your account once (idempotent), and local data is preserved, just no longer used while authenticated
- Share a board through a generated invite link
- Assign an owner / editor / viewer role per invite
- Viewers are read-only: editing, dragging, resizing, and creation are disabled
- Role enforcement happens server-side on every change
- Live sync over a Socket.IO
/collabgateway: tasks, columns, and participants update instantly across collaborators - Live presence and collaborator cursors rendered on the board, each with a per-user color and name label
- Last-write-wins per entity; presence cursors are ephemeral and never persisted
The project is a pnpm monorepo: the Angular app lives in apps/frontend (@scheduler/frontend) and the NestJS backend in apps/backend (@scheduler/backend). Anonymous use requires only the frontend; the backend is additive and selected only when a user is authenticated.
โโโโโโโโโโโโโโโโโโโโโโโโโโโโ REST /api โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Frontend (Angular 18) โ โโโโโโโโโโโโโโโโโโโโโโโโถ โ Backend (NestJS 11) โ
โ http://localhost:4200 โ โ http://localhost:3100 โ
โ โ โโโโโโโโโโโโโโโโโโโโโโโ โ global prefix: /api โ
โ localStorage (anon) โ Socket.IO /collab โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโถ โโโโโโโโโโโโโโฌโโโโโโโโโโโโโโ
โ Kysely + pg
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ PostgreSQL 16 โ
โ localhost:5432 โ
โ dbs: scheduler, โ
โ scheduler_test โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- Frontend (
:4200): Angular 18 SPA. Anonymous boards live inlocalStorage; authenticated boards are read/written via REST and synced in real time over WebSockets. - Backend (
:3100, API prefix/api): NestJS REST API plus a Socket.IO gateway on the/collabnamespace. Health endpoint athttp://localhost:3100/api/health. - Database (
:5432): PostgreSQL accessed through Kysely with apgpool.
Naming convention: the database is snake_case; REST and WebSocket JSON payloads are camelCase, mapped at the API boundary. Kysely is used directly (no CamelCasePlugin).
| Module | Responsibility |
|---|---|
DatabaseModule |
Global Kysely provider (token KYSELY) over a pg.Pool; migrations and shutdown hook |
AuthModule |
Google OAuth + JWT, JwtAuthGuard, GET /api/auth/me, and test-only POST /api/auth/impersonate |
UsersModule |
User repository (upsert by Google id / email, find by id) |
BoardsModule |
Boards plus their content (columns, tasks, participants) with membership/role enforcement, POST /api/boards/import |
InvitesModule |
Shareable invite links, role assignment, and invite acceptance |
CollaborationModule |
Socket.IO /collab gateway: JWT handshake, board rooms, entity ops, and presence cursors |
- Node.js 22 or higher
- pnpm 10 or higher (
corepack enablewill provide the pinned version) - PostgreSQL 16 on port
5432โ via the bundleddocker-compose.yml(recommended) or a native install
git clone https://github.com/davidrojom/scheduler.git
cd scheduler
# Install both apps from the workspace root (single lockfile)
pnpm installThe frontend alone is enough to run the app anonymously. The steps below add the backend for accounts, persistence, and collaboration.
Option A โ Docker (recommended). Starts Postgres 16 and creates both scheduler and
scheduler_test (plus the pgcrypto extension) on first run:
docker compose up -d # postgres on localhost:5432, user/password: postgres/postgresOption B โ native Postgres.
createdb scheduler
createdb scheduler_test # used by the backend e2e testsCopy the example file and adjust as needed:
cp apps/backend/.env.example apps/backend/.env| Variable | Description |
|---|---|
PORT |
Backend port (3100) |
DATABASE_URL |
PostgreSQL connection string for the scheduler database |
JWT_SECRET |
Secret used to sign JWTs (use a long random value) |
GOOGLE_CLIENT_ID |
Google OAuth client id (optional placeholder for local dev) |
GOOGLE_CLIENT_SECRET |
Google OAuth client secret (optional placeholder for local dev) |
GOOGLE_CALLBACK_URL |
OAuth callback, e.g. http://localhost:3100/api/auth/google/callback |
FRONTEND_URL |
Frontend origin for CORS and redirects (http://localhost:4200) |
AUTH_TEST_MODE |
When true, enables the local impersonation login path (/api/auth/impersonate) |
The Google credentials are optional and may stay as placeholders for local development; the real consent flow is not required. Set
AUTH_TEST_MODE=trueto enable the local impersonation login path used for development and automated testing. The impersonate endpoint is inert unlessAUTH_TEST_MODE=true.apps/backend/.envis gitignored, onlyapps/backend/.env.exampleis versioned, never commit secrets.
pnpm --filter @scheduler/backend migratepnpm start:backendVerify it is up: http://localhost:3100/api/health.
pnpm start:frontendOpen http://localhost:4200.
Run from the repo root. The root package.json exposes convenience wrappers; you can always call any app script directly with pnpm --filter <pkg> <script>.
| Command | Description |
|---|---|
pnpm install |
Install all apps (single lockfile) |
pnpm build |
Build every app (pnpm -r build) |
pnpm lint |
Lint every app (pnpm -r lint) |
| Command | Description |
|---|---|
pnpm start:frontend |
Start dev server on localhost:4200 |
pnpm --filter @scheduler/frontend start:host |
Start dev server accessible on network (0.0.0.0) |
pnpm build:frontend |
Production build to apps/frontend/dist/scheduler |
pnpm --filter @scheduler/frontend watch |
Development build with watch mode |
pnpm --filter @scheduler/frontend test |
Run Karma/Jasmine tests |
pnpm --filter @scheduler/frontend lint |
Run ESLint (angular-eslint) |
| Command | Description |
|---|---|
pnpm start:backend |
Start NestJS in watch mode (nest start --watch) |
pnpm --filter @scheduler/backend start |
Start NestJS once |
pnpm build:backend |
Build to apps/backend/dist/ |
pnpm --filter @scheduler/backend migrate |
Apply Kysely migrations (tsx scripts/migrate.ts) |
pnpm --filter @scheduler/backend test |
Run Jest unit tests |
pnpm --filter @scheduler/backend test:e2e |
Run Jest e2e tests (supertest) against scheduler_test |
pnpm --filter @scheduler/backend lint |
Run ESLint |
pnpm --filter @scheduler/frontend test -- --watch=falseHeadless Chrome example:
CHROME_BIN=/usr/bin/google-chrome pnpm --filter @scheduler/frontend test -- --watch=false --browsers=ChromeHeadlessNoSandboxpnpm --filter @scheduler/backend test -- --runInBand # unit tests
pnpm --filter @scheduler/backend test:e2e -- --runInBand # e2e tests against scheduler_testThe e2e suite runs against the real scheduler_test database, so make sure it exists and migrations have been applied.
- Add Locations (Columns): click "Add Column", name it (e.g. "Main Stage", "Room A"), and drag to reorder.
- Add Participants: open "Manage Participants" and add names to your board.
- Create Tasks: click the calendar to create a task, set its title, assign participants, and adjust time by dragging.
- Manage Your Schedule: drag tasks between columns, resize to change duration, edit by clicking, and delete as needed.
- Total time scheduled per participant
- Complete task breakdown with times and locations
- Task count and distribution
- Remove participants from tasks or delete them entirely
- Offline share: export a base64 configuration and import it elsewhere, no account needed.
- Live collaboration (logged in): generate an invite link, choose a role (editor or viewer), and share it. Collaborators who open
/join/<token>join the board and see live updates and each other's cursors. Viewers have read-only access.
- Captures the entire schedule grid as PNG
- High-resolution output, ideal for quick sharing and presentations
- One PDF per participant showing their assigned tasks
- Includes task names, times, locations, and durations
- Bundled into a single ZIP file
- Generates a shareable base64 code
- No server required, all data lives in the code
Core
- Angular 18: modern web framework with standalone components
- TypeScript 5.5: type-safe development
- RxJS: reactive state management
UI & Styling
- TailwindCSS: utility-first CSS framework
- Bootstrap 5 + ng-bootstrap: component library
- SCSS: advanced styling
Scheduling & Calendar
- angular-calendar-scheduler: calendar component with drag-and-drop
- date-fns: modern date manipulation
- angularx-flatpickr: date picker
Export & PDF Generation
- @pdfme/generator: PDF generation engine
- html-to-image: screenshot capture
- @zip.js/zip.js: ZIP file creation
Real-time client
- socket.io-client: WebSocket client for the
/collabnamespace
- NestJS 11: modular Node.js framework
- Kysely + pg: type-safe SQL query builder over a PostgreSQL pool (no TypeORM/Prisma, and no
CamelCasePlugin) - @nestjs/jwt + passport (
passport-jwt,passport-google-oauth20): JWT auth and Google OAuth - @nestjs/websockets + @nestjs/platform-socket.io + socket.io: real-time
/collabgateway - class-validator / class-transformer: request validation and transformation
- Custom time ranges and intervals
- Multiple project support
- Mobile-responsive improvements
- Real-time collaboration via WebSockets
- Dark mode support
- Undo/Redo functionality
- Task color customization
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the project
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
- Built with Angular and NestJS
- Calendar component by angular-calendar-scheduler
- PDF generation by @pdfme
- Icons from Heroicons