Inspiration
The idea came from the human need to reflect, hope, and communicate across time. People often journal to process emotions or make promises to themselves, but rarely revisit or share those thoughts. Echoes combines journaling with delayed gratification: a user writes today, forgets about it, and one day receives a surprise message from their past self—a moment of reconnection with who they were and who they wanted to become.
What We Built
A full-stack time-capsule application that:
- Authenticates users with scrypt-hashed passwords and secure session cookies
- Stores capsules with a future unlock date, mood tag, and privacy setting
- Automatically manages states: capsules stay locked until
unlock_at, then become readable - Releases to a public Wall: users can share unlocked capsules anonymously or under their name to inspire others
- Displays live stats: home page shows the count of released echoes and unique dreamers
- Manages user dashboards: authenticated users see their capsule timeline and can manage privacy/deletion
How We Built It
Frontend & Framework:
- Built with Next.js 16 (App Router) and React 19 for server-side rendering and fast interactions
- Shadcn UI components for consistent, accessible buttons and forms
- Tailwind CSS 4.2 for responsive dark-mode design
- Server Actions for form submissions without API routes
Backend & Database:
- PostgreSQL (Amazon Aurora) for relational data and time-based queries
- Indexed tables for fast lookups:
users,sessions,capsules - Partial index on
capsulesfor the public Wall feed (WHERE is_public AND released_at IS NOT NULL)
Authentication & Infrastructure:
- Passwords hashed with Node.js crypto (scrypt) using 16-byte salts and timing-safe comparison
- Sessions stored in PostgreSQL with 30-day expiry and secure HTTP-only cookies
- AWS RDS Signer + Vercel OIDC for keyless IAM authentication (no secrets stored)
- Vercel for serverless deployment and edge caching
- Vercel Analytics for monitoring
Key Technologies:
- TypeScript for type-safe server and client code
- pg (node-postgres) client for database queries
- nanoid for unique identifiers
- Lucide React icons for visual indicators (mood badges)
Challenges We Faced
Time-based state management: Capsules have multiple states (locked, unlocked, released). We solved this with SQL queries that check
unlock_at <= now()andis_public = trueto determine visibility—no background jobs needed.Secure authentication without secrets: Storing database passwords in environment variables is risky. We integrated AWS IAM OIDC through Vercel Functions, allowing the app to assume an IAM role at runtime and sign temporary RDS tokens. This eliminated long-lived credentials.
Password security: We avoided bcrypt (which requires the
better-sqlite3native module on Vercel). Instead, we used Node.jscrypto.scryptwith salts and timing-safe comparison to prevent timing attacks.Session persistence across serverless functions: HTTP-only cookies ensure sessions survive request boundaries, and session lookups validate both the session ID and expiry time.
Querying unlocked public capsules efficiently: A partial index on
capsules(released_at DESC) WHERE is_public = true AND released_at IS NOT NULLensures the Wall feed stays fast even with thousands of capsules.
Accomplishments We're Proud Of
- Zero secrets in production: Database authentication via AWS IAM OIDC—no plaintext passwords in
.env. - Clean architecture: Server Actions encapsulate business logic; components are stateless and server-rendered when possible.
- Type safety: Full TypeScript coverage with shared types between server and client.
- Scalable querying: Indexed tables and partial indexes keep queries fast as the Wall grows.
- Accessible UI: Semantic HTML, ARIA labels, keyboard navigation, and dark-mode support.
- Deployed and live: The app runs on Vercel and is publicly accessible.
What We Learned
- AWS IAM OIDC is powerful: It enables keyless, just-in-time authentication—a best practice for serverless apps.
- PostgreSQL partial indexes save queries: Rather than filtering in application code, let the database do it with strategic indexes.
- Next.js Server Actions reduce boilerplate: No need for REST API routes; actions integrate form submission and revalidation seamlessly.
- Scrypt is underrated: Node.js's built-in crypto module is sufficient for password hashing without external dependencies.
- Session design matters: Storing sessions in the database (with indexes) is simpler and safer than JWT for stateful apps like this.
- Time-based features are subtle: Ensuring users can't accidentally lock capsules in the past, and that the UI clearly shows lock/unlock states, requires careful validation and UX design.
Built With
- amazon-aurora-postgresql
- amazon-web-services
- aurora-postgresql
- aws-database
- aws-iam-oidc
- next.js
- node.js
- postgresql
- react
- shadcn-ui
- tailwind-css
- typescript
- vercel
Log in or sign up for Devpost to join the conversation.