Skip to content

Repository files navigation

Diary

A private, encrypted place to keep track of your thoughts.

Website · Changelog · Documentation · MIT License

Diary is a journaling app built around one idea: what you write stays yours. Entry content is encrypted before it reaches the database, every read and write is scoped to the signed-in owner, and the writing surface is deliberately plain — a title, a page, and nothing asking for your attention.

Features

  • Distraction-free editor with per-entry font family and size, plus a blur toggle for writing in public.
  • Autosave that debounces as you type and backs off when the network fails, so you never press save.
  • Encrypted at rest — entry content is stored as AES-256-GCM ciphertext, so a database dump contains no readable text.
  • Passwordless accounts through Better Auth and Resend, using six-digit email codes. Stripe powers the optional Plus plan.
  • Light and dark themes, server-side rendering, and a mobile layout.

Tech stack

Layer Choice
Runtime and package manager Bun
Monorepo tasks Turborepo
Web TanStack Start, React 19, TanStack Query, Tailwind CSS, shadcn/ui, CodeMirror
API Elysia
Database PostgreSQL with Drizzle ORM
Auth Better Auth with Resend
Payments Stripe
Validation Zod contracts shared by both apps
Lint and format Biome

Project structure

Diary is a Bun and TypeScript monorepo:

apps/web            TanStack Start web application, server-rendered by Bun
apps/api            Elysia HTTP API — the only writer of entry data
packages/contracts  Zod request, response, and domain contracts shared by both apps
packages/database   Drizzle schema, migrations, and the PostgreSQL connection
infra               PostgreSQL image with the initial Diary schema
docs                Full developer documentation

The browser talks to the API directly with a Better Auth session cookie; the web server renders UI and never proxies entry data.

Getting started

Prerequisites

  • Bun 1.3.14 or newer
  • PostgreSQL — use the Compose service in this repository or your own server
  • A Resend API key and verified sending domain
  • A Stripe account with one recurring Plus price
  • Docker, if you want the Compose database or the full container stack

The API validates its database, Better Auth, Resend, encryption, and Stripe configuration at startup. Use test-mode Stripe credentials locally.

1. Install

git clone https://github.com/kyledickey/diary.git
cd diary
bun install

2. Configure

cp .env.example .env

Fill in BETTER_AUTH_SECRET, RESEND_API_KEY, ENCRYPTION_KEY, and the Stripe values. Every variable is documented in docs/configuration.md.

Keep all secret API values server-side in the API environment. Only VITE_* values are exposed to the browser, and they are baked into the client bundle at build time. The development scripts load the repository-root .env file for both applications.

3. Start PostgreSQL

Bring up the Compose database, then apply all checked-in migrations:

docker compose up -d infra
bun run migrate

bun run migrate also works with an empty external PostgreSQL database. It recognizes an older Compose schema and safely records its initial migration before applying newer migrations.

4. Connect Stripe

Expose the local API with a tunnel, then point Stripe at https://<tunnel-host>/api/auth/stripe/webhook. Copy the endpoint signing secret into STRIPE_WEBHOOK_SECRET. Better Auth verifies the webhook and keeps the local subscriptions table synchronized.

5. Run

bun dev

The web app runs at http://localhost:3000 and the API at http://localhost:8080.

6. Verify

curl http://localhost:8080/health
# {"status":"ok","timestamp":"..."}

Sign up at http://localhost:3000 using a one-time email code, then create an entry and type into it — the header shows "Saving" and settles on "Edited …". The generated API reference is at http://localhost:8080/openapi.

Commands

bun dev          # run both apps
bun dev:web      # run only TanStack Start
bun dev:api      # run only Elysia
bun check        # typecheck every workspace
bun test         # run the test suite
bun run build    # build every workspace
bun lint         # lint the repository
bun format       # format the repository

After building, run either production service directly:

bun --filter @diary/web start
bun --filter @diary/api start

Database commands are routed through the database package and read DB_URL from the environment:

bun run db:generate                        # generate SQL from the schema
bun run migrate                            # apply migrations
bun --filter @diary/database db:studio     # browse the data

Testing

Tests use bun:test and live beside the code as *.test.ts. Run the whole suite with bun test, or a single file:

bun test apps/api/src/lib/cipher.test.ts

Business rules live in the service layer and take interfaces, so tests exercise real logic against in-memory doubles with no database or network.

Containers

The repository has focused production images for each runtime:

  • infra/Dockerfile — PostgreSQL with the initial Diary schema
  • apps/api/Dockerfile — the bundled Elysia API
  • apps/web/Dockerfile — the TanStack Start SSR server and static assets

Run the complete stack through Compose:

cp .env.example .env
# Fill in the Better Auth, Resend, Stripe, encryption, and database values.
docker compose up -d infra
bun run migrate
docker compose up --build

The web app is available at http://localhost:3000, the API at http://localhost:8080, and PostgreSQL at 127.0.0.1:5432. Stop the stack with docker compose down; add --volumes only when you intentionally want to delete local database data.

Each image can also be built independently from the repository root:

docker build -f infra/Dockerfile -t diary-infra .
docker build -f apps/api/Dockerfile -t diary-api .
docker build \
  -f apps/web/Dockerfile \
  --build-arg VITE_API_URL=http://localhost:8080 \
  -t diary-web .

See docs/deployment.md for deploying the complete stack to Railway with managed PostgreSQL and separate web and API services.

Contributing

Issues and pull requests are welcome.

  1. Branch from main.
  2. Make the change. Biome owns formatting and linting — four-space indentation, 100-column lines, double quotes — so run bun format rather than hand-tuning style.
  3. Run bun check && bun test && bun lint before opening a pull request. There is no CI workflow, so these checks are the gate.
  4. Note anything user-facing in CHANGELOG.md.

A few conventions worth knowing before you start:

  • Shared request and response shapes belong in packages/contracts, so the web app and API cannot drift apart.
  • Keep business rules in the API's service layer, database access in repositories, and scope every document query to (id, owner_id).
  • Never route user input through the Markdown rendering used for policy pages.

docs/development.md has the full workflow, including recipes for adding an endpoint, changing the schema, and adding a UI component.

Documentation

Full documentation lives in docs/:

Guide Covers
Getting started First run, end to end
Architecture Services, boundaries, runtime flows
Configuration Every environment variable
HTTP API Auth, documents, billing, errors
Data model Schema, migrations, ciphertext format
Web application Routes, data layer, editor
Development Commands, tests, change recipes
Deployment Docker, Compose, Railway
Security Auth, ownership, encryption, secrets

Security

  • Better Auth sessions are resolved from HTTP-only cookies at the API boundary.
  • Every entry lookup and mutation is scoped to the authenticated owner.
  • Entry content uses authenticated AES-256-GCM encryption.
  • Stripe portal sessions are created from the authenticated user's stored customer ID; customer IDs are never trusted from browser input.

This is server-side encryption at rest, not end-to-end encryption, and ENCRYPTION_KEY has no rotation path — see docs/security.md for the full model and its limits.

Please report vulnerabilities privately to hi@kyle.so rather than opening a public issue.

Changelog

See CHANGELOG.md for product history.

License

MIT © Kyle Dickey

About

A private and secure place to keep your thoughts.

Resources

Security policy

Stars

8 stars

Watchers

1 watching

Forks

Releases

Sponsor this project

Used by

Contributors

Languages