Skip to main content

Ristek Link Backend - Documentation

Document Status
Document OwnerProduct Engineering 2026Product Engineering 2026
Contributors
Project Linkshttps://github.com/RistekCSUI/ristek-link-backendhttps://github.com/RistekCSUI/ristek-link-backend
Project Linkshttps://ristek.linkhttps://ristek.link
Project LinksSentry (configured via SENTRY_DSN)Sentry (configured via SENTRY_DSN)
Project LinksSwagger UI at /docs (non-production environments only)Swagger UI at /docs (non-production environments only)
TeamTheodore Kevin Himawantheodore.kevin@ristek.cs.ui.ac.id
TeamAri Darrell Muljonodarrell@ristek.cs.ui.ac.id
TeamGrace Karinagracekarin@ristek.cs.ui.ac.id
TeamYeshua Marco G. Manurungmarco@ristek.cs.ui.ac.id

πŸ”Ž Background

Overview​

Ristek.Link is a URL shortening service that allows users to create custom short URLs, track click analytics, generate QR codes, and manage custom subdomains. It provides both anonymous and authenticated shortening, bulk URL creation via Excel upload, per-link analytics (visitors, unique visitors, retention over configurable time ranges), and a superuser admin system for custom domain management.

Stakeholders​

  • Users: RISTEK members, general public (anonymous shortening), superusers (domain management)
  • Upstream Dependencies: Google OAuth API, Google Web Risk API (currently disabled), SendinBlue (Brevo) for transactional emails, AWS S3 (bulk Excel result storage)
  • Downstream Consumers: Ristek.Link Frontend, any service consuming short URLs via redirect

Scope & Boundaries​

  • In Scope: URL shortening (single & bulk), custom short URL aliases, click tracking & analytics, QR code generation with custom colors/logo, user authentication (email/password + Google OAuth), password reset flow, custom subdomain management (superuser), phishing/unsafe URL detection, device-based unique click tracking.
  • Out of Scope: Frontend application, DNS configuration for custom subdomains, Google Web Risk billing (feature disabled), advanced analytics dashboards.

βš™οΈ Architecture & Design

System Diagram​

Architecture Decisions (ADRs)​

  • Language/Framework: TypeScript / Node.js / Express 4.18.1
  • Database: Google Cloud Firestore (NoSQL document database) β€” separate projects for prod (ristek-link) and dev (ristek-link-dev)
  • Authentication: JWT (Bearer token) with bcrypt password hashing; Google OAuth as alternative; separate superuser JWT for admin operations
  • Communication: Synchronous RESTful API over HTTP
  • File Storage: AWS S3 for bulk Excel result uploads
  • Email: SendinBlue (Brevo) for password reset emails
  • Error Monitoring: Sentry with tracing
  • QR Generation: @cheprasov/qrcode (SVG-based with customizable colors and logo)
  • URL Safety: Local unsafe host blacklist (Firestore unsafe_host collection) + hardcoded email blacklist; Google Web Risk integration exists but is disabled due to GCP billing
  • API Documentation: Swagger/OpenAPI via swagger-jsdoc + swagger-ui-express (non-production only)

πŸ’»Technical Specifications

API Documentation​

All routes are prefixed under /api/v1. Authentication uses Authorization: Bearer <token> headers. Superuser routes additionally require authorization-superuser: <token> header. Swagger UI is available at /docs in non-production environments.

Auth Endpoints (/api/v1/auth)​

MethodPathAuthDescription
POST/registerβ€”Register with fullName, email, password
POST/loginβ€”Login, returns JWT (24h expiry) + optional superUserToken
POST/googleβ€”Google OAuth login (auto-registers if new)
POST/forgot-passwordβ€”Sends password reset email via SendinBlue
POST/reset-passwordβ€”Reset password using token from email

Shorten Endpoints (/api/v1/shorten)​

MethodPathAuthDescription
POST/β€”Create short URL (optional auth for ownership)
POST/bulkβ€”Bulk create from Excel file URL (max 100 links per attempt)
GET/BearerList all URLs owned by authenticated user
GET/bulkBearerList bulk Excel import results for user
PUT/BearerEdit existing short URL (url and/or alias)
POST/redirectβ€”Resolve short URL β†’ target URL (tracks clicks + unique visitors)
GET/:shortenβ€”Get short URL details (public)
PATCH/generate-qrβ€”Generate custom QR code with color and logo options

Analytics Endpoints (/api/v1/analytics)​

MethodPathAuthQuery ParamsDescription
GET/Bearershorten, category, page, rangeGet analytics for a short URL
Query parameter enums:
category: visitorunique_visitorretention
range: week (7 daily data points)month (4 weekly data points)six_month (6 monthly data points)
Returns: percentage change vs previous period, current/previous totals, avg, max, min, paginated data with labels.

Domain Endpoints (/api/v1/domain) β€” Superuser Only​

