Click Below to Get the Code

Browse, clone, and build from real-world templates powered by Harper.
Tutorial
GitHub Logo

Building a Calendar and Scheduling App with Harper

This post is about using Harper's GraphQL capabilities to build an API for a calendar and scheduling backend with minimal code
Tutorial

Building a Calendar and Scheduling App with Harper

Nenne Nwodo
Developer Relations
at Harper
August 18, 2025
Nenne Nwodo
Developer Relations
at Harper
August 18, 2025
Nenne Nwodo
Developer Relations
at Harper
August 18, 2025
August 18, 2025
This post is about using Harper's GraphQL capabilities to build an API for a calendar and scheduling backend with minimal code
Nenne Nwodo
Developer Relations

Building calendar features turns into a mess fast. What starts as "just store some events" becomes Redis for caching, Kafka for real-time updates, and multiple services just to check if someone's free for a meeting.

You end up debugging why calendars aren't syncing instead of building the scheduling features users actually want.

Harper changes this. Everything runs in one process: database, cache, real-time updates, conflict detection. No network calls between services, no complex synchronization.

The Traditional Calendar Nightmare

Calendar systems get complex because of coordination between services. Your availability service needs to talk to your event service. Notifications need to sync with conflict detection. Time zones need to work across everything.

A simple "meeting time changed" notification becomes a distributed systems problem. Database updates, cache invalidation, real-time pushes, and hoping nothing fails halfway through.

Setting Up Your Calendar Schema

Harper uses GraphQL to define your data model and automatically generates REST endpoints:

type User @table @export {
  id: ID @primaryKey
  email: String @indexed
  name: String
  timezone: String
  createdAt: String @createdTime
}

type Event @table @export {
  id: ID @primaryKey
  title: String @indexed
  startTime: String @indexed
  endTime: String @indexed
  organizerId: ID @indexed
  organizer: User @relationship(from: organizerId)
  attendeeIds: [ID] @indexed
  attendees: [User] @relationship(from: attendeeIds)
  status: String @indexed
  createdAt: String @createdTime
}

type Availability @table @export {
  id: ID @primaryKey
  userId: ID @indexed
  dayOfWeek: Int @indexed
  startTime: String @indexed
  endTime: String @indexed
  isAvailable: Boolean @indexed
}

The @export directive creates REST endpoints at /User/, /Event/, and /Availability/. The @relationship directive handles joins automatically.

Simple Scheduling Operations

Create a meeting:

POST /Event/
Content-Type: application/json

{
  "title": "Product Review",
  "startTime": "2024-08-15T14:00:00Z",
  "endTime": "2024-08-15T15:00:00Z",
  "organizerId": "user123",
  "attendeeIds": ["user456", "user789"]
}

 

Get someone's calendar:

GET /Event/?organizerId=user123&startTime=gte=2024-08-01&sort=startTime

Check for conflicts:

GET /Event/?attendeeIds=user123&startTime=lt=2024-08-15T15:00:00Z&endTime=gt=2024-08-15T14:00:00Z

Everything happens in the same process. No network latency, no coordination complexity.

Real-Time Updates

Harper has WebSockets built in:

ws://localhost:9926/Event/user123

When someone accepts a meeting, all attendees see the update instantly. No message brokers needed.

Time Zone Handling

Store everything in UTC, handle conversion at query time:

GET /Event/?organizerId=user123&timezone=America/New_York&startTime=gte=2024-08-15

No separate timezone service, no complex conversion logic.

Why This Works

Harper's single-process architecture eliminates the coordination problems that make calendar systems complex. You get faster response times, automatic conflict detection, and real-time sync without managing multiple services.

You focus on building scheduling features instead of debugging distributed systems.

Building calendar features turns into a mess fast. What starts as "just store some events" becomes Redis for caching, Kafka for real-time updates, and multiple services just to check if someone's free for a meeting.

You end up debugging why calendars aren't syncing instead of building the scheduling features users actually want.

Harper changes this. Everything runs in one process: database, cache, real-time updates, conflict detection. No network calls between services, no complex synchronization.

The Traditional Calendar Nightmare

