{"id":30671526,"url":"https://github.com/tanim-mishkat/url-shortener","last_synced_at":"2025-09-01T03:18:48.096Z","repository":{"id":308745254,"uuid":"1033875530","full_name":"tanim-mishkat/URL-Shortener","owner":"tanim-mishkat","description":"Full-featured URL shortener built with React and Node.js/Express. Create custom or auto-generated short links, organize with folders and tags, track clicks with location and device info, and manage accounts with JWT authentication.","archived":false,"fork":false,"pushed_at":"2025-08-29T08:39:19.000Z","size":441,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-08-29T12:24:14.897Z","etag":null,"topics":["express","jwt","mongodb","nodejs","react","rest-api","tailwindcss","url-shortener"],"latest_commit_sha":null,"homepage":"https://url-shortener-frontend-q3gy.onrender.com/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tanim-mishkat.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-08-07T13:35:29.000Z","updated_at":"2025-08-29T08:39:22.000Z","dependencies_parsed_at":"2025-08-29T10:37:19.517Z","dependency_job_id":null,"html_url":"https://github.com/tanim-mishkat/URL-Shortener","commit_stats":null,"previous_names":["tanim-mishkat/url-shortener"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/tanim-mishkat/URL-Shortener","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tanim-mishkat%2FURL-Shortener","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tanim-mishkat%2FURL-Shortener/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tanim-mishkat%2FURL-Shortener/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tanim-mishkat%2FURL-Shortener/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tanim-mishkat","download_url":"https://codeload.github.com/tanim-mishkat/URL-Shortener/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tanim-mishkat%2FURL-Shortener/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273068856,"owners_count":25039911,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-01T02:00:09.058Z","response_time":120,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["express","jwt","mongodb","nodejs","react","rest-api","tailwindcss","url-shortener"],"created_at":"2025-09-01T03:18:46.177Z","updated_at":"2025-09-01T03:18:48.089Z","avatar_url":"https://github.com/tanim-mishkat.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# URL Shortener - Complete Documentation\n\n## Table of Contents\n\n1. [Overview](#overview)\n2. [Architecture](#architecture)\n3. [Technology Stack](#technology-stack)\n4. [Project Structure](#project-structure)\n5. [Environment Configuration](#environment-configuration)\n6. [Data Models](#data-models)\n7. [API Reference](#api-reference)\n   - [Authentication Endpoints](#authentication-endpoints)\n   - [URL Management](#url-management)\n   - [Folder Management](#folder-management)\n   - [Analytics](#analytics)\n   - [User Utilities](#user-utilities)\n8. [Authentication \u0026 Security](#authentication--security)\n9. [Error Handling](#error-handling)\n10. [Deployment](#deployment)\n11. [Development Setup](#development-setup)\n12. [License](#license)\n\n---\n\n## Overview\n\nA production-ready URL shortener application featuring user accounts, folder organization, tag management, server-side analytics aggregation, and a responsive React frontend. The system provides comprehensive link management with detailed analytics and organizational tools.\n\n**Key Features:**\n\n- User registration and authentication\n- Custom and auto-generated short URLs\n- Folder-based organization\n- Tag system for categorization\n- Comprehensive analytics with geographical and device tracking\n- Bulk operations for link management\n- Responsive web interface\n- RESTful API architecture\n\n**Deployment:**\n\n- Backend: Node.js/Express API deployed on Render\n- Frontend: React SPA deployed separately\n- Database: MongoDB Atlas cluster\n\n---\n\n## Architecture\n\nThe application follows a modern full-stack architecture with clear separation of concerns:\n\n- **Frontend**: Single Page Application (React) communicating via RESTful API\n- **Backend**: Node.js/Express server with MongoDB database\n- **Analytics**: Server-side click tracking with daily aggregation\n- **Authentication**: JWT-based with httpOnly cookies or Bearer tokens\n- **Deployment**: Separate deployments with CORS-enabled communication\n\n---\n\n## Technology Stack\n\n### Backend Technologies\n\n- **Runtime**: Node.js\n- **Framework**: Express.js\n- **Database**: MongoDB with Mongoose ODM\n- **Authentication**: JSON Web Tokens (JWT)\n- **Analytics**: geoip-lite for geographical data, ua-parser-js for device detection\n- **Security**: CORS, httpOnly cookies, proxy trust configuration\n\n### Frontend Technologies\n\n- **Framework**: React 18\n- **Routing**: TanStack Router\n- **State Management**: TanStack Query for server state\n- **Styling**: Tailwind CSS\n- **Build Tool**: Vite\n\n### Deployment Infrastructure\n\n- **Hosting**: Render (both frontend and backend)\n- **Database**: MongoDB Atlas\n- **CDN**: Static asset delivery via Render\n- **SSL**: Automatic HTTPS termination\n\n---\n\n## Project Structure\n\n### Monorepo Organization\n\n```\nroot/\n├── backend/\n│   ├── app.js                    # Express application entry point\n│   └── src/\n│       ├── config/              # Configuration files\n│       ├── controller/          # Route handlers\n│       ├── dao/                # Data access objects\n│       ├── errors/             # Custom error classes\n│       ├── middlewares/        # Express middlewares\n│       ├── models/             # Mongoose schemas\n│       ├── routes/             # Route definitions\n│       ├── services/           # Business logic\n│       └── utils/              # Utility functions\n└── frontend/\n    └── src/\n        ├── api/                # API client functions\n        ├── components/         # React components\n        ├── hooks/              # Custom React hooks\n        ├── context/            # React context providers\n        └── utils/              # Frontend utilities\n```\n\n### Backend Structure Detail\n\n```\nbackend/src/\n├── config/\n│   ├── mongo.config.js         # MongoDB connection configuration\n│   └── config.js               # Application configuration\n├── controller/\n│   ├── analytics.controller.js  # Analytics endpoint handlers\n│   ├── auth.controller.js       # Authentication handlers\n│   ├── folder.controller.js     # Folder management handlers\n│   ├── shortUrl.controller.js   # URL management handlers\n│   └── user.controller.js       # User-related handlers\n├── dao/\n│   ├── analytics.dao.js         # Analytics data access\n│   ├── folder.dao.js            # Folder data access\n│   ├── shortUrl.dao.js          # URL data access\n│   └── user.dao.js              # User data access\n├── errors/\n│   └── AppError.js              # Custom application error class\n├── middlewares/\n│   ├── assetsHandler.middleware.js  # Static asset handling\n│   ├── auth.middleware.js           # Authentication middleware\n│   ├── error.middleware.js          # Error handling middleware\n│   └── notFound.middleware.js       # 404 handler\n├── models/\n│   ├── clickAgg.model.js        # Click aggregation schema\n│   ├── folder.model.js          # Folder schema\n│   ├── shortUrl.model.js        # Short URL schema\n│   └── user.model.js            # User schema\n├── routes/\n│   ├── analytics.route.js       # Analytics routes\n│   ├── auth.route.js            # Authentication routes\n│   ├── folder.route.js          # Folder routes\n│   └── shortUrl.route.js        # URL routes\n└── utils/\n    ├── analyticsRange.js        # Analytics date range utilities\n    ├── attachUser.js            # User attachment utilities\n    ├── helper.utils.js          # General helper functions\n    └── wrapAsync.js             # Async error wrapper\n```\n\n### Frontend Structure Detail\n\n```\nfrontend/src/\n├── api/\n│   └── shortUrl.api.js          # API client methods\n├── components/\n│   ├── UrlForm.jsx              # URL creation form\n│   ├── UserUrls.jsx             # URL listing component\n│   ├── FolderSidebar.jsx        # Folder navigation\n│   ├── TagEditor.jsx            # Tag management interface\n│   ├── modals/\n│   │   └── ConfirmModals.jsx    # Confirmation dialogs\n│   └── user-urls/\n│       ├── BulkToolbar.jsx      # Desktop bulk operations\n│       ├── MobileBulkToolbar.jsx # Mobile bulk operations\n│       ├── UrlRow.jsx           # URL list item (desktop)\n│       ├── UrlCard.jsx          # URL card (mobile)\n│       ├── FolderSelect.jsx     # Folder selection dropdown\n│       ├── TagBulkUrl.jsx       # Bulk tag operations\n│       ├── StatusPill.jsx       # Status indicator\n│       └── NoUrlsCard.jsx       # Empty state component\n├── hooks/                       # Custom React hooks\n├── context/\n│   └── ToastContext.jsx         # Toast notification system\n└── utils/\n    └── publicBase.js            # Public API utilities\n```\n\n---\n\n## Environment Configuration\n\n### Backend Environment Variables\n\nCreate a `.env` file in the backend directory:\n\n```bash\n# Database Configuration\nMONGO_URL=mongodb+srv://username:password@cluster.mongodb.net/database\n\n# Server Configuration\nPORT=5000                        # Render sets this automatically in production\nNODE_ENV=production              # development | production\n\n# Application Configuration\nAPP_URL=https://url-shortener-backend-xxx.onrender.com\n\n# Authentication\nJWT_SECRET=your-super-secure-jwt-secret-key\n\n# CORS Configuration (comma-separated URLs)\nCORS_ORIGINS=https://url-shortener-frontend-xxx.onrender.com,https://yourdomain.com\n```\n\n### Configuration Details\n\n#### CORS Configuration\n\n- **Purpose**: Whitelist allowed origins for cross-origin requests\n- **Format**: Comma-separated list of complete URLs\n- **Security**: Requests from unlisted origins are automatically rejected\n- **Credentials**: Enabled for cookie-based authentication\n\n#### Cookie Configuration\n\n- **Production**: `sameSite: 'none'`, `secure: true`, `httpOnly: true`\n- **Development**: `sameSite: 'strict'`, `secure: false`, `httpOnly: true`\n- **Name**: `accessToken`\n- **Expiration**: 1 hour (configurable)\n\n#### Proxy Configuration\n\n- **Setting**: `trust proxy: 1`\n- **Purpose**: Proper IP address and protocol detection behind reverse proxy\n- **Platforms**: Render, Heroku, and similar PaaS providers\n\n---\n\n## Data Models\n\n### User Model\n\n```javascript\n{\n  _id: ObjectId,\n  name: String,                    // Display name\n  email: String,                   // Unique identifier\n  password: String,                // Bcrypt hashed\n  avatar: String,                  // Gravatar URL\n  createdAt: Date,\n  updatedAt: Date\n}\n```\n\n**Indexes:**\n\n- `email`: unique index for authentication\n\n### Short URL Model\n\n```javascript\n{\n  _id: ObjectId,\n  fullUrl: String,                 // Original URL\n  shortUrl: String,                // Short identifier/slug\n  clicks: Number,                  // Total click count\n  user: ObjectId,                  // Reference to User (optional)\n  status: String,                  // 'active' | 'paused' | 'disabled'\n  tags: [String],                  // User-defined tags\n  folderId: ObjectId,              // Reference to Folder (optional)\n  deletedAt: Date,                 // Soft delete timestamp\n  createdAt: Date,\n  updatedAt: Date\n}\n```\n\n**Indexes:**\n\n- `shortUrl`: unique index for fast lookup\n- `user`: compound index with other fields for user queries\n- `status`: filtering by status\n- `createdAt`: date-based sorting\n\n### Folder Model\n\n```javascript\n{\n  _id: ObjectId,\n  name: String,                    // Folder name\n  user: ObjectId,                  // Reference to User\n  createdAt: Date,\n  updatedAt: Date\n}\n```\n\n**Constraints:**\n\n- Unique folder names per user\n\n### Click Aggregation Model\n\n```javascript\n{\n  _id: ObjectId,\n  linkId: ObjectId,                // Reference to ShortUrl\n  day: Date,                       // Date (start of day UTC)\n  total: Number,                   // Total clicks for the day\n  country: Map,                    // Country code -\u003e count\n  referrer: Map,                   // Referrer domain -\u003e count\n  device: {\n    desktop: Number,\n    mobile: Number,\n    tablet: Number,\n    bot: Number,\n    other: Number\n  },\n  createdAt: Date,\n  updatedAt: Date\n}\n```\n\n**Indexes:**\n\n- `linkId, day`: compound unique index for efficient aggregation queries\n\n---\n\n## API Reference\n\n**Base URL (Production):** `https://url-shortener-backend-xxx.onrender.com`\n\n**Content Type:** All requests and responses use `application/json`\n\n**Authentication:** JWT tokens via httpOnly cookies or `Authorization: Bearer` headers\n\n### URL Redirection\n\n#### GET `/:id`\n\nResolve a short URL slug and redirect to the original URL.\n\n**Parameters:**\n\n- `id` (path): Short URL identifier\n\n**Response:**\n\n- `302 Found`: Successful redirect\n- `403 Forbidden`: Link is paused\n- `404 Not Found`: Link not found or disabled\n\n**Example:**\n\n```http\nGET /abc123\nLocation: https://example.com/original-url\n```\n\n**Analytics:** Each successful redirect records analytics data asynchronously.\n\n---\n\n### Authentication Endpoints\n\n#### POST `/api/auth/register`\n\nRegister a new user account.\n\n**Request Body:**\n\n```json\n{\n  \"name\": \"John Doe\",\n  \"email\": \"john.doe@example.com\",\n  \"password\": \"SecurePassword123!\"\n}\n```\n\n**Response (201 Created):**\n\n```json\n{\n  \"message\": \"User registered successfully\"\n}\n```\n\n**Side Effects:**\n\n- Sets `accessToken` httpOnly cookie\n- Generates Gravatar avatar URL\n\n**Validation:**\n\n- Email must be unique\n- Password minimum 8 characters\n- All fields required\n\n---\n\n#### POST `/api/auth/login`\n\nAuthenticate existing user.\n\n**Request Body:**\n\n```json\n{\n  \"email\": \"john.doe@example.com\",\n  \"password\": \"SecurePassword123!\"\n}\n```\n\n**Response (200 OK):**\n\n```json\n{\n  \"user\": {\n    \"_id\": \"66f1234567890abcdef12345\",\n    \"name\": \"John Doe\",\n    \"email\": \"john.doe@example.com\",\n    \"avatar\": \"https://www.gravatar.com/avatar/...?d=identicon\",\n    \"createdAt\": \"2025-01-01T00:00:00.000Z\",\n    \"updatedAt\": \"2025-01-01T00:00:00.000Z\"\n  },\n  \"message\": \"User logged in successfully\"\n}\n```\n\n**Side Effects:**\n\n- Sets `accessToken` httpOnly cookie\n\n---\n\n#### POST `/api/auth/logout`\n\nEnd user session.\n\n**Authentication:** Required\n\n**Response (200 OK):**\n\n```json\n{\n  \"message\": \"Logged out successfully\"\n}\n```\n\n**Side Effects:**\n\n- Clears `accessToken` cookie\n\n---\n\n#### GET `/api/auth/me`\n\nGet current user information.\n\n**Authentication:** Required\n\n**Response (200 OK):**\n\n```json\n{\n  \"user\": {\n    \"_id\": \"66f1234567890abcdef12345\",\n    \"name\": \"John Doe\",\n    \"email\": \"john.doe@example.com\",\n    \"avatar\": \"https://www.gravatar.com/avatar/...?d=identicon\"\n  }\n}\n```\n\n---\n\n### URL Management\n\n#### POST `/api/create`\n\nCreate a new short URL.\n\n**Authentication:** Optional (anonymous links supported)\n\n**Request Body:**\n\n```json\n{\n  \"url\": \"https://example.com/very/long/path?with=parameters\",\n  \"slug\": \"custom-slug\" // Optional, authenticated users only\n}\n```\n\n**Response (201 Created):**\n\n```json\n{\n  \"shortUrl\": \"https://url-shortener-backend-xxx.onrender.com/custom-slug\"\n}\n```\n\n**Validation:**\n\n- URL must be valid HTTP/HTTPS\n- Custom slugs only for authenticated users\n- Slug must be unique (alphanumeric, hyphens, underscores)\n\n**Errors:**\n\n- `400 Bad Request`: Invalid URL format\n- `409 Conflict`: Custom slug already taken\n\n---\n\n#### GET `/api/links`\n\nList user's short URLs with pagination.\n\n**Authentication:** Required\n\n**Query Parameters:**\n\n- `limit` (1-100, default: 50): Items per page\n- `cursor` (string): Pagination cursor from previous response\n- `folderId` (ObjectId|null): Filter by folder\n- `status` (active|paused|disabled): Filter by status\n- `q` (string): Search in full URLs (case-insensitive)\n- `tags` (comma-separated): Filter by tags (all must match)\n- `from` (ISO date): Filter by creation date (start)\n- `to` (ISO date): Filter by creation date (end)\n\n**Response (200 OK):**\n\n```json\n{\n  \"items\": [\n    {\n      \"_id\": \"66f1234567890abcdef12345\",\n      \"fullUrl\": \"https://example.com/long-url\",\n      \"shortUrl\": \"abc123\",\n      \"clicks\": 42,\n      \"status\": \"active\",\n      \"tags\": [\"marketing\", \"q3\", \"campaign-a\"],\n      \"folderId\": \"66e9876543210fedcba09876\",\n      \"user\": \"66d1111111111111111111111\",\n      \"createdAt\": \"2025-01-02T09:12:33.123Z\",\n      \"updatedAt\": \"2025-01-02T09:12:33.123Z\"\n    }\n  ],\n  \"nextCursor\": \"1735776133123_66f1234567890abcdef12345\"\n}\n```\n\n**Pagination:**\n\n- Use `nextCursor` from response for subsequent requests\n- `null` cursor indicates end of results\n\n---\n\n#### POST `/api/links/batch`\n\nPerform bulk operations on multiple links.\n\n**Authentication:** Required\n\n**Request Body:**\n\n```json\n{\n  \"op\": \"pause\",\n  \"ids\": [\"66f1234567890abcdef12345\", \"66f1234567890abcdef12346\"],\n  \"payload\": {}\n}\n```\n\n**Supported Operations:**\n\n| Operation      | Description                | Payload                            |\n| -------------- | -------------------------- | ---------------------------------- |\n| `pause`        | Set status to paused       | None                               |\n| `resume`       | Set status to active       | None                               |\n| `disable`      | Soft delete (set disabled) | None                               |\n| `moveToFolder` | Change folder              | `{\"folderId\": \"ObjectId or null\"}` |\n| `addTags`      | Add tags to existing       | `{\"tags\": [\"tag1\", \"tag2\"]}`       |\n| `removeTags`   | Remove specific tags       | `{\"tags\": [\"tag1\", \"tag2\"]}`       |\n| `hardDelete`   | Permanently delete         | None                               |\n\n**Response (200 OK):**\n\n```json\n{\n  \"message\": \"Batch processed\",\n  \"changed\": 2\n}\n```\n\n**Hard Delete Response:**\n\n```json\n{\n  \"ok\": true,\n  \"op\": \"hardDelete\",\n  \"attempted\": 2,\n  \"deletedCount\": 2\n}\n```\n\n---\n\n#### PATCH `/api/links/:id/status`\n\nUpdate the status of a single link.\n\n**Authentication:** Required\n\n**Parameters:**\n\n- `id` (path): Link ObjectId\n\n**Request Body:**\n\n```json\n{\n  \"status\": \"paused\" // active | paused | disabled\n}\n```\n\n**Response (200 OK):**\n\n```json\n{\n  \"message\": \"Status updated\",\n  \"link\": {\n    \"_id\": \"66f1234567890abcdef12345\",\n    \"status\": \"paused\",\n    \"updatedAt\": \"2025-01-02T10:30:00.000Z\"\n  }\n}\n```\n\n---\n\n#### DELETE `/api/links/:id`\n\nSoft delete a link (sets status to disabled).\n\n**Authentication:** Required\n\n**Parameters:**\n\n- `id` (path): Link ObjectId\n\n**Response (200 OK):**\n\n```json\n{\n  \"message\": \"Link disabled (soft deleted)\",\n  \"link\": {\n    \"_id\": \"66f1234567890abcdef12345\",\n    \"status\": \"disabled\",\n    \"deletedAt\": \"2025-01-02T10:30:00.000Z\"\n  }\n}\n```\n\n---\n\n#### DELETE `/api/links/:id/permanent`\n\nPermanently delete a link (irreversible).\n\n**Authentication:** Required\n\n**Parameters:**\n\n- `id` (path): Link ObjectId\n\n**Response (200 OK):**\n\n```json\n{\n  \"message\": \"Link permanently deleted\"\n}\n```\n\n**Warning:** This operation cannot be undone and will also delete all associated analytics data.\n\n---\n\n#### PATCH `/api/links/:id/tags`\n\nReplace the tag list for a link.\n\n**Authentication:** Required\n\n**Parameters:**\n\n- `id` (path): Link ObjectId\n\n**Request Body:**\n\n```json\n{\n  \"tags\": [\"marketing\", \"q3\", \"campaign-a\"]\n}\n```\n\n**Response (200 OK):**\n\n```json\n{\n  \"message\": \"Tags updated\",\n  \"link\": {\n    \"_id\": \"66f1234567890abcdef12345\",\n    \"tags\": [\"marketing\", \"q3\", \"campaign-a\"],\n    \"updatedAt\": \"2025-01-02T10:30:00.000Z\"\n  }\n}\n```\n\n**Processing:**\n\n- Tags are normalized (trimmed, lowercased)\n- Duplicates are automatically removed\n- Empty tags are filtered out\n\n---\n\n#### PATCH `/api/links/:id/folder`\n\nMove a link to a different folder.\n\n**Authentication:** Required\n\n**Parameters:**\n\n- `id` (path): Link ObjectId\n\n**Request Body:**\n\n```json\n{\n  \"folderId\": \"66e9876543210fedcba09876\" // ObjectId or null for unfiled\n}\n```\n\n**Response (200 OK):**\n\n```json\n{\n  \"message\": \"Folder updated\",\n  \"link\": {\n    \"_id\": \"66f1234567890abcdef12345\",\n    \"folderId\": \"66e9876543210fedcba09876\",\n    \"updatedAt\": \"2025-01-02T10:30:00.000Z\"\n  }\n}\n```\n\n**Errors:**\n\n- `404 Not Found`: Folder doesn't exist or not owned by user\n- `404 Not Found`: Link doesn't exist or not owned by user\n\n---\n\n#### PATCH `/api/links/:id/restore`\n\nRestore a soft-deleted link.\n\n**Authentication:** Required\n\n**Parameters:**\n\n- `id` (path): Link ObjectId\n\n**Response (200 OK):**\n\n```json\n{\n  \"message\": \"Link restored\",\n  \"link\": {\n    \"_id\": \"66f1234567890abcdef12345\",\n    \"status\": \"active\",\n    \"deletedAt\": null,\n    \"updatedAt\": \"2025-01-02T10:30:00.000Z\"\n  }\n}\n```\n\n---\n\n### Folder Management\n\n#### GET `/api/folders`\n\nList user's folders.\n\n**Authentication:** Required\n\n**Query Parameters:**\n\n- `withCounts` (1|0): Include link counts per folder\n\n**Response (200 OK):**\n\n```json\n{\n  \"folders\": [\n    {\n      \"_id\": \"66e9876543210fedcba09876\",\n      \"name\": \"Marketing Campaigns\",\n      \"user\": \"66d1111111111111111111111\",\n      \"count\": 12 // Only if withCounts=1\n    }\n  ]\n}\n```\n\n---\n\n#### POST `/api/folders`\n\nCreate a new folder.\n\n**Authentication:** Required\n\n**Request Body:**\n\n```json\n{\n  \"name\": \"Q4 Campaigns\"\n}\n```\n\n**Response (201 Created):**\n\n```json\n{\n  \"folder\": {\n    \"_id\": \"66e9876543210fedcba09876\",\n    \"name\": \"Q4 Campaigns\",\n    \"user\": \"66d1111111111111111111111\",\n    \"createdAt\": \"2025-01-02T10:30:00.000Z\",\n    \"updatedAt\": \"2025-01-02T10:30:00.000Z\"\n  }\n}\n```\n\n**Constraints:**\n\n- Folder names must be unique per user\n- Maximum length: 50 characters\n\n---\n\n#### PATCH `/api/folders/:id`\n\nRename a folder.\n\n**Authentication:** Required\n\n**Parameters:**\n\n- `id` (path): Folder ObjectId\n\n**Request Body:**\n\n```json\n{\n  \"name\": \"Q4 Marketing Campaigns\"\n}\n```\n\n**Response (200 OK):**\n\n```json\n{\n  \"folder\": {\n    \"_id\": \"66e9876543210fedcba09876\",\n    \"name\": \"Q4 Marketing Campaigns\",\n    \"user\": \"66d1111111111111111111111\",\n    \"updatedAt\": \"2025-01-02T10:30:00.000Z\"\n  }\n}\n```\n\n---\n\n#### DELETE `/api/folders/:id`\n\nDelete a folder and move contained links to unfiled.\n\n**Authentication:** Required\n\n**Parameters:**\n\n- `id` (path): Folder ObjectId\n\n**Response (200 OK):**\n\n```json\n{\n  \"message\": \"Folder removed and links unfiled\"\n}\n```\n\n**Side Effects:**\n\n- All links in the folder are moved to unfiled status\n- Folder deletion is permanent\n\n---\n\n### Analytics\n\n#### GET `/api/analytics/:linkId/timeseries`\n\nGet daily click counts for a link over a date range.\n\n**Authentication:** Required\n\n**Parameters:**\n\n- `linkId` (path): Link ObjectId\n\n**Query Parameters:**\n\n- `from` (ISO date, optional): Start date (default: 30 days ago)\n- `to` (ISO date, optional): End date (default: today)\n\n**Response (200 OK):**\n\n```json\n{\n  \"series\": [\n    {\n      \"day\": \"2025-01-01T00:00:00.000Z\",\n      \"total\": 5\n    },\n    {\n      \"day\": \"2025-01-02T00:00:00.000Z\",\n      \"total\": 12\n    }\n  ]\n}\n```\n\n**Date Handling:**\n\n- All dates are in UTC\n- Missing days are filled with zero values\n- Maximum range: 365 days\n\n---\n\n#### GET `/api/analytics/:linkId/breakdown`\n\nGet top categories for a specific dimension over a date range.\n\n**Authentication:** Required\n\n**Parameters:**\n\n- `linkId` (path): Link ObjectId\n\n**Query Parameters:**\n\n- `dim` (country|referrer|device, default: country): Dimension to analyze\n- `from` (ISO date, optional): Start date\n- `to` (ISO date, optional): End date\n- `limit` (1-50, default: 10): Number of top results\n\n**Response (200 OK):**\n\n```json\n{\n  \"dimension\": \"referrer\",\n  \"rows\": [\n    {\n      \"label\": \"direct\",\n      \"count\": 42\n    },\n    {\n      \"label\": \"twitter.com\",\n      \"count\": 17\n    },\n    {\n      \"label\": \"facebook.com\",\n      \"count\": 8\n    }\n  ]\n}\n```\n\n**Dimension Details:**\n\n- `country`: ISO country codes (US, GB, DE, etc.)\n- `referrer`: Domain names or \"direct\" for direct traffic\n- `device`: desktop, mobile, tablet, bot, other\n\n---\n\n### User Utilities\n\n#### GET `/api/user/urls`\n\nConvenience endpoint to list user URLs with simple filtering.\n\n**Authentication:** Required\n\n**Query Parameters:**\n\n- `folderId` (ObjectId|\"unfiled\"): Filter by folder\n- `tag` (string): Filter by single tag\n\n**Response (200 OK):**\n\n```json\n{\n  \"urls\": [\n    {\n      \"_id\": \"66f1234567890abcdef12345\",\n      \"fullUrl\": \"https://example.com/long-url\",\n      \"shortUrl\": \"abc123\",\n      \"tags\": [\"marketing\", \"q3\"]\n    }\n  ]\n}\n```\n\n**Response (404 Not Found):**\n\n```json\n{\n  \"message\": \"No URLs found\"\n}\n```\n\n---\n\n## Authentication \u0026 Security\n\n### Authentication Methods\n\nThe API supports two authentication methods:\n\n1. **HTTP-Only Cookies** (Recommended for web browsers)\n\n   - Cookie name: `accessToken`\n   - Automatically set on login/register\n   - Requires `credentials: 'include'` in fetch requests\n   - Secure and httpOnly flags in production\n\n2. **Bearer Tokens** (For API clients)\n   - Header: `Authorization: Bearer \u003cjwt-token\u003e`\n   - Suitable for mobile apps and server-to-server communication\n\n### JWT Token Structure\n\n```javascript\n{\n  \"sub\": \"66d1111111111111111111111\",  // User ID\n  \"email\": \"user@example.com\",\n  \"iat\": 1735776133,                   // Issued at\n  \"exp\": 1735779733                    // Expires at (1 hour)\n}\n```\n\n### Security Features\n\n#### CORS Configuration\n\n- Strict origin whitelist via `CORS_ORIGINS` environment variable\n- Credentials enabled for cookie authentication\n- Preflight request handling for complex requests\n\n#### Cookie Security\n\n- `httpOnly`: Prevents XSS access to tokens\n- `secure`: HTTPS-only in production\n- `sameSite`: 'none' for cross-origin requests in production\n\n#### Request Security\n\n- Rate limiting (implementation dependent)\n- Request size limits via Express built-in middleware\n- Input validation and sanitization\n\n#### Database Security\n\n- Mongoose schema validation\n- Parameterized queries prevent SQL injection\n- User data isolation through ownership checks\n\n### Password Security\n\n- Bcrypt hashing with salt rounds (configurable)\n- Minimum password requirements enforced\n- No password storage in JWT tokens\n\n---\n\n## Error Handling\n\n### Error Response Format\n\nAll API errors follow a consistent format:\n\n```json\n{\n  \"success\": false,\n  \"error\": \"Descriptive error message\",\n  \"stack\": \"Error stack trace (development only)\"\n}\n```\n\n### HTTP Status Codes\n\n| Code | Meaning               | Usage                                    |\n| ---- | --------------------- | ---------------------------------------- |\n| 400  | Bad Request           | Invalid input, validation errors         |\n| 401  | Unauthorized          | Missing or invalid authentication        |\n| 403  | Forbidden             | Valid auth but insufficient permissions  |\n| 404  | Not Found             | Resource doesn't exist                   |\n| 409  | Conflict              | Resource conflict (duplicate slug, etc.) |\n| 413  | Payload Too Large     | Request body exceeds size limit          |\n| 429  | Too Many Requests     | Rate limit exceeded                      |\n| 500  | Internal Server Error | Unexpected server errors                 |\n\n### Error Categories\n\n#### Validation Errors (400)\n\n- Missing required fields\n- Invalid data formats\n- Business rule violations\n\n**Example:**\n\n```json\n{\n  \"success\": false,\n  \"error\": \"URL is required and must be a valid HTTP/HTTPS URL\"\n}\n```\n\n#### Authentication Errors (401)\n\n- Missing JWT token\n- Expired token\n- Invalid token signature\n\n**Example:**\n\n```json\n{\n  \"success\": false,\n  \"error\": \"Invalid or expired token\"\n}\n```\n\n#### Authorization Errors (403)\n\n- Link paused (for redirects)\n- Accessing another user's resources\n\n**Example:**\n\n```json\n{\n  \"success\": false,\n  \"error\": \"This link has been paused by the owner\"\n}\n```\n\n#### Resource Errors (404)\n\n- Link not found\n- User not found\n- Folder not found\n\n**Example:**\n\n```json\n{\n  \"success\": false,\n  \"error\": \"Link not found\"\n}\n```\n\n### Error Handling Best Practices\n\n#### Client-Side Handling\n\n```javascript\ntry {\n  const response = await fetch(\"/api/links\", {\n    credentials: \"include\",\n  });\n\n  if (!response.ok) {\n    const error = await response.json();\n    throw new Error(error.error || \"Request failed\");\n  }\n\n  const data = await response.json();\n  return data;\n} catch (error) {\n  console.error(\"API Error:\", error.message);\n  // Handle error appropriately\n}\n```\n\n#### Server-Side Logging\n\n- All errors are logged with appropriate context\n- Sensitive information excluded from client responses\n- Error tracking integration ready\n\n---\n\n## Deployment\n\n### Render Deployment Configuration\n\n#### Backend Service\n\n1. **Service Type**: Web Service\n2. **Runtime**: Node.js\n3. **Build Command**: `npm install`\n4. **Start Command**: `node app.js`\n5. **Environment Variables**: Set all required variables from configuration section\n\n#### Frontend Service\n\n1. **Service Type**: Static Site or Web Service\n2. **Runtime**: Node.js (for build process)\n3. **Build Command**: `npm install \u0026\u0026 npm run build`\n4. **Publish Directory**: `dist` (or build output directory)\n\n### Environment Configuration\n\n#### Production Checklist\n\n- [ ] `NODE_ENV=production`\n- [ ] Secure `JWT_SECRET` (minimum 32 characters)\n- [ ] Correct `APP_URL` pointing to backend service\n- [ ] Complete `CORS_ORIGINS` with all frontend URLs\n- [ ] Valid `MONGO_URL` with proper credentials\n\n#### SSL and Domain Setup\n\n- Render provides automatic SSL certificates\n- Custom domains supported with DNS configuration\n- HTTPS enforced in production environment\n\n### Database Setup\n\n#### MongoDB Atlas Configuration\n\n1. Create cluster with appropriate tier\n2. Configure network access (0.0.0.0/0 for cloud deployment)\n3. Create database user with read/write permissions\n4. Obtain connection string for `MONGO_URL`\n\n#### Index Creation\n\nEnsure proper indexes exist for optimal performance:\n\n```javascript\n// Short URL collection\ndb.shorturls.createIndex({ shortUrl: 1 }, { unique: true });\ndb.shorturls.createIndex({ user: 1, createdAt: -1 });\ndb.shorturls.createIndex({ user: 1, status: 1 });\n\n// User collection\ndb.users.createIndex({ email: 1 }, { unique: true });\n\n// Folder collection\ndb.folders.createIndex({ user: 1, name: 1 }, { unique: true });\n\n// Click aggregation collection\ndb.clickaggs.createIndex({ linkId: 1, day: 1 }, { unique: true });\n```\n\n### Monitoring and Maintenance\n\n#### Health Checks\n\nThe application includes basic health check endpoints for monitoring service availability:\n\n```javascript\n// GET /health\n{\n  \"status\": \"ok\",\n  \"timestamp\": \"2025-01-02T10:30:00.000Z\",\n  \"database\": \"connected\"\n}\n```\n\n#### Performance Monitoring\n\n- Response time tracking via Express middleware\n- Database query performance monitoring\n- Error rate tracking and alerting\n- Resource usage monitoring (CPU, memory)\n\n#### Backup Strategy\n\n- MongoDB Atlas provides automatic backups\n- Point-in-time recovery available\n- Regular backup testing recommended\n- Export critical data for disaster recovery\n\n#### Scaling Considerations\n\n- Horizontal scaling supported via multiple service instances\n- Database connection pooling configured\n- Static asset caching via CDN\n- Analytics aggregation optimized for performance\n\n---\n\n## Development Setup\n\n### Prerequisites\n\n- **Node.js**: Version 18+ (LTS recommended)\n- **MongoDB**: Local instance or Atlas connection\n- **Git**: For version control\n- **Package Manager**: npm or yarn\n\n### Local Installation\n\n#### Backend Setup\n\n```bash\n# Clone repository\ngit clone \u003crepository-url\u003e\ncd url-shortener\n\n# Install backend dependencies\ncd backend\nnpm install\n\n# Create environment file\ncp .env.example .env\n# Edit .env with your configuration\n\n# Start development server\nnpm run dev\n```\n\n#### Frontend Setup\n\n```bash\n# Install frontend dependencies\ncd ../frontend\nnpm install\n\n# Start development server\nnpm run dev\n```\n\n### Development Environment Variables\n\nCreate `.env` file in backend directory:\n\n```bash\n# Development configuration\nNODE_ENV=development\nPORT=5000\nMONGO_URL=mongodb://localhost:27017/urlshortener\nAPP_URL=http://localhost:5000\nJWT_SECRET=development-jwt-secret-key\nCORS_ORIGINS=http://localhost:3000,http://localhost:5173\n```\n\n### Development Scripts\n\n#### Backend Scripts\n\n```json\n{\n  \"scripts\": {\n    \"dev\": \"nodemon app.js\",\n    \"start\": \"node app.js\",\n    \"test\": \"jest\",\n    \"test:watch\": \"jest --watch\",\n    \"lint\": \"eslint src/\",\n    \"lint:fix\": \"eslint src/ --fix\"\n  }\n}\n```\n\n#### Frontend Scripts\n\n```json\n{\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"vite build\",\n    \"preview\": \"vite preview\",\n    \"lint\": \"eslint src/\",\n    \"lint:fix\": \"eslint src/ --fix\"\n  }\n}\n```\n\n### Development Workflow\n\n#### Code Structure Guidelines\n\n- Follow established folder structure\n- Implement proper error handling\n- Add JSDoc comments for public APIs\n- Write unit tests for critical functions\n- Use consistent naming conventions\n\n#### Database Seeding\n\n```bash\n# Run database seeders (if available)\nnpm run seed\n\n# Reset database\nnpm run db:reset\n```\n\n#### Testing Strategy\n\n- Unit tests for utility functions\n- Integration tests for API endpoints\n- End-to-end tests for critical user flows\n- Performance tests for analytics aggregation\n\n### API Testing with cURL\n\n#### Authentication Flow\n\n```bash\n# Set base URL\nBASE_URL=\"http://localhost:5000\"\n\n# Register user\ncurl -X POST \"$BASE_URL/api/auth/register\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\":\"Test User\",\"email\":\"test@example.com\",\"password\":\"password123\"}' \\\n  -c cookies.txt\n\n# Login and save cookies\ncurl -X POST \"$BASE_URL/api/auth/login\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"email\":\"test@example.com\",\"password\":\"password123\"}' \\\n  -c cookies.txt\n\n# Test authenticated endpoint\ncurl -X GET \"$BASE_URL/api/links\" \\\n  -H \"Content-Type: application/json\" \\\n  -b cookies.txt\n```\n\n#### Link Management\n\n```bash\n# Create short URL\ncurl -X POST \"$BASE_URL/api/create\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"url\":\"https://example.com\",\"slug\":\"test\"}' \\\n  -b cookies.txt\n\n# List user links\ncurl -X GET \"$BASE_URL/api/links?limit=10\" \\\n  -H \"Content-Type: application/json\" \\\n  -b cookies.txt\n\n# Batch operations\ncurl -X POST \"$BASE_URL/api/links/batch\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"op\":\"pause\",\"ids\":[\"LINK_ID_1\",\"LINK_ID_2\"]}' \\\n  -b cookies.txt\n```\n\n### Debugging\n\n#### Backend Debugging\n\n- Use `console.log()` for development debugging\n- Implement structured logging for production\n- MongoDB queries logged in development mode\n- Error stack traces available in development\n\n#### Frontend Debugging\n\n- React Developer Tools browser extension\n- TanStack Query Devtools for state inspection\n- Network tab for API request debugging\n- Console logging for state changes\n\n### Hot Reloading\n\n#### Backend (Nodemon)\n\n```javascript\n// nodemon.json\n{\n  \"watch\": [\"src\"],\n  \"ext\": \"js,json\",\n  \"ignore\": [\"node_modules\", \"*.test.js\"],\n  \"exec\": \"node app.js\"\n}\n```\n\n#### Frontend (Vite)\n\n- Automatic hot module replacement\n- Fast refresh for React components\n- Instant updates for CSS changes\n- Source map support for debugging\n\n---\n\n## API Examples\n\n### Complete Workflow Examples\n\n#### User Registration and First Link\n\n```bash\n# 1. Register new user\ncurl -X POST \"http://localhost:5000/api/auth/register\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"name\": \"John Doe\",\n    \"email\": \"john@example.com\",\n    \"password\": \"SecurePass123!\"\n  }' \\\n  -c cookies.txt\n\n# 2. Create first link\ncurl -X POST \"http://localhost:5000/api/create\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"url\": \"https://example.com/my-landing-page\",\n    \"slug\": \"landing\"\n  }' \\\n  -b cookies.txt\n\n# 3. Create a folder\ncurl -X POST \"http://localhost:5000/api/folders\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\": \"Marketing\"}' \\\n  -b cookies.txt\n\n# 4. List links to get IDs\ncurl -X GET \"http://localhost:5000/api/links\" \\\n  -b cookies.txt\n```\n\n#### Bulk Operations Example\n\n```bash\n# Get link IDs first\nLINK_IDS='[\"66f1234567890abcdef12345\",\"66f1234567890abcdef12346\"]'\n\n# Pause multiple links\ncurl -X POST \"http://localhost:5000/api/links/batch\" \\\n  -H \"Content-Type: application/json\" \\\n  -d \"{\\\"op\\\":\\\"pause\\\",\\\"ids\\\":$LINK_IDS}\" \\\n  -b cookies.txt\n\n# Add tags to multiple links\ncurl -X POST \"http://localhost:5000/api/links/batch\" \\\n  -H \"Content-Type: application/json\" \\\n  -d \"{\\\"op\\\":\\\"addTags\\\",\\\"ids\\\":$LINK_IDS,\\\"payload\\\":{\\\"tags\\\":[\\\"q4\\\",\\\"promo\\\"]}}\" \\\n  -b cookies.txt\n\n# Move to folder\ncurl -X POST \"http://localhost:5000/api/links/batch\" \\\n  -H \"Content-Type: application/json\" \\\n  -d \"{\\\"op\\\":\\\"moveToFolder\\\",\\\"ids\\\":$LINK_IDS,\\\"payload\\\":{\\\"folderId\\\":\\\"66e9876543210fedcba09876\\\"}}\" \\\n  -b cookies.txt\n```\n\n#### Analytics Retrieval\n\n```bash\n# Get timeseries data for last 30 days\nLINK_ID=\"66f1234567890abcdef12345\"\ncurl -X GET \"http://localhost:5000/api/analytics/$LINK_ID/timeseries\" \\\n  -b cookies.txt\n\n# Get country breakdown for specific date range\ncurl -X GET \"http://localhost:5000/api/analytics/$LINK_ID/breakdown?dim=country\u0026from=2025-01-01\u0026to=2025-01-31\u0026limit=20\" \\\n  -b cookies.txt\n\n# Get referrer breakdown\ncurl -X GET \"http://localhost:5000/api/analytics/$LINK_ID/breakdown?dim=referrer\u0026limit=10\" \\\n  -b cookies.txt\n```\n\n### JavaScript/Fetch Examples\n\n#### Frontend API Integration\n\n```javascript\n// API client configuration\nconst API_BASE = \"https://url-shortener-backend-xxx.onrender.com\";\n\nclass ApiClient {\n  constructor() {\n    this.baseURL = API_BASE;\n  }\n\n  async request(endpoint, options = {}) {\n    const url = `${this.baseURL}${endpoint}`;\n    const config = {\n      credentials: \"include\",\n      headers: {\n        \"Content-Type\": \"application/json\",\n        ...options.headers,\n      },\n      ...options,\n    };\n\n    const response = await fetch(url, config);\n\n    if (!response.ok) {\n      const error = await response.json();\n      throw new Error(error.error || \"Request failed\");\n    }\n\n    return response.json();\n  }\n\n  // Authentication methods\n  async register(userData) {\n    return this.request(\"/api/auth/register\", {\n      method: \"POST\",\n      body: JSON.stringify(userData),\n    });\n  }\n\n  async login(credentials) {\n    return this.request(\"/api/auth/login\", {\n      method: \"POST\",\n      body: JSON.stringify(credentials),\n    });\n  }\n\n  async logout() {\n    return this.request(\"/api/auth/logout\", { method: \"POST\" });\n  }\n\n  async getCurrentUser() {\n    return this.request(\"/api/auth/me\");\n  }\n\n  // Link management methods\n  async createShortUrl(data) {\n    return this.request(\"/api/create\", {\n      method: \"POST\",\n      body: JSON.stringify(data),\n    });\n  }\n\n  async getLinks(params = {}) {\n    const queryString = new URLSearchParams(params).toString();\n    return this.request(`/api/links${queryString ? `?${queryString}` : \"\"}`);\n  }\n\n  async batchOperation(operation, linkIds, payload = {}) {\n    return this.request(\"/api/links/batch\", {\n      method: \"POST\",\n      body: JSON.stringify({\n        op: operation,\n        ids: linkIds,\n        payload,\n      }),\n    });\n  }\n\n  async updateLinkStatus(linkId, status) {\n    return this.request(`/api/links/${linkId}/status`, {\n      method: \"PATCH\",\n      body: JSON.stringify({ status }),\n    });\n  }\n\n  async updateLinkTags(linkId, tags) {\n    return this.request(`/api/links/${linkId}/tags`, {\n      method: \"PATCH\",\n      body: JSON.stringify({ tags }),\n    });\n  }\n\n  // Folder methods\n  async getFolders(withCounts = false) {\n    const params = withCounts ? \"?withCounts=1\" : \"\";\n    return this.request(`/api/folders${params}`);\n  }\n\n  async createFolder(name) {\n    return this.request(\"/api/folders\", {\n      method: \"POST\",\n      body: JSON.stringify({ name }),\n    });\n  }\n\n  async deleteFolder(folderId) {\n    return this.request(`/api/folders/${folderId}`, {\n      method: \"DELETE\",\n    });\n  }\n\n  // Analytics methods\n  async getTimeseries(linkId, dateRange = {}) {\n    const params = new URLSearchParams(dateRange).toString();\n    return this.request(\n      `/api/analytics/${linkId}/timeseries${params ? `?${params}` : \"\"}`\n    );\n  }\n\n  async getBreakdown(linkId, dimension = \"country\", options = {}) {\n    const params = new URLSearchParams({\n      dim: dimension,\n      ...options,\n    }).toString();\n    return this.request(\n      `/api/analytics/${linkId}/breakdown${params ? `?${params}` : \"\"}`\n    );\n  }\n}\n\n// Usage example\nconst api = new ApiClient();\n\n// Login and create a link\nasync function example() {\n  try {\n    await api.login({\n      email: \"user@example.com\",\n      password: \"password123\",\n    });\n\n    const result = await api.createShortUrl({\n      url: \"https://example.com\",\n      slug: \"my-link\",\n    });\n\n    console.log(\"Created:\", result.shortUrl);\n  } catch (error) {\n    console.error(\"Error:\", error.message);\n  }\n}\n```\n\n### React Hook Examples\n\n#### Custom Hook for Link Management\n\n```javascript\nimport { useQuery, useMutation, useQueryClient } from \"@tanstack/react-query\";\nimport { ApiClient } from \"../api/client\";\n\nconst api = new ApiClient();\n\nexport function useLinks(filters = {}) {\n  return useQuery({\n    queryKey: [\"links\", filters],\n    queryFn: () =\u003e api.getLinks(filters),\n    staleTime: 5 * 60 * 1000, // 5 minutes\n  });\n}\n\nexport function useCreateLink() {\n  const queryClient = useQueryClient();\n\n  return useMutation({\n    mutationFn: api.createShortUrl.bind(api),\n    onSuccess: () =\u003e {\n      // Invalidate and refetch links\n      queryClient.invalidateQueries({ queryKey: [\"links\"] });\n    },\n  });\n}\n\nexport function useBatchOperation() {\n  const queryClient = useQueryClient();\n\n  return useMutation({\n    mutationFn: ({ operation, linkIds, payload }) =\u003e\n      api.batchOperation(operation, linkIds, payload),\n    onSuccess: () =\u003e {\n      queryClient.invalidateQueries({ queryKey: [\"links\"] });\n    },\n  });\n}\n\n// Usage in component\nfunction LinkManager() {\n  const { data: links, isLoading } = useLinks({ limit: 50 });\n  const createLink = useCreateLink();\n  const batchOp = useBatchOperation();\n\n  const handleCreateLink = async (linkData) =\u003e {\n    try {\n      await createLink.mutateAsync(linkData);\n      toast.success(\"Link created successfully!\");\n    } catch (error) {\n      toast.error(error.message);\n    }\n  };\n\n  const handleBatchPause = async (selectedIds) =\u003e {\n    try {\n      await batchOp.mutateAsync({\n        operation: \"pause\",\n        linkIds: selectedIds,\n      });\n      toast.success(\"Links paused successfully!\");\n    } catch (error) {\n      toast.error(error.message);\n    }\n  };\n\n  if (isLoading) return \u003cdiv\u003eLoading...\u003c/div\u003e;\n\n  return \u003cdiv\u003e{/* Link management UI */}\u003c/div\u003e;\n}\n```\n\n---\n\n## Performance Optimization\n\n### Backend Optimization\n\n#### Database Indexing Strategy\n\n```javascript\n// Compound indexes for common query patterns\ndb.shorturls.createIndex({ user: 1, createdAt: -1 }); // User's recent links\ndb.shorturls.createIndex({ user: 1, status: 1 }); // User's links by status\ndb.shorturls.createIndex({ user: 1, folderId: 1 }); // User's links by folder\ndb.shorturls.createIndex({ user: 1, tags: 1 }); // User's links by tags\ndb.clickaggs.createIndex({ linkId: 1, day: -1 }); // Analytics queries\n```\n\n#### Query Optimization\n\n- Use pagination with cursors instead of offset/limit\n- Implement field selection to reduce data transfer\n- Aggregate analytics data at write time\n- Cache frequently accessed data\n\n#### Memory Management\n\n```javascript\n// MongoDB connection pooling\nconst mongooseOptions = {\n  maxPoolSize: 10, // Maximum number of connections\n  serverSelectionTimeoutMS: 5000,\n  socketTimeoutMS: 45000,\n  bufferCommands: false, // Disable mongoose buffering\n  bufferMaxEntries: 0, // Disable mongoose buffering\n};\n```\n\n### Frontend Optimization\n\n#### Bundle Optimization\n\n```javascript\n// Vite configuration for optimal builds\nexport default defineConfig({\n  build: {\n    rollupOptions: {\n      output: {\n        manualChunks: {\n          vendor: [\"react\", \"react-dom\"],\n          router: [\"@tanstack/react-router\"],\n          query: [\"@tanstack/react-query\"],\n        },\n      },\n    },\n  },\n});\n```\n\n#### Code Splitting\n\n```javascript\n// Lazy load components\nconst Analytics = lazy(() =\u003e import(\"./components/Analytics\"));\nconst Settings = lazy(() =\u003e import(\"./components/Settings\"));\n\n// Route-based splitting\nconst routes = [\n  {\n    path: \"/analytics\",\n    component: lazy(() =\u003e import(\"./pages/Analytics\")),\n  },\n];\n```\n\n#### Data Fetching Optimization\n\n```javascript\n// Infinite scroll implementation\nexport function useInfiniteLinks(filters) {\n  return useInfiniteQuery({\n    queryKey: [\"links\", \"infinite\", filters],\n    queryFn: ({ pageParam }) =\u003e api.getLinks({ ...filters, cursor: pageParam }),\n    getNextPageParam: (lastPage) =\u003e lastPage.nextCursor,\n    staleTime: 5 * 60 * 1000,\n  });\n}\n```\n\n### Caching Strategy\n\n#### Browser Caching\n\n- Static assets cached with long expiration\n- API responses cached with appropriate headers\n- Service worker for offline functionality\n\n#### Server-Side Caching\n\n- Redis for session storage (if implemented)\n- Database query result caching\n- CDN for static asset delivery\n\n---\n\n## Security Best Practices\n\n### Input Validation and Sanitization\n\n#### Backend Validation\n\n```javascript\n// URL validation middleware\nconst validateUrl = (req, res, next) =\u003e {\n  const { url } = req.body;\n\n  if (!url || typeof url !== \"string\") {\n    return res.status(400).json({\n      success: false,\n      error: \"URL is required and must be a string\",\n    });\n  }\n\n  try {\n    new URL(url);\n    if (!url.startsWith(\"http://\") \u0026\u0026 !url.startsWith(\"https://\")) {\n      throw new Error(\"Invalid protocol\");\n    }\n  } catch (error) {\n    return res.status(400).json({\n      success: false,\n      error: \"Invalid URL format\",\n    });\n  }\n\n  next();\n};\n\n// Slug validation\nconst validateSlug = (slug) =\u003e {\n  const slugRegex = /^[a-zA-Z0-9_-]+$/;\n  return slug.length \u003e= 1 \u0026\u0026 slug.length \u003c= 50 \u0026\u0026 slugRegex.test(slug);\n};\n```\n\n#### Frontend Validation\n\n```javascript\n// Form validation with schema\nconst linkSchema = {\n  url: {\n    required: true,\n    pattern: /^https?:\\/\\/.+/,\n    message: \"Please enter a valid HTTP or HTTPS URL\",\n  },\n  slug: {\n    pattern: /^[a-zA-Z0-9_-]+$/,\n    maxLength: 50,\n    message:\n      \"Slug must contain only letters, numbers, hyphens, and underscores\",\n  },\n};\n```\n\n### Rate Limiting\n\n#### Implementation Options\n\n```javascript\n// Express rate limiting middleware\nconst rateLimit = require(\"express-rate-limit\");\n\nconst createLimiter = rateLimit({\n  windowMs: 15 * 60 * 1000, // 15 minutes\n  max: 100, // Limit each IP to 100 requests per windowMs\n  message: {\n    success: false,\n    error: \"Too many requests, please try again later\",\n  },\n});\n\napp.use(\"/api/create\", createLimiter);\n```\n\n### SQL Injection Prevention\n\n#### Mongoose Protection\n\n- Always use Mongoose schema validation\n- Avoid direct MongoDB queries with user input\n- Use parameterized queries when necessary\n\n### XSS Prevention\n\n#### Output Encoding\n\n- React automatically escapes JSX content\n- Validate and sanitize HTML input\n- Use Content Security Policy headers\n\n### CSRF Protection\n\n#### Token-Based Protection\n\n```javascript\n// CSRF middleware (if needed)\nconst csrf = require(\"csurf\");\nconst csrfProtection = csrf({ cookie: true });\n\napp.use(csrfProtection);\n```\n\n---\n\n## Troubleshooting\n\n### Common Issues and Solutions\n\n#### Authentication Problems\n\n**Issue**: \"Invalid or expired token\"\n\n```bash\n# Check token expiration\ncurl -X GET \"http://localhost:5000/api/auth/me\" \\\n  -H \"Authorization: Bearer YOUR_TOKEN\" \\\n  -v\n```\n\n**Solutions**:\n\n- Verify JWT_SECRET matches between environments\n- Check token expiration time\n- Ensure proper cookie settings for cross-origin requests\n\n#### CORS Errors\n\n**Issue**: Cross-origin request blocked\n\n```javascript\n// Browser console error:\n// Access to fetch at 'https://api.example.com' from origin 'https://app.example.com'\n// has been blocked by CORS policy\n```\n\n**Solutions**:\n\n- Add frontend URL to CORS_ORIGINS environment variable\n- Verify credentials: 'include' in fetch requests\n- Check preflight request handling\n\n#### Database Connection Issues\n\n**Issue**: MongoDB connection timeout\n\n```bash\n# Test connection\nnode -e \"\nconst mongoose = require('mongoose');\nmongoose.connect('YOUR_MONGO_URL')\n  .then(() =\u003e console.log('Connected'))\n  .catch(err =\u003e console.error('Error:', err));\n\"\n```\n\n**Solutions**:\n\n- Verify MongoDB URL format and credentials\n- Check network access rules (MongoDB Atlas)\n- Ensure database exists and user has permissions\n\n#### Deployment Issues\n\n**Issue**: Environment variables not loading\n\n```bash\n# Check environment in deployed app\ncurl https://your-app.onrender.com/api/health\n```\n\n**Solutions**:\n\n- Verify all environment variables are set in deployment platform\n- Check for typos in variable names\n- Ensure proper quote handling for complex values\n\n### Debug Mode\n\n#### Backend Debugging\n\n```javascript\n// Enable debug mode\nprocess.env.DEBUG = \"app:*\";\n\n// Add debug logging\nconst debug = require(\"debug\")(\"app:api\");\ndebug(\"Processing request:\", req.path);\n```\n\n#### Frontend Debugging\n\n```javascript\n// Enable React Query devtools\nimport { ReactQueryDevtools } from \"@tanstack/react-query-devtools\";\n\nfunction App() {\n  return (\n    \u003c\u003e\n      \u003cYourApp /\u003e\n      {process.env.NODE_ENV === \"development\" \u0026\u0026 \u003cReactQueryDevtools /\u003e}\n    \u003c/\u003e\n  );\n}\n```\n\n### Performance Issues\n\n#### Slow API Responses\n\n1. Check database query performance\n2. Review index usage\n3. Monitor network latency\n4. Analyze payload sizes\n\n#### High Memory Usage\n\n1. Check for memory leaks in event listeners\n2. Review database connection pooling\n3. Monitor garbage collection\n4. Analyze bundle sizes\n\n---\n\n## License\n\nMIT License\n\nCopyright (c) 2025\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n---\n\n## Support and Contributing\n\n### Getting Help\n\nFor technical support and questions:\n\n- Review this documentation thoroughly\n- Check the troubleshooting section\n- Search existing issues in the repository\n- Create a new issue with detailed information\n\n### Contributing\n\nContributions are welcome! Please follow these guidelines:\n\n1. Fork the repository\n2. Create a feature branch\n3. Follow existing code style and conventions\n4. Add tests for new functionality\n5. Update documentation as needed\n6. Submit a pull request with clear description\n\n### Development Standards\n\n- Follow existing code structure and naming conventions\n- Write comprehensive tests for new features\n- Update API documentation for endpoint changes\n- Maintain backward compatibility when possible\n- Use semantic versioning for releases\n\n---\n\n_This documentation is maintained alongside the codebase. For the most current information, please refer to the source code and inline comments._\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftanim-mishkat%2Furl-shortener","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftanim-mishkat%2Furl-shortener","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftanim-mishkat%2Furl-shortener/lists"}