MethodPathAuthDescription
POST/Bearer + SuperuserCreate custom subdomain (one per user, alpha-only, max 11 chars)
POST/findBearerFind domain by authenticated user's email
PATCH/:idBearer + SuperuserUpdate domain (subdomain name, user email)
DELETE/:idBearer + SuperuserDelete domain
PATCH/:id/active-statusBearer + SuperuserToggle domain active/inactive
GET/allBearer + SuperuserList all domains (paginated, filterable by active status and search)
GET/testBearer + SuperuserSuperuser auth verification test

Data Model/Schema​

Firestore collections (schema-less NoSQL documents):

CollectionDocument IDKey FieldsPurpose
userUser emailfullName, password (bcrypt hash), google (boolean), malicious (boolean)User accounts
super_userUser email(membership check only)Superuser role mapping
shortenUUIDemail, url, shortenUrl, click, uniqueClick, createdDate, qrCode (SVG), withLogo, phising, subdomain?Shortened URLs
analyticsUUIDshortenUrl, YYYY-MM-DD: { totalClicks, totalUniqueClick }Per-day click analytics
devicesdevice_iddeviceId, createdAt, url-{shorten}: booleanDevice tracking for unique clicks
bulk_excelUUIDemail, fail, success, createdAt, excelUrlBulk import results
unsafe_hostHostnamehostName, threatTypes[], createdAtBlocked unsafe hostnames
domainUUIDid, subdomain, userEmail, isActive, createdAt, updatedAt, lastUpdatedByCustom subdomains

Project Structure​

ristek.link-backend/
β”œβ”€β”€ .github/
β”‚ └── workflows/
β”‚ β”œβ”€β”€ deploy.yml # Production: push to master β†’ Vercel prod deploy
β”‚ β”œβ”€β”€ preview.yml # Staging: push/PR to dev β†’ Vercel preview deploy
β”‚ └── deploy-pusilkom.yml # Manual: ECR build β†’ Pusilkom (staging/prod/all)
β”œβ”€β”€ src/
β”‚ β”œβ”€β”€ index.ts # Entry point: Firestore init, health check, server start
β”‚ β”œβ”€β”€ config.ts # Express app setup: Sentry, CORS, helmet, morgan, session, routes
β”‚ β”œβ”€β”€ controllers/
β”‚ β”‚ β”œβ”€β”€ authController.ts # Register, login, Google OAuth, forgot/reset password
β”‚ β”‚ β”œβ”€β”€ shortenController.ts # URL shorten, bulk import, redirect, edit, QR generation
β”‚ β”‚ β”œβ”€β”€ analyticsController.ts # Click analytics with week/month/6-month ranges
β”‚ β”‚ └── domainController.ts # Custom subdomain CRUD (superuser)
β”‚ β”œβ”€β”€ routers/
β”‚ β”‚ β”œβ”€β”€ authRouter.ts # Auth route definitions + validation rules
β”‚ β”‚ β”œβ”€β”€ shortenRouter.ts # Shorten route definitions + validation rules
β”‚ β”‚ β”œβ”€β”€ analyticsRouter.ts # Analytics route definitions
β”‚ β”‚ └── domainRouter.ts # Domain route definitions
β”‚ β”œβ”€β”€ middlewares/
β”‚ β”‚ β”œβ”€β”€ authorization.ts # JWT Bearer token verification + Firestore user lookup
β”‚ β”‚ └── superuserAuthorization.ts # Superuser JWT verification via authorization-superuser header
β”‚ β”œβ”€β”€ database/
β”‚ β”‚ β”œβ”€β”€ firestore.ts # Firebase Admin SDK initialization (prod/dev creds)
β”‚ β”‚ └── blacklistedEmail.ts # Hardcoded email blacklist array
β”‚ β”œβ”€β”€ types/
β”‚ β”‚ β”œβ”€β”€ auth.ts # Auth DTOs and response interfaces
β”‚ β”‚ β”œβ”€β”€ shorten.ts # Shorten DTOs, response interfaces, controller interface
β”‚ β”‚ β”œβ”€β”€ analytics.ts # Analytics DTOs and response interfaces
β”‚ β”‚ └── domain.ts # Domain DTOs, pagination types, controller interface
β”‚ β”œβ”€β”€ error/
β”‚ β”‚ β”œβ”€β”€ error.ts # ApiError class (statusCode, message, status)
β”‚ β”‚ └── serviceError.ts # Predefined error instances (30+ error types)
β”‚ β”œβ”€β”€ utils/
β”‚ β”‚ β”œβ”€β”€ validator.ts # express-validator validation schemas for all endpoints
β”‚ β”‚ β”œβ”€β”€ mailer.ts # SendinBlue transactional email for password reset
β”‚ β”‚ β”œβ”€β”€ s3.ts # AWS S3 upload for bulk Excel results
β”‚ β”‚ β”œβ”€β”€ webrisk.ts # Google Web Risk API URL threat check (disabled)
β”‚ β”‚ └── wrapper.ts # Response wrapper utility
β”‚ └── log/
β”‚ └── log.ts # Winston logger configuration (console + file)
β”œβ”€β”€ firebase.json # Firebase service account credentials (production)
β”œβ”€β”€ firebase.dev.json # Firebase service account credentials (development)
β”œβ”€β”€ swagger.json # OpenAPI 3.0 config (JWT security, source paths)
β”œβ”€β”€ vercel.json # Vercel serverless build config (all routes β†’ index.ts)
β”œβ”€β”€ tsconfig.json # TypeScript config (ES6, CommonJS, strict)
β”œβ”€β”€ .env.local # Local environment variables
β”œβ”€β”€ .vercelignore # Vercel deploy ignore patterns
β”œβ”€β”€ .gitignore # Git ignore patterns
└── package.json # Dependencies, scripts, metadata

