Skip to content
Kouskous56Public

About

No description, website, or topics provided.

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

38 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

GoShorty - URL Shortener with TTL

Current release: v3.0.0. The canonical local address is http://goshorty.localhost:8080; public deployments use the hostname supplied through PUBLIC_BASE_URL.

A production-ready URL shortener service built with Go, featuring automatic expiration (TTL) support with configurable time windows from 5 minutes to 7 days.

Features

Production hardening includes origin-allowlisted CORS, browser security headers, per-route rate limiting, a 1 MiB request-body limit, authenticated password rotation, and separate liveness/PostgreSQL readiness checks.

  • ✅ Create short URLs with automatic TTL expiration
  • ✅ Multiple TTL options: 5m, 15m, 1h, 24h, 7 days (168h)
  • ✅ Custom short codes (optional)
  • ✅ Visit tracking and analytics
  • ✅ RESTful API
  • ✅ PostgreSQL persistence in production
  • ✅ In-memory storage fallback for local development
  • ✅ CORS enabled
  • ✅ Production-ready error handling

Project Structure

GoShorty/
├── main.go                 # Application entry point
├── config/
│   └── config.go          # Configuration management
├── models/
│   └── url.go            # Data models
├── services/
│   └── url_service.go    # Business logic
├── handlers/
│   └── handler.go        # HTTP request handlers
├── storage/
│   ├── interfaces.go     # Repository contracts
│   ├── storage.go        # In-memory development storage
│   ├── postgres.go       # PostgreSQL production storage
│   └── migrations/       # Embedded database migrations
├── utils/
│   └── random.go         # Utility functions
├── go.mod                # Go module file
├── middleware.go         # Security headers, CORS, request body limits
├── observability.go      # Structured logs, metrics, panic recovery
├── api_spec.go           # Embedded OpenAPI 3.1 document + route
├── main_test.go          # Route-layer and SPA fallback tests
├── frontend_test.go      # SPA asset regression tests
├── static/
│   ├── index.html        # SPA markup (no inline script/style)
│   ├── css/style.css     # SPA stylesheet
│   └── js/app.js         # SPA logic — canonical /api/v1, session restore
├── e2e/
│   ├── package.json        # Playwright dependency + scripts (pinned version)
│   ├── playwright.config.ts # chromium (CI) + local-chrome projects
│   └── tests/
│       └── spa.spec.ts     # SPA happy path + session-restore specs
└── README.md             # This file

Installation

Prerequisites

  • Go 1.26.8 or higher (includes required standard-library security fixes)
  • Windows, macOS, or Linux

Setup

Recommended environment bootstrap:

powershell -NoProfile -ExecutionPolicy Bypass -File scripts/setup-env.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File scripts/doctor.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File scripts/dev.ps1

On Linux/macOS/WSL:

bash scripts/setup-env.sh
bash scripts/doctor.sh
bash scripts/dev.sh

The scripts start PostgreSQL and run GoShorty with the ignored .env.local. They use Docker Compose (PostgreSQL 16) when Docker is available and fall back to an embedded PostgreSQL instance (cmd/localdb) when it is not. See ENVIRONMENTS.md for development, test, CI, and Railway production configuration.

  1. Navigate to the project directory:
cd GoShorty
  1. Install dependencies:
go mod download
  1. Set a development signing key and run the application:
# PowerShell
$env:SECRET_KEY="development-secret-change-me"
go run .

# Linux/macOS
SECRET_KEY="development-secret-change-me" go run .

The server will start on http://goshorty.localhost:8080 when using the generated local environment. http://localhost:8080 remains usable for direct access.

Quality gate

GitHub Actions runs formatting, module verification, vet, unit tests, coverage thresholds, a Linux race detector, govulncheck, PostgreSQL integration tests, and an end-to-end API flow for every pull request and push to main.

Local equivalents:

go test ./... -count=1
go vet ./...
go test ./... -covermode=atomic -coverprofile=coverage.out
bash scripts/check-coverage.sh coverage.out 30

See INTEGRATION_TESTING.md for PostgreSQL, race, and E2E instructions.

API Documentation

