Backend service for managing user-created custom events alongside a university timetable.
The service provides authenticated CRUD operations for custom events, per-user data isolation, date-range filtering, and iCalendar (.ics) export. It is implemented with Fastify, TypeScript, Bun, and MongoDB, with Docker Compose support for running the API and database together.
- Create, list, retrieve, update, and delete custom timetable events
- Bearer-token authentication with per-user event isolation
- Validation for event fields and time ranges
- Overlap-aware date-range filtering
- iCalendar (
.ics) export - MongoDB index on
(userId, startAt) - Integration tests for authentication, CRUD, isolation, validation, and export
- Dockerized API and MongoDB setup
All event endpoints require a bearer token.
| Method | Endpoint | Description |
|---|---|---|
POST |
/events |
Create a custom event |
GET |
/events |
List the authenticated user's events |
GET |
/events/:id |
Retrieve one event |
PATCH |
/events/:id |
Partially update one event |
DELETE |
/events/:id |
Delete one event |
GET |
/events/export.ics |
Export the user's events as iCalendar |
See API.md for request/response details and examples.
The implementation keeps HTTP handling separate from event business logic:
HTTP request
↓
Bearer-token authentication
↓
Fastify route
↓
Custom event service
↓
MongoDB
Routes are responsible for request validation and HTTP responses. The service layer owns event business rules and database operations, while ical.ts handles calendar serialization.
Each event stores its owning user's identifier.
Database operations are scoped by both the requested event ID and the authenticated user's ID. This means that knowing another user's event UUID is not enough to access or modify it.
For example, if Bob requests Alice's event, the lookup behaves as:
_id = event ID
AND
userId = Bob
Since Alice's event does not satisfy both conditions, the API returns 404.
Clients send RFC 3339 timestamps.
MongoDB stores them as date values so chronological queries and indexes operate on actual time values. Responses are serialized as ISO 8601 timestamps.
Date-range queries use event overlap rather than only checking the start time:
event.endAt > from
AND
event.startAt < to
This includes events that started before the requested window but continue into it.
The service supports:
GET /events/export.ics
The export generates iCalendar VEVENT records containing the event's UID, timestamps, title, and optional description/location fields.
I chose iCalendar export as the extra feature because it is directly useful for timetable events while keeping the core data model simple. Recurring events would introduce additional complexity around recurrence rules, exceptions, and time zones.
- Bun 1.4.2+
- Docker Desktop (optional for running the persistent MongoDB service)
bun installbun run devThe API runs at:
http://localhost:3000
Swagger UI:
http://localhost:3000/documentation
Scalar:
http://localhost:3000/reference
By default, development uses an in-memory MongoDB instance.
bun testbun run compile
bun run checkThe complete application can be run with:
docker compose up --buildThis starts:
- the Fastify API
- a MongoDB instance
- a healthcheck so the API waits for MongoDB to become ready
The MongoDB data directory is stored in a Docker volume so it survives container restarts.
The development authentication provides two users:
Alice: Bearer alice-dev-token
Bob: Bearer bob-dev-token
These tokens are only for local development and testing.
The test suite covers:
- unauthenticated requests
- event creation and retrieval
- partial updates
- deletion
- per-user isolation
- invalid event ranges
- iCalendar export
- MongoDB initialization and collection registration
The supplied USThing starter already provides MongoDB integration and in-memory MongoDB support for development and tests. Reusing that infrastructure keeps the implementation focused on the requested timetable feature.
Events can be modified field-by-field, so PATCH matches the partial-update behavior of the API.
The main list operation is scoped to a single user and ordered by event start time. The compound index matches that access pattern.
The service queries by both event ID and authenticated user ID. Returning 404 makes an unauthorized event indistinguishable from a nonexistent one.
For a production deployment, I would replace the development bearer tokens with the university's real identity provider, move credentials entirely outside source control, add pagination for large event collections, and add rate limiting and audit logging.
src/
app.ts
options.ts
auth/
users.ts
plugins/
auth.ts
init-mongo.ts
sensible.ts
routes/
events/
index.ts
services/
custom-events.ts
ical.ts
types/
custom-event.ts
test/
routes/
events.test.ts
auth-schema.test.ts
init-mongo.test.ts
mongo.test.ts
options.test.ts