Components​

LayerPathsResponsibility
Auth Controllersrc/controllers/authController.tsHandles user registration with bcrypt password hashing, email/password login returning a 24-hour JWT (plus a separate superUserToken if the user exists in the super_user collection), Google OAuth login (auto-creates users on first login), and a password reset flow using SendinBlue transactional emails with JWT-based reset tokens.
Shorten Controllersrc/controllers/shortenController.tsCore URL shortening engine. Creates short URLs with duplicate checking, optional custom subdomain association, and automatic SVG QR code generation with the RISTEK logo. Enforces HTTPS-only URLs, checks against the unsafe_host blacklist and hardcoded email blacklist, and flags phishing URLs. Supports bulk creation from Excel files (max 100 links per attempt) with results uploaded to S3. The redirect flow tracks total clicks and device-based unique clicks, updating both the shorten document and the analytics collection with daily breakdowns.
Analytics Controllersrc/controllers/analyticsController.tsProvides paginated analytics for individual short URLs across three time ranges: weekly (7 daily data points), monthly (4 weekly aggregates), and six-month (6 monthly aggregates). Calculates percentage change vs the previous period, averages, min/max, and total page count for pagination.
Domain Controllersrc/controllers/domainController.tsSuperuser-only custom subdomain management. Supports creating subdomains (one per user, alphabetic characters only, max 11 chars), updating subdomain names and owner emails with duplicate validation, toggling active/inactive status, paginated listing with search and active-status filtering, and deletion.
Authorization Middlewaresrc/middlewares/authorization.tsVerifies Authorization: Bearer <token> header, decodes the JWT, validates the user exists in Firestore, and injects the decoded payload into req.body.data.
Superuser Middlewaresrc/middlewares/superuserAuthorization.tsVerifies a separate authorization-superuser header token, checks role === "super_user" in the JWT payload, and validates the user in Firestore. Used alongside authorization for domain management endpoints.
Firestore Databasesrc/database/firestore.tsInitializes the Firebase Admin SDK with service account credentials, selecting between production (ristek-link) and development (ristek-link-dev) Firebase projects based on NODE_ENV.

☁️ Operational Playbook

Infrastructure​

  • Primary Platform: Vercel (serverless deployment via @vercel/node)
  • Secondary Platform: AWS ECR + Pusilkom instance (Docker container deployment)
  • Production Deploy: Push to master β†’ GitHub Actions β†’ Vercel production (deploy.yml)
  • Staging Deploy: Push/PR to dev β†’ GitHub Actions β†’ Vercel preview (preview.yml)
  • Pusilkom Deploy: Manual workflow dispatch β†’ ECR build (deploy-pusilkom.yml) for staging (latest tag) and/or production (stable tag)
  • ECR Registry: 638207107223.dkr.ecr.ap-southeast-1.amazonaws.com/ristek-link-backend
  • Database: Google Cloud Firestore β€” ristek-link (prod), ristek-link-dev (dev)

Environment Variables​

KeyDescriptionDefault (Dev)Sensitive?
PORTServer listening port4000No
NODE_ENVEnvironment (development / production) β€” controls Firebase creds and Swagger visibilitydevelopmentNo
JWT_SECRETJWT signing secret for user and superuser tokensβ€”Yes
GOOGLE_CLIENT_IDGoogle OAuth Client ID (note: env var has typo GOOGLE_CLIENT_I in code)β€”Yes
GOOGLE_CLIENT_SECRETGoogle OAuth Client Secretβ€”Yes
SENDINBLUE_API_KEYSendinBlue (Brevo) API key for transactional emailsβ€”Yes
CLIENT_HOSTBase URL for password reset links (e.g. http://localhost:3000/login/reset?token=)http://localhost:3000/login/reset?token=No
SENTRY_DSNSentry DSN for error trackingβ€”Yes
AWS_ACCESS_KEY_IDAWS access key for S3 bulk Excel uploadsβ€”Yes
AWS_SECRET_KEY_VALUEAWS secret key for S3β€”Yes
AWS_REGIONAWS region for S3β€”No
AWS_BUCKET_NAMES3 bucket name for bulk Excel resultsβ€”

πŸ™‹Questions

πŸ—’ List of frequently asked questions or question that need to be answered that is related to this initiative ..