Calendar systems get complex because of coordination between services. Your availability service needs to talk to your event service. Notifications need to sync with conflict detection. Time zones need to work across everything.

A simple "meeting time changed" notification becomes a distributed systems problem. Database updates, cache invalidation, real-time pushes, and hoping nothing fails halfway through.

Setting Up Your Calendar Schema

Harper uses GraphQL to define your data model and automatically generates REST endpoints:

type User @table @export {
  id: ID @primaryKey
  email: String @indexed
  name: String
  timezone: String
  createdAt: String @createdTime
}

type Event @table @export {
  id: ID @primaryKey
  title: String @indexed
  startTime: String @indexed
  endTime: String @indexed
  organizerId: ID @indexed
  organizer: User @relationship(from: organizerId)
  attendeeIds: [ID] @indexed
  attendees: [User] @relationship(from: attendeeIds)
  status: String @indexed
  createdAt: String @createdTime
}

type Availability @table @export {
  id: ID @primaryKey
  userId: ID @indexed
  dayOfWeek: Int @indexed
  startTime: String @indexed
  endTime: String @indexed
  isAvailable: Boolean @indexed
}

The @export directive creates REST endpoints at /User/, /Event/, and /Availability/. The @relationship directive handles joins automatically.

Simple Scheduling Operations

Create a meeting:

POST /Event/
Content-Type: application/json

{
  "title": "Product Review",
  "startTime": "2024-08-15T14:00:00Z",
  "endTime": "2024-08-15T15:00:00Z",
  "organizerId": "user123",
  "attendeeIds": ["user456", "user789"]
}

 

Get someone's calendar:

GET /Event/?organizerId=user123&startTime=gte=2024-08-01&sort=startTime

Check for conflicts:

GET /Event/?attendeeIds=user123&startTime=lt=2024-08-15T15:00:00Z&endTime=gt=2024-08-15T14:00:00Z

Everything happens in the same process. No network latency, no coordination complexity.

Real-Time Updates

Harper has WebSockets built in:

ws://localhost:9926/Event/user123

When someone accepts a meeting, all attendees see the update instantly. No message brokers needed.

Time Zone Handling

Store everything in UTC, handle conversion at query time:

GET /Event/?organizerId=user123&timezone=America/New_York&startTime=gte=2024-08-15

No separate timezone service, no complex conversion logic.

Why This Works

Harper's single-process architecture eliminates the coordination problems that make calendar systems complex. You get faster response times, automatic conflict detection, and real-time sync without managing multiple services.

You focus on building scheduling features instead of debugging distributed systems.

This post is about using Harper's GraphQL capabilities to build an API for a calendar and scheduling backend with minimal code

Download

White arrow pointing right
This post is about using Harper's GraphQL capabilities to build an API for a calendar and scheduling backend with minimal code

Download

White arrow pointing right
This post is about using Harper's GraphQL capabilities to build an API for a calendar and scheduling backend with minimal code

Download

White arrow pointing right

Explore Recent Resources

Comparison
GitHub Logo

Kafka-Centered Stacks vs. a Single Harper Cluster: Where Real-Time Latency Actually Comes From

End-to-end latency in real-time pipelines comes from coordination across systems, not from any single component. Four common workloads, tested two ways, show where multi-hop architectures compound delays and where collapsing storage, messaging, and compute into one runtime changes the math.
Cache
Comparison
End-to-end latency in real-time pipelines comes from coordination across systems, not from any single component. Four common workloads, tested two ways, show where multi-hop architectures compound delays and where collapsing storage, messaging, and compute into one runtime changes the math.
Person with short dark hair and moustache, wearing a colorful plaid shirt, smiling outdoors in a forested mountain landscape.
Aleks Haugom
Senior Manager of GTM
Comparison

Kafka-Centered Stacks vs. a Single Harper Cluster: Where Real-Time Latency Actually Comes From

End-to-end latency in real-time pipelines comes from coordination across systems, not from any single component. Four common workloads, tested two ways, show where multi-hop architectures compound delays and where collapsing storage, messaging, and compute into one runtime changes the math.
Aleks Haugom
Jun 2026
Comparison

