Skip to content

shubhamxdd/paste

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

25 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Pastebin

A self-hosted pastebin with syntax highlighting, paraphrase-protected pastes, gist collections, and full-text search.

Features

  • Create pastes with syntax highlighting for 17 languages
  • Optional paraphrase protection — only people with the paraphrase can view the content
  • Custom URLs — choose your own slug instead of a random ID
  • Delete any paste using a secret server-side delete code
  • View count — each paste tracks how many times it has been viewed
  • Line highlighting — click any line to highlight it and update the URL hash (e.g. /p/abc123#L42)
  • Gists — group multiple pastes into a single shareable collection (up to 20 pastes)
  • Edit and delete gists (requires delete code)
  • Admin page at /admin — metrics dashboard (total views, most viewed paste, top languages, protected count, recent activity) plus paginated browse and delete for all pastes and gists (protected by delete code)
  • Full-text search across all pastes
  • Raw paste view
  • File upload support — drag and drop a file to populate the editor
  • Dark and light theme
  • PostHog analytics (server-side and client-side)

Tech Stack

Client: React, TypeScript, Vite, Tailwind CSS, react-syntax-highlighter

Server: Node.js, Express, TypeScript, MongoDB

Infrastructure: Docker, Nginx, Caddy (TLS termination)

Self-Hosting

Prerequisites

  • Docker and Docker Compose
  • A domain pointing to your server (for TLS via Caddy)

Setup

  1. Clone the repository:

    git clone https://github.com/shubhamxdd/pastebin
    cd pastebin
  2. Create a .env file in the project root:

    POSTHOG_API_KEY=your_posthog_api_key
    POSTHOG_HOST=https://us.i.posthog.com
    
    VITE_POSTHOG_API_KEY=your_posthog_api_key
    VITE_POSTHOG_HOST=https://us.i.posthog.com
    
    PASTE_DELETE_CODE=your_secret_delete_code
  3. Update Caddyfile with your domain:

    your-domain.com {
        reverse_proxy client:80
    }
    
  4. Build and start:

    docker compose up --build -d

Environment Variables

Variable Required Description
PASTE_DELETE_CODE Yes Secret code required to delete or edit any paste or gist
POSTHOG_API_KEY No PostHog project API key for server-side analytics
POSTHOG_HOST No PostHog instance URL
VITE_POSTHOG_API_KEY No PostHog project API key for client-side analytics (baked in at build time)
VITE_POSTHOG_HOST No PostHog instance URL for client

API

Pastes


GET /api/pastes

List pastes with optional full-text search and pagination.

Query params: q, page, limit (max 50, default 20)

Response 200:

{
  "pastes": [
    {
      "id": "abc123",
      "title": "My snippet",
      "language": "typescript",
      "created_at": "2024-07-01T10:00:00.000Z",
      "protected": false
    }
  ],
  "total": 42,
  "page": 1,
  "limit": 20
}

POST /api/pastes

Create a paste.

Body:

{
  "title": "My snippet",
  "content": "console.log('hello')",
  "language": "javascript",
  "paraphrase": "optional-secret",
  "customId": "optional-slug"
}

Response 201:

{
  "id": "abc123"
}

GET /api/pastes/:id

Get a paste. If protected, content is omitted and protected is true. Each call increments the view count.

Response 200 (unprotected):

{
  "id": "abc123",
  "title": "My snippet",
  "content": "console.log('hello')",
  "language": "javascript",
  "created_at": "2024-07-01T10:00:00.000Z",
  "views": 42,
  "protected": false
}

Response 200 (protected):

{
  "id": "abc123",
  "title": "My snippet",
  "language": "javascript",
  "created_at": "2024-07-01T10:00:00.000Z",
  "views": 42,
  "protected": true
}

POST /api/pastes/:id/unlock

Verify paraphrase and return the full content of a protected paste.

Body:

{
  "paraphrase": "your-secret"
}

Response 200:

{
  "id": "abc123",
  "title": "My snippet",
  "content": "console.log('hello')",
  "language": "javascript",
  "created_at": "2024-07-01T10:00:00.000Z",
  "views": 42,
  "protected": true
}

Response 401:

{
  "error": "Incorrect paraphrase"
}

DELETE /api/pastes/:id

Delete a paste. Requires the server-side delete code.

Body:

{
  "deleteCode": "your-delete-code"
}

Response 200:

{
  "success": true
}

Response 401:

{
  "error": "Incorrect delete code"
}

GET /api/pastes/:id/raw

Returns the raw paste content as text/plain. For protected pastes, pass ?paraphrase=your-secret.


Gists (Collections)

Method Endpoint Description
GET /api/collections List all gists with pagination
POST /api/collections Create a gist
GET /api/collections/:id Get a gist with all its pastes
PATCH /api/collections/:id Update title and/or pastes (requires deleteCode)
DELETE /api/collections/:id Delete a gist (requires deleteCode)

GET /api/collections

List all gists with pagination.

Query params: page, limit (max 50, default 20)

Response 200:

{
  "collections": [
    {
      "id": "xyz789",
      "title": "My collection",
      "paste_count": 3,
      "created_at": "2024-07-01T10:00:00.000Z"
    }
  ],
  "total": 10,
  "page": 1,
  "limit": 20
}

POST /api/collections

Create a gist from existing pastes. Maximum 20 pastes per gist.

Body:

{
  "title": "My collection",
  "paste_ids": ["abc123", "def456"]
}

Response 201:

{
  "id": "xyz789"
}

GET /api/collections/:id

Get a gist with all its pastes populated. Protected pastes within the collection omit content.

Response 200:

{
  "id": "xyz789",
  "title": "My collection",
  "created_at": "2024-07-01T10:00:00.000Z",
  "pastes": [
    {
      "id": "abc123",
      "title": "My snippet",
      "content": "console.log('hello')",
      "language": "javascript",
      "created_at": "2024-07-01T10:00:00.000Z",
      "protected": false
    }
  ]
}

PATCH /api/collections/:id

Update the title and/or paste list of a gist. Requires the delete code.

Body:

{
  "deleteCode": "your-delete-code",
  "title": "Updated title",
  "paste_ids": ["abc123", "def456", "ghi789"]
}

Response 200:

{
  "success": true
}

Response 401:

{
  "error": "Incorrect delete code"
}

DELETE /api/collections/:id

Delete a gist. Requires the delete code. The individual pastes are not deleted.

Body:

{
  "deleteCode": "your-delete-code"
}

Response 200:

{
  "success": true
}

Stats


GET /api/stats

Returns aggregate metrics across all pastes and collections. Used by the admin dashboard.

Response 200:

{
  "total_pastes": 120,
  "total_collections": 15,
  "total_views": 4821,
  "protected_pastes": 8,
  "pastes_last_7_days": 23,
  "most_viewed": {
    "id": "abc123",
    "title": "My snippet",
    "language": "typescript",
    "views": 310
  },
  "top_languages": [
    { "language": "javascript", "count": 42 },
    { "language": "typescript", "count": 38 },
    { "language": "python", "count": 21 },
    { "language": "bash", "count": 10 },
    { "language": "json", "count": 9 }
  ]
}

Health


GET /health

Response 200:

{
  "status": "ok",
  "uptime": 3600,
  "mongodb": "connected",
  "memory": {
    "used_mb": 64,
    "heap_used_mb": 32,
    "heap_total_mb": 48
  },
  "node_version": "v20.14.0",
  "stats": {
    "pastes": 120,
    "collections": 15
  },
  "timestamp": "2024-07-01T10:00:00.000Z"
}

Local Development

Server:

cd server
npm install
npm run dev

Client:

cd client
npm install
npm run dev

The client dev server proxies /api requests to localhost:3000.

About

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages