Skip to content

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).

Korta uses a simple 3-layer architecture:

  1. Frontend (Vercel, React + Vite)
  2. Backend API (Koyeb, Node.js + Express + TypeScript)
  3. 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.

  • Backend entrypoint: backend/src/main.ts
    • Bootstraps Express, CORS, Helmet, Passport, routes, and error middleware.
    • Validates database connectivity at startup with prisma.$connect() before app.listen.
  • Frontend entrypoint: frontend/src/main.tsx
    • Mounts <App /> into #root.
    • Route definitions live in frontend/src/App.tsx.
  • Responsibility: request validation, authentication, rate limiting, CORS, and error normalization.
  • Inputs/Outputs:
    • Input: HTTP requests from browser/integrations.
    • Output: JSON responses or HTTP redirects.
  • 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 (PrismaPg adapter) and reuses a singleton client via globalThis in 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 by prisma.ts.
  • 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-Key middleware-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.
  • Responsibility: public/private shorten flows, user link listing, link update/delete flows, and redirect resolution.
  • Redirect behavior: resolves shortId, increments click count, and redirects to originalUrl.
  • Ownership behavior: mutating routes enforce ownership checks before update/delete execution.
  • Auth middleware:
    • authMiddleware: requires either valid Bearer token or valid X-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.
  • Rate limiter middleware: configured per endpoint group (public shorten, redirect, login, register, forgot/reset password, API key regeneration).
  • URL middleware:
    • linkExistenceValidator: checks that shortId exists.
    • linkOwnershipValidator: checks shortId exists and belongs to authenticated user.
  • Validation middleware (validateRequest): validates body, query, and params with Zod schemas.
  • 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.
  • Component organization follows an atomic/compositional structure (atoms, molecules, organisms) with templates as a layout layer (for example auth/dashboard layouts).
  • AuthContext shares 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.
  1. Client sends request (/auth/* or /urls/*).
  2. Middleware chain applies: CORS, rate limit, validation, auth (if required).
  3. Controller executes business logic.
  4. Prisma performs database reads/writes.
  5. API returns normalized JSON response (or redirect for short links).

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 429 behavior is reproducible in E2E runs.
  • 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.
  • 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.

  • 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.
  • 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.
  • Stable operation under portfolio-scale usage with clear security boundaries.
  • A practical target is supporting up to ~1000 concurrent users with current architecture assumptions.