Kafka-Centered Stacks vs. a Single Harper Cluster: Where Real-Time Latency Actually Comes From

End-to-end latency in real-time pipelines comes from coordination across systems, not from any single component. Four common workloads, tested two ways, show where multi-hop architectures compound delays and where collapsing storage, messaging, and compute into one runtime changes the math.
Aleks Haugom
Comparison

Kafka-Centered Stacks vs. a Single Harper Cluster: Where Real-Time Latency Actually Comes From

End-to-end latency in real-time pipelines comes from coordination across systems, not from any single component. Four common workloads, tested two ways, show where multi-hop architectures compound delays and where collapsing storage, messaging, and compute into one runtime changes the math.
Aleks Haugom
Tutorial
GitHub Logo

Your API cache is secretly a database

Most teams treat a cache as a black box: URL-keyed blobs with a TTL, useful for speed and nothing else. In Harper, cached data lands in a real table inside the same query engine. That means filtering, joining, real-time subscriptions, and vector search all work against it.
Cache
Tutorial
Most teams treat a cache as a black box: URL-keyed blobs with a TTL, useful for speed and nothing else. In Harper, cached data lands in a real table inside the same query engine. That means filtering, joining, real-time subscriptions, and vector search all work against it.
Person with very short blonde hair wearing a light gray button‑up shirt, standing with arms crossed and smiling outdoors with foliage behind.
Kris Zyp
SVP of Engineering
Tutorial

Your API cache is secretly a database

Most teams treat a cache as a black box: URL-keyed blobs with a TTL, useful for speed and nothing else. In Harper, cached data lands in a real table inside the same query engine. That means filtering, joining, real-time subscriptions, and vector search all work against it.
Kris Zyp
Jun 2026
Tutorial

Your API cache is secretly a database

Most teams treat a cache as a black box: URL-keyed blobs with a TTL, useful for speed and nothing else. In Harper, cached data lands in a real table inside the same query engine. That means filtering, joining, real-time subscriptions, and vector search all work against it.
Kris Zyp
Tutorial

Your API cache is secretly a database

Most teams treat a cache as a black box: URL-keyed blobs with a TTL, useful for speed and nothing else. In Harper, cached data lands in a real table inside the same query engine. That means filtering, joining, real-time subscriptions, and vector search all work against it.
Kris Zyp
Tutorial
GitHub Logo

Introducing Structon: Random-Access Binary Encoding for JavaScript

Deserializing entire records to read one field is a bottleneck at scale. Structon stores objects in a binary format where any field is reachable by byte offset, with lazy getters that never allocate until you access a property. It's the encoding Harper has used internally for years, now a standalone package.
JavaScript
Tutorial
Deserializing entire records to read one field is a bottleneck at scale. Structon stores objects in a binary format where any field is reachable by byte offset, with lazy getters that never allocate until you access a property. It's the encoding Harper has used internally for years, now a standalone package.
Person with very short blonde hair wearing a light gray button‑up shirt, standing with arms crossed and smiling outdoors with foliage behind.
Kris Zyp
SVP of Engineering
Tutorial

Introducing Structon: Random-Access Binary Encoding for JavaScript

Deserializing entire records to read one field is a bottleneck at scale. Structon stores objects in a binary format where any field is reachable by byte offset, with lazy getters that never allocate until you access a property. It's the encoding Harper has used internally for years, now a standalone package.
Kris Zyp
Jun 2026
Tutorial

Introducing Structon: Random-Access Binary Encoding for JavaScript

Deserializing entire records to read one field is a bottleneck at scale. Structon stores objects in a binary format where any field is reachable by byte offset, with lazy getters that never allocate until you access a property. It's the encoding Harper has used internally for years, now a standalone package.
Kris Zyp
Tutorial

Introducing Structon: Random-Access Binary Encoding for JavaScript

Deserializing entire records to read one field is a bottleneck at scale. Structon stores objects in a binary format where any field is reachable by byte offset, with lazy getters that never allocate until you access a property. It's the encoding Harper has used internally for years, now a standalone package.
Kris Zyp