Same idea as the simple Svelte tutorial, but not simple. This app covers things you'll actually run into when building something real: module boundaries, validation, rate limiting, caching, migrations, jobs, and proper separation of concerns.
Built with Meteor 3.4.1-rc.1, Svelte 5, Skeleton UI, and Tailwind CSS v4.
Demo: https://complex-todos-svelte.sandbox.galaxycloud.app/
| Runtime | Meteor 3.4.1-rc.1 |
| Frontend | Svelte 5 (runes) |
| UI | Skeleton UI v4 + Tailwind CSS v4, Cerberus theme |
| Build | Rspack |
| Database | MongoDB |
| Validation | Zod + jam:method |
| Caching | node-cache |
| Code Quality | oxlint (OXC) + import plugin |
| Tests | Mocha + Chai (integration), Cypress (E2E) |
meteor npm install
npm start
# http://localhost:3000/Other commands:
| Command | What it does |
|---|---|
npm test |
Integration tests (Mocha, watch mode) |
npm run test:headless |
Integration tests (Mocha, headless/CI) |
npm run e2e |
Open Cypress UI for interactive E2E testing |
npm run e2e:headless |
Run E2E tests headless (CI-friendly) |
npm run visualize |
Bundle analyzer in production mode |
npm run lint |
Lint (oxlint) |
npm run lint:fix |
Lint and auto-fix (oxlint) |
The app must be running (npm start) before you run Cypress.
The code follows a modular monolith approach, organized by feature, not by layer. You should be able to delete a module folder and have everything else keep working.
imports/modules/tasks/
├── database/tasks.js # Collection
├── enums/ # Method names, publication names, rate limits
├── taskRepository.js # Data access (extends BaseRepository)
├── taskService.js # Business logic
├── tasks.methods.js # Methods with Zod validation
├── tasks.publications.js # Publications
├── tasks.guards.js # Authorization guards
├── tasks.ensureIndexes.js # Mongo index setup
└── tasks.events.js # EventEmitter handlers (e.g. expire)
Methods are controllers: they take a request and return a result, nothing more. Services hold the business logic. Repositories talk to the database. If a service needs to notify another module, it fires an event through a shared EventEmitter instead of importing that module directly.
- Repository pattern:
BaseRepositorywith common CRUD, extended byTaskRepository - Zod validation: every method validates its input through
jam:method - Rate limiting: DDPRateLimiter, 5 req/s per method
- User caching:
node-cacheso you're not hitting MongoDB on every user lookup - Migrations: versioned files in
server/modules/migrations/, run once on startup - Scheduled jobs: tasks expire after 7 days via
Meteor.setInterval() - Events: cross-module communication through Node's EventEmitter
- Logging: level-based (debug, info, warning, error), configurable in
settings.json
Svelte 5 runes throughout: $state, $derived, $props, no $: or export let anywhere. A custom useTracker() hook bridges Meteor's Tracker.autorun() into Svelte writable stores.
The UI uses Skeleton's compound components where they add value (AppBar for navigation, Dialog for the login modal) and plain Tailwind for the rest (task cards, badges, form inputs).
Getting Skeleton UI to work with Rspack required a few tweaks in rspack.config.js:
fullySpecified: falsebecause@zag-js/svelteuses ESM imports without extensions- A separate
.svelte.jsloader rule, since these files contain Svelte runes but aren't.sveltefiles onwarnfilter to suppressstate_referenced_locallywarnings from Skeleton's internals
settings.json in the project root is for development, npm start loads it automatically. For production, point your deployment tool at a different settings file.
- Galaxy:
meteor deploy your-app.meteorapp.com- To try it quickly with a free tier and shared MongoDB:
meteor deploy your-app.meteorapp.com --free --mongo
- To try it quickly with a free tier and shared MongoDB:
- Any Node.js host:
meteor buildgives you a standard Node bundle - MUP: automated deploy to your own server over SSH
For monitoring in production, MontiAPM drops in with minimal config. Galaxy has its own built-in APM.