Contacts CRUD
Create, edit, delete, favorite, and browse contacts with multi-phone JSON fields.
NeedOwners need one reliable book, not scattered notes.
ValueSingle source of truth for important numbers.
ProjectsCase Study

Apna Contacts solves a simple, painful problem: important numbers live in one person’s phone, while family and staff keep asking for the electrician, the doctor, or the shop supplier in group chats.
The product is a private vault—not a public social directory. Owners create named workspaces (home, shop, family), invite members as view-only, and issue guest share links with optional passwords and expiry.
The engineering story is defense in depth: Server Actions enforce roles, Postgres RLS isolates tenants, and guests never touch tables through the Data API—only SECURITY DEFINER RPCs after a signed session cookie.
The build evolved from a single-owner family book into a multi-tenant SaaS with Google OAuth, CSV import (including Google Contacts), CSP nonces, and privacy-aware SEO that keeps private routes out of indexes.
Households and shops store numbers in personal phones or shared Google accounts, then forward screenshots, CSVs, or full account access whenever someone needs a lookup.
Contact books hold trusted personal and business relationships. The product only works if sharing is easy for guests and impossible to abuse—view-only by default, tenant isolation by design.
Apna Contacts keeps one source of truth for numbers and three clear access modes:
Full CRUD on contacts, categories, favorites, CSV import, invites, guest links, and workspace settings across multiple named vaults.
Authenticated view-only access to a single vault—search and open contacts without create, edit, or delete rights.
Password-optional, expiring links at /g/{token}. Guests get a signed cookie session and a mobile-first read-only contact UI—no signup.
All / Favorites / Recent tabs, multi-phone contacts, call / WhatsApp / copy actions, and category badges inspired by familiar phone contact apps.
Dual authorization (app roles + RLS), HMAC guest cookies, rate-limited share attempts, CSP with nonces, and noindex on private routes.
Families and shops keep one trusted book, share lookup access without edit rights, and separate home vs work vaults—while guests never become members and never gain table-level API access.
Create, edit, delete, favorite, and browse contacts with multi-phone JSON fields.
NeedOwners need one reliable book, not scattered notes.
ValueSingle source of truth for important numbers.
Search by name, phone, or category; filter All / Favorites / Recent.
NeedLookups happen on mobile, under time pressure.
ValueFaster find → call / WhatsApp.
Import standard and Google Contacts CSV with preview and validation.
NeedExisting phone books should not be retyped.
ValueLow-friction migration into the vault.
Multiple workspaces per account (e.g. Home, Shop) with active-workspace context.
NeedHome and business contacts should not mix.
ValueClean separation without multiple apps.
Email invite tokens, join flow, revoke, and member remove—members stay view-only.
NeedTrusted family needs access without owner powers.
ValueShared access without shared passwords for members.
Totals, favorites, weekly adds, category breakdown, and recent updates.
NeedOwners need a quick health check of the vault.
ValueClear overview before diving into contacts.
Create, list, and revoke guest links with optional password and expiry.
NeedRelatives should not need accounts for a quick lookup.
ValueFrictionless read access under owner control.
HMAC-signed HTTP-only cookie after RPC session create; middleware validates signature then DB session.
NeedAnonymous access must still be authenticatable and short-lived.
ValueGuest UX without opening the Data API to anon.
Read-only list and detail under /{slug}/guest/contacts with call / WhatsApp / copy.
NeedGuests need the same lookup actions as members.
ValueUseful sharing, not a dead view-only dump.
Supabase Auth with signup bootstrap that creates a personal workspace via DB trigger.
NeedOwners need a fast, familiar sign-in path.
ValueLower setup friction for the person who owns the vault.
Profile, password change, and delete-account confirmation.
NeedPersonal data products need clear exit and security hygiene.
ValueOwner trust and compliance-minded UX.
Signs up or signs in (email / Google)
Personal workspace bootstrapped automatically
Adds contacts or imports CSV
Optionally creates a second vault (e.g. Shop)
Invites family members as view-only
Creates a guest share link for relatives
Accepts invite and signs in
Opens the assigned vault contacts
Searches or browses Favorites / Recent
Calls, WhatsApps, or copies a number
Cannot create, edit, or delete contacts
Opens /g/{token}
Enters optional share password
Receives signed guest session cookie
Browses read-only contacts for that vault
Calls or WhatsApps without creating an account
Server Actions check owner / member / guest roles; Postgres RLS isolates every row by workspace and blocks member writes.
Business valueA UI bug cannot casually cross tenants or grant edit rights.
Anonymous clients have no table grants. Contact reads go through SECURITY DEFINER RPCs that return only the columns a share link needs.
Business valueGuest links stay useful without exposing the vault schema.
Share access uses HMAC cookies (GUEST_SESSION_SECRET), middleware validation, and a DB check that the session is still valid.
Business valuePassworded, time-bounded lookup without forcing guests to create accounts.
Migrations move from a single-owner share password to named workspaces, invites, view-only members, and repeated authz hardening.
Business valueProduction path: ship the vault, then harden sharing as real households use it.
CSV import maps Google Contacts headers, multi-phone rows, system labels, and starred → favorite—with a preview before insert.
Business valueOwners migrate existing books without cleaning spreadsheets first.
| Challenge | Solution |
|---|---|
| Guests needed one-tap lookup, but giving anon clients direct table access would leak private numbers across workspaces. | No anon table grants—HMAC guest cookies, rate-limited share passwords, and narrow SECURITY DEFINER RPCs for list/get only. |
| App-layer role checks alone are not enough when Server Actions and the Data API can disagree on who may write. | Same rules in two places: action-level roles plus Postgres RLS that isolates tenants and restricts writes to owners. |
| A single shared book mixed home and shop numbers, and a one-password share link could not support invites or view-only staff. | Named workspaces with RLS, member invites as view-only, and guest share links as a separate path. |
| Early share-password design broke as soon as households needed multiple vaults and harder session rules. | Incremental migrations—workspaces, invitations, guest_share_links—then authz and input hardening passes against real misuse cases. |
Recommended capture order for the case-study gallery.
On private contact data, the access model is the product. Owners, members, and guests need different paths—one shared login is the wrong default.
UI checks and RLS must agree. If only one layer is correct, multi-tenant contact data will eventually leak through the other.
Guest safety improves when anonymous users never receive table grants and only call RPCs that return a minimal column set.
| Priority | Improvement | Business value |
|---|---|---|
| High | Invite email delivery (Resend) so owners do not rely only on copyable links | Faster member onboarding with less manual sharing. |
| High | Fine-grained roles and selective contact sharing beyond owner vs view-only | Better fit for shops where some staff need limited edits. |
| Medium | Workspace switcher and settings UX polish for multi-vault owners | Clearer day-to-day vault management. |
| Medium | Contact activity / recent-call hints for guests and members | Faster repeat lookups for frequently used numbers. |
| Low | Optional WhatsApp deep-link templates and label presets per category | Slightly faster outreach from the detail screen. |
Apna Contacts is a full-stack case study in private data sharing—workspaces, RLS, guest sessions, and a mobile-first vault UX. If you are building something where permissions are the product, let’s talk.
Start a conversationNotes on how guest access, tenancy, and authorization stay aligned across the Next.js app and Supabase.
Owner, member, and guest are different products in one codebase. Write paths require owner; members and guests only read. That rule is enforced in role helpers and again in RLS policies.
Guest share passwords protect the link—not the workspace membership model. Guests never become members through a share URL.
Every contact, category, invite, and share link is scoped by workspace_id. Signup bootstraps a personal workspace via a database trigger so owners land in a usable vault immediately.
The workspace slug in guest URLs is branding only—the active guest workspace always comes from the validated session, never from a client-trusted path segment alone.
The SQL history is part of the product story: initial contacts schema, production owner RLS, guest sessions, multi-tenant workspaces, member view-only, then waves that hide share tokens, bind invites to auth emails, and harden rate limiting.
That arc is deliberate—ship a useful vault, then harden the surfaces that appear once real sharing begins.
Full Stack Product Engineer
Personal product / SaaS build
2026
Solo build
Next Project
Ask about projects, skills, or how to hire.