/api/v1/* is the canonical API surface; the legacy /api/* routes remain fully functional as backward-compatible aliases. The canonical short-link redirect format is /r/:code (/s/:code and /goshorty/:timeout/:code still work). See API_REFERENCE.md for the complete reference.

A machine-readable OpenAPI 3.1 description of the full surface is served at GET /api/v1/openapi.json (JSON); run the server to view it (http://localhost:8080/api/v1/openapi.json).

Create Short URL

Endpoint: POST /api/v1/urls (legacy alias: POST /api/shorten)

Request Body:

{
  "url": "https://example.com/very/long/url/that/needs/shortening",
  "expires_in": "24h",
  "custom_code": "mycode"
}

Parameters:

  • url (required): The original URL to shorten
  • expires_in (optional): TTL - one of: 5m, 15m, 1h, 24h, 168h (default: 24h)
  • custom_code (optional): Custom short code (must be alphanumeric, max 50 chars)

Response:

{
  "id": "a1b2c3d4e5f6g7h8",
  "short_url": "http://localhost:8080/r/mycode",
  "short_code": "mycode",
  "original_url": "https://example.com/very/long/url",
  "expires_in": "24h",
  "expires_at": "2026-02-17T12:00:00Z",
  "created_at": "2026-02-16T12:00:00Z"
}

Redirect to Original URL

Endpoint: GET /r/:code

Example: GET /r/mycode

Redirects to the original URL if not expired. Returns 404 if expired or not found. Backward-compatible aliases: GET /s/:code and GET /goshorty/:timeout/:code.

Get URL Information

Endpoint: GET /api/v1/urls/:code (legacy alias: GET /api/shorten/:code)

Response:

{
  "id": "a1b2c3d4e5f6g7h8",
  "short_code": "mycode",
  "original_url": "https://example.com/very/long/url",
  "expires_in": "24h",
  "expires_at": "2026-02-17T12:00:00Z",
  "created_at": "2026-02-16T12:00:00Z",
  "visits": 5
}

Delete Short URL

Endpoint: DELETE /api/v1/urls/:code (legacy alias: DELETE /api/shorten/:code)

Response:

{
  "message": "URL deleted successfully",
  "code": "mycode"
}

Get All URLs

Endpoint: GET /api/v1/urls (legacy alias: GET /api/shorten/all)

Paginated: limit (1–200, default 50) and cursor (from next_cursor) query parameters; responds with next_cursor and total. The legacy alias returns the full list without pagination fields. URLs are ordered newest first.

Response:

{
  "urls": [...]
}

Get Statistics

Endpoint: GET /api/v1/stats (legacy alias: GET /api/stats)

Response:

{
  "total_urls": 42,
  "total_visits": 156
}

Health Check

Endpoint: GET /health

Response:

{
  "status": "ok",
  "service": "goshorty"
}

Readiness Check

Endpoint: GET /ready

Returns 200 only when required production dependencies, including PostgreSQL, are reachable. Railway uses this endpoint for deployment health checks.

Endpoint: GET /metrics

Returns Prometheus-compatible HTTP request counters and duration summaries. Every response also carries X-Request-ID, which matches the structured JSON request log. Release deployments require Authorization: Bearer <METRICS_TOKEN>.

Endpoint: GET /version

Returns the application version, source commit, and build timestamp.

Operational procedures, backup/restore drills, alerts, and deployment verification are documented in OPERATIONS.md. Security boundaries and token revocation semantics are documented in SECURITY.md.

Change Password

Endpoint: PUT /api/auth/password

Requires Authorization: Bearer <token>.

{
  "current_password": "current-password",
  "new_password": "new-unique-password"
}

The new password must be between 12 and 72 bytes.

Usage Examples

Using cURL

Create a short URL:

curl -X POST http://localhost:8080/api/v1/urls \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://www.google.com",
    "expires_in": "1h",
    "custom_code": "google"
  }'

Get URL info:

curl http://localhost:8080/api/v1/urls/google

Delete short URL:

curl -X DELETE http://localhost:8080/api/v1/urls/google

Using JavaScript/Fetch

// Create short URL
const response = await fetch('http://localhost:8080/api/v1/urls', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    url: 'https://www.example.com',
    expires_in: '24h'
  })
});

const data = await response.json();
console.log(data.short_url);

TTL Options

Option Duration Use Case
5m 5 minutes Quick temporary links
15m 15 minutes Short-lived sharing
1h 1 hour Same-day links
24h 1 day Default option
168h 7 days Long-term sharing

Technical Details

Storage

  • Production: PostgreSQL selected through DATABASE_URL
  • Development: Thread-safe in-memory fallback when not in release mode
  • Migrations: Embedded, versioned SQL migrations run automatically at startup
  • Consistency: Unique short-code constraint, transactional admin invariants, atomic visit counters
  • Cleanup: Expired URLs are removed in bounded background batches

Production security

  • Login: 10 requests/minute/IP
  • Registration: 5 requests/hour/IP (configurable via REGISTER_LIMIT_PER_HOUR)
  • Password changes: 5 requests/hour/IP
  • URL creation: 60 requests/minute/IP
  • Redirects: 300 requests/minute/IP
  • Request bodies: maximum 1 MiB
  • CORS defaults to PUBLIC_BASE_URL; override with comma-separated ALLOWED_ORIGINS
  • Trusted reverse proxies default to Railway's 100.64.0.0/10; override with comma-separated TRUSTED_PROXIES
  • HTTP timeouts: 5s headers, 15s read/write, 60s idle

Frontend (SPA)

  • Single-page app served from the embedded static/ tree: markup (index.html), stylesheet (static/css/style.css) and logic (static/js/app.js) are split; no inline <script> or onclick.
  • Targets the canonical /api/v1/* surface.
  • Validates the stored session on load via GET /api/v1/auth/me and handles 401 globally as an expired session (auto logout).
  • Runs under the strict script-src 'self' Content Security Policy.

Concurrency

  • Thread-safe operations using sync.RWMutex
  • Goroutine for background cleanup
  • Safe for concurrent read/write access

Performance

  • Indexed short-code and expiry lookups
  • PostgreSQL connection pooling through pgx
  • Horizontal instances share the same persistent data

Architecture

Request → Router → Handler → Service → Storage
                     ↑                    ↓
                  Config ← ← ← ← ← ← ← ← 
  1. Handlers: Accept HTTP requests, validate input
  2. Services: Implement business logic, coordinate operations
  3. Storage: Manage data persistence and TTL
  4. Config: Centralized configuration management

Future Enhancements

  • Redis backend support for distributed deployments
  • Database persistence (PostgreSQL)
  • User authentication and role-based access
  • Analytics dashboard
  • Frontend session hardening (session restore, strict CSP, asset split)
  • Batch URL shortening
  • QR code generation
  • URL preview feature
  • Advanced rate limiting
  • Webhook support
  • Docker containerization

Development

Build

go build -o goshorty.exe

Run Binary

./goshorty.exe

Testing

go test ./...

Configuration

Production configuration is provided through environment variables:

  • SECRET_KEY — required token signing key.
  • ADMIN_PASSWORD — required in release mode.
  • DATABASE_URL — required in release mode.
  • PUBLIC_BASE_URL — canonical public origin for short links.
  • ADMIN_EMAIL — optional bootstrap admin email.
  • TOKEN_TTL — optional token lifetime, default 24h.
  • TOKEN_ISSUER / TOKEN_AUDIENCE — optional claims that bind tokens to the deployment; empty = disabled (backward compatible), enabling invalidates previously issued tokens.
  • SECRET_KEY_PREVIOUS — optional previous signing key for smooth key rotation; tokens signed with it keep verifying until the value is removed.
  • PORT — listening port; Railway supplies this automatically.
  • GIN_MODE=release — enables production mode.

Error Handling

The API returns appropriate HTTP status codes:

  • 200 OK: Successful request
  • 201 Created: URL successfully created
  • 400 Bad Request: Invalid input
  • 404 Not Found: URL not found or expired
  • 500 Internal Server Error: Server error

License

MIT License - Feel free to use this project

Support

For issues or questions, please open an issue in the repository.


GoShorty v1.0.0 - Built with Go & Gin Framework

About

No description, website, or topics provided.

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages