Executive Architecture Overview
ContactHub is a production-grade REST API and full-stack contact management service (live at contact-manager-fawn-alpha.vercel.app). Built with Go (v1.25), Chi Router v5, PostgreSQL 15, and Docker. It provides secure multi-user contact management with strict user-level data isolation, soft deletion with a 30-day retention grace period, and clean separation of concerns across four decoupled layers.
Many CRUD APIs leak multi-tenant boundaries when repository queries forget WHERE user_id = $1 filters, suffer from unique constraint collisions when users attempt to re-add previously soft-deleted phone numbers, leak account existence through login timing discrepancies, and cause slow request spikes by performing synchronous cascading deletes on user-facing paths.
System Architecture & Data Pipeline
Engineered with strict unidirectional dependency inversion: Transport Handlers decode HTTP requests → Service enforces domain business logic & security → Repository executes raw SQL queries strictly scoped to user_id → Domain models define pure Go business entities with zero framework dependencies.
HTTP Transport Layer (chi router v5)
Zero-allocation routing, JSON DTO decoding and validation, route parameter parsing, and consistent JSON response envelope formatting.
Service / Business Layer
Enforces authorization policies, orchestrates password hashing and timing-safe verification, manages JWT token lifecycles, and triggers domain state transitions.
Repository Layer (database/sql)
Executes parameterized SQL queries with strict user_id scoping to guarantee multi-tenant data boundary isolation with zero ORM overhead.
Domain Models Layer
Pure Go structs representing business entities with zero external HTTP router or database driver dependencies.
System Design Trade-Offs
Raw SQL (database/sql) vs Heavy ORM (GORM/Ent)
Engineering Rationale: Raw SQL gives full visibility over query execution plans, eliminates hidden N+1 query surprises, avoids heavy runtime reflection allocations, and explicitly takes advantage of PostgreSQL partial indexes and ON DELETE CASCADE constraints.
Timing-Attack Mitigation on Account Enumeration
Engineering Rationale: When an unknown email attempts login, standard APIs return 401 immediately (taking ~2ms), while valid accounts incur a heavy ~70ms bcrypt hash comparison. ContactHub executes a dummy bcrypt comparison against a pre-computed hash when a user is not found, making response times indistinguishable and neutralizing enumeration attacks.
Soft Deletion with Partial Unique Index vs Global Table Constraints
Engineering Rationale: A global UNIQUE constraint on phone prevents different users from saving the same contact and blocks a user from restoring a previously deleted contact. Using a composite partial index (user_id, phone) WHERE deleted_at IS NULL scopes uniqueness strictly per-user and strictly to active records.
Ticker-Based Retention Reaper vs Synchronous Table Purge
Engineering Rationale: Soft-deleted contacts are assigned a purge_at timestamp (now + 30 days). A dedicated background worker goroutine periodically runs batch deletions (DELETE FROM contacts WHERE purge_at <= NOW()), offloading vacuum overhead and table locking delays from user-facing HTTP request paths.
JWT Token Versioning vs Stateful Session Store
Engineering Rationale: To support instant session revocation upon password change without introducing a stateful Redis session dependency, users have a token_version column in PostgreSQL. JWT claims carry this version; changing a password increments the version, instantly invalidating all existing JWTs.
Database Schema & Indexing Strategy
Normalized relational schema enforcing categories lookup, users authentication table, and contacts table with cascading foreign keys and partial unique indexes.
CREATE TABLE categories (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL UNIQUE
);
INSERT INTO categories (name) VALUES ('General'), ('Family'), ('Friends'), ('Work'), ('College')
ON CONFLICT (name) DO NOTHING;CREATE TABLE users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
role TEXT NOT NULL DEFAULT 'user' CHECK (role IN ('user', 'admin')),
is_verified BOOLEAN NOT NULL DEFAULT false,
token_version INT NOT NULL DEFAULT 1,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);CREATE TABLE contacts (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
name TEXT NOT NULL,
phone TEXT NOT NULL,
email TEXT,
category_id INTEGER NOT NULL DEFAULT 1 REFERENCES categories(id) ON DELETE RESTRICT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ,
purge_at TIMESTAMPTZ
);CREATE UNIQUE INDEX contacts_user_phone_active_unique
ON contacts (user_id, phone)
WHERE deleted_at IS NULL;
CREATE INDEX idx_contacts_user_id ON contacts (user_id);Technology Stack Justifications
Go 1.25
Strong static typing, sub-millisecond route handling, native concurrency primitives, and clean standard library database interfaces.
Chi Router v5
100% net/http stdlib compatibility, zero allocations during routing, and composable middleware chaining.
PostgreSQL 15
Partial unique index capabilities, ON DELETE CASCADE foreign key lifecycles, and transactional DDL migrations.
Docker Compose
Orchestrates API backend and Postgres containers on a bridge network using Docker internal DNS (host=db) with automatic retry connection loops.
React & Next.js
Interactive responsive frontend deployed on Vercel communicating with containerized Go REST backend.
REST API Specification
| Method | Endpoint Path | Semantics & Status |
|---|---|---|
| POST | /api/v1/auth/signup | Register new user account with verification token |
| POST | /api/v1/auth/login | Authenticate user and issue JWT bearer token |
| POST | /api/v1/auth/forgot-password | Request password reset token with timing-safe enumeration protection |
| POST | /api/v1/auth/reset-password | Reset password and increment token_version to revoke existing sessions |
| GET | /api/v1/contacts | List user-scoped contacts with search, category filter, and pagination |
| POST | /api/v1/contacts | Create contact enforcing user-scoped phone uniqueness |
| DELETE | /api/v1/contacts/:id | Soft-delete contact setting deleted_at and purge_at (30-day retention) |
| POST | /api/v1/contacts/:id/restore | Restore soft-deleted contact within 30-day retention window |
| GET | /health | Health check with 2s database ping verification |
Production Takeaways & Next Steps
Engineering Lessons
- •Passing context.Context through all repository calls allowed clean database query cancellation when HTTP clients disconnect.
- •Decoupling input DTOs from internal domain models prevented accidental leaks of private fields or sensitive user data.
- •Mitigated timing attacks on user enumeration by executing dummy bcrypt comparisons when emails are not found.
Future Improvements
- •Replace in-memory sliding rate limiting with Redis sliding-window ZSET for multi-replica horizontal scaling.
- •Add Webhook push notifications for contact synchronization across external CRMs.