System Design
- Shorten URLs in a consistent and secure way.
- Keep infrastructure simple and low-cost for portfolio-scale usage.
- Protect API resources from abuse (rate limiting + auth + ownership checks).
- Provide reliable user-level link management and basic analytics (
clicks).
High-level architecture
Section titled “High-level architecture”Korta uses a simple 3-layer architecture:
- Frontend (Vercel, React + Vite)
- Backend API (Koyeb, Node.js + Express + TypeScript)
- Database (PostgreSQL via Prisma ORM)
The backend is a monolithic modular API (routes, controllers, middleware, services, models). It is not an MVC app with server-rendered views.
Entrypoints
Section titled “Entrypoints”- Backend entrypoint:
backend/src/main.ts- Bootstraps Express, CORS, Helmet, Passport, routes, and error middleware.
- Validates database connectivity at startup with
prisma.$connect()beforeapp.listen.
- Frontend entrypoint:
frontend/src/main.tsx- Mounts
<App />into#root. - Route definitions live in
frontend/src/App.tsx.
- Mounts
Main components
Section titled “Main components”API layer (Express routes + middleware)
Section titled “API layer (Express routes + middleware)”- Responsibility: request validation, authentication, rate limiting, CORS, and error normalization.
- Inputs/Outputs:
- Input: HTTP requests from browser/integrations.
- Output: JSON responses or HTTP redirects.
Config layer (backend/src/config/*)
Section titled “Config layer (backend/src/config/*)”env.ts: loads environment variables (.env.*), validates them with Zod, and exposes normalized runtime config (for example CORS origin list).logger.ts: lightweight timestamped logging utility used by startup code, middleware, and controllers; it is not an Express middleware.passport.ts: registers Google OAuth 2.0 strategy and upserts Google users in persistence.prisma.ts: configures the Prisma client (PrismaPgadapter) and reuses a singleton client viaglobalThisin non-production to avoid duplicate instances during reloads; it does not define runtime environments.- Environment modes are handled by env loading (
env.ts+NODE_ENV), not byprisma.ts.
Auth module (AuthController)
Section titled “Auth module (AuthController)”- Responsibility: orchestrates HTTP auth/account flows (register, verify email, login, forgot/reset password, profile, API key lifecycle, password change, account deletion).
- Authentication model: JWT Bearer and
X-API-Keymiddleware-driven auth; this is not HTTP Basic Auth. - Token/key generation: UUID values are used for verification tokens, reset tokens, and API keys; user primary keys remain numeric database IDs.
URL module (ShortenController)
Section titled “URL module (ShortenController)”- Responsibility: public/private shorten flows, user link listing, link update/delete flows, and redirect resolution.
- Redirect behavior: resolves
shortId, increments click count, and redirects tooriginalUrl. - Ownership behavior: mutating routes enforce ownership checks before update/delete execution.
Middleware responsibilities
Section titled “Middleware responsibilities”- Auth middleware:
authMiddleware: requires either valid Bearer token or validX-API-Key.bearerAuthMiddleware: requires a valid Bearer token only.optionalAuthMiddleware: attaches user context when token/key is valid and continues anonymously otherwise.
- Error middleware (
errorMiddleware):- Zod validation errors ->
400 Bad Request. - CORS rejections (
CORS origin not allowed) ->403 Forbidden. - Boom errors -> status passes through from Boom payload.
- Non-Boom unexpected errors ->
500 Internal Server Error.
- Zod validation errors ->
- Rate limiter middleware: configured per endpoint group (public shorten, redirect, login, register, forgot/reset password, API key regeneration).
- URL middleware:
linkExistenceValidator: checks thatshortIdexists.linkOwnershipValidator: checksshortIdexists and belongs to authenticated user.
- Validation middleware (
validateRequest): validatesbody,query, andparamswith Zod schemas.
Persistence Layer (Prisma + PostgreSQL)
Section titled “Persistence Layer (Prisma + PostgreSQL)”- Responsibility: store users/urls, enforce uniqueness (
email,shortId,apiKey), update click counters. - Inputs/Outputs:
- Input: CRUD/query operations from services/controllers.
- Output: persisted entities and query results.
Frontend architecture
Section titled “Frontend architecture”- Component organization follows an atomic/compositional structure (
atoms,molecules,organisms) with templates as a layout layer (for example auth/dashboard layouts). AuthContextshares authentication state and session actions across routed pages.- Frontend schemas (
frontend/src/schemas/*.ts) validate form/input payloads before API calls. - Shared layer (
frontend/src/shared/*) centralizes cross-cutting concerns such as SEO helpers, utilities, and constants. - Environment-dependent constants should be sourced from frontend environment variables where deployment-specific values are required.
Data flow
Section titled “Data flow”- Client sends request (
/auth/*or/urls/*). - Middleware chain applies: CORS, rate limit, validation, auth (if required).
- Controller executes business logic.
- Prisma performs database reads/writes.
- API returns normalized JSON response (or redirect for short links).
Backend tests (current strategy)
Section titled “Backend tests (current strategy)”Backend tests live in backend/tests/ and follow a pragmatic, risk-based E2E strategy: prioritize critical user journeys and stable HTTP contracts over exhaustive endpoint permutations.
Current coverage focus:
backend/tests/setup.ts- Sets deterministic test runtime defaults (
NODE_ENV=test, test DB fallback, JWT/OAuth placeholders). - Pins rate-limit windows and maxima to predictable values so
429behavior is reproducible in E2E runs.
- Sets deterministic test runtime defaults (
backend/tests/e2e/health.test.ts- Verifies health contract (
GET /api/v1/health->200,{ status: 'OK' }). - Checks versioned routing boundaries and baseline validation/error behavior for invalid auth payloads.
- Verifies health contract (
backend/tests/e2e/auth.test.ts- Covers high-risk auth/account flows: register, verify, login gating, forgot/reset password, password change, API key lifecycle, account deletion.
- Asserts abuse controls and security boundaries (rate limits, malformed/expired tokens, bearer-only endpoints, rollback when email delivery fails).
backend/tests/e2e/url.test.ts- Covers core URL contracts: public and authenticated shortening, redirect behavior, ownership enforcement, my-links isolation, update/delete lifecycle.
- Validates contract and abuse scenarios (schema failures, collisions/conflicts, invalid API keys, public/redirect throttling).
This gives solid confidence in business-critical API behavior while keeping the suite maintainable for current project scope.
Key tradeoffs
Section titled “Key tradeoffs”- Cost vs latency: optimized for low/zero-cost deployment over max performance.
- Simplicity vs resiliency: single backend + single DB, no queue/cache layer yet.
- Security vs friction: verified users and protected endpoints reduce abuse, but add auth steps.
- Accuracy vs complexity: click counts are exact per redirect update in DB, with higher DB dependency.
Risks and constraints
Section titled “Risks and constraints”- Infrastructure limits from free-tier hosting.
- Abuse/spike sensitivity despite rate limiting.
- Strong dependency on external providers (Koyeb, PostgreSQL, Google OAuth, Resend).
- No advanced failover strategy yet for DB outages.
Success metric (current target)
Section titled “Success metric (current target)”- Stable operation under portfolio-scale usage with clear security boundaries.
- A practical target is supporting up to ~1000 concurrent users with current architecture assumptions.