A modern, self-hosted URL shortener service built with TanStack Start, React, Prisma, and SQLite. Features optional OIDC authentication and a clean, user-friendly interface.
⚠️ Warning: This project is under active development and is not ready for production use. Use at your own risk.
- 🔗 URL Shortening: Create short links from long URLs
- 🔐 Optional Authentication: OIDC-based authentication (can be disabled)
- 📊 Click Tracking: Track clicks with IP address and user agent information
- 🎨 Modern UI: Built with Mantine UI components
- 🚀 Fast: Powered by Bun runtime and TanStack Start
- 🐳 Docker Ready: Includes Dockerfile and compose configuration
- 💾 SQLite Database: Lightweight, file-based database
📋 See Release Notes for version history and changes.
-
Download
compose.yamlcurl -O https://github.com/skrylnikov/cutly/releases/download/v0.2.0/compose.yaml
-
Configure environment variables (optional)
Edit
compose.yamland replace the environment variable placeholders with your values:environment: # Database configuration - DATABASE_URL=file:/app/data/app.db # OIDC Configuration (optional - leave empty to disable authentication) - OIDC_ISSUER=https://your-oidc-provider.com - OIDC_CLIENT_ID=your-client-id - OIDC_CLIENT_SECRET=your-client-secret - JWT_SECRET=your-secret-key-for-jwt-signing # Application URL (optional - defaults to http://localhost:3000) - APP_URL=http://localhost:3000
If you don't need authentication, you can leave the OIDC variables empty (they default to empty strings). Note: All four OIDC-related variables (
OIDC_ISSUER,OIDC_CLIENT_ID,OIDC_CLIENT_SECRET, andJWT_SECRET) must be set together for authentication to work. -
Start the service
docker compose up -d
-
View logs
docker compose logs -f
-
Stop the service
docker compose down
The application will be available at http://localhost:3000 (or the port you configured).
-
Build the image
docker build -t cutly-app . -
Run the container
docker run -d \ --name cutly-app \ -p 3000:3000 \ -v $(pwd)/data:/app/data \ -e DATABASE_URL="file:/app/data/dev.db" \ -e APP_URL="http://localhost:3000" \ ghcr.io/skrylnikov/cutly:0.2.0
The application uses the following environment variables:
| Variable | Description | Example |
|---|---|---|
DATABASE_URL |
SQLite database connection string. Use file: prefix for SQLite. |
file:./dev.db or file:/app/data/dev.db |
| Variable | Description | Default | Example |
|---|---|---|---|
APP_URL |
Application URL. Used for generating short link URLs. | http://localhost:3000 |
https://short.ly |
OIDC_ISSUER |
OIDC provider issuer URL. If not set, authentication is disabled. | - | https://accounts.google.com |
OIDC_CLIENT_ID |
OIDC client ID. Required if OIDC authentication is enabled. | - | your-client-id |
OIDC_CLIENT_SECRET |
OIDC client secret. Required if OIDC authentication is enabled. | - | your-client-secret |
JWT_SECRET |
Secret key for signing JWT tokens. Required if OIDC authentication is enabled. | - | your-secret-key-for-jwt-signing |
- Type: Required
- Format: SQLite connection string with
file:prefix - Examples:
- Local development:
file:./dev.db - Docker:
file:/app/data/dev.db - Absolute path:
file:/var/lib/cutly/database.db
- Local development:
- Type: Optional
- Purpose: Used to generate full short link URLs
- When to set:
- When deploying behind a reverse proxy
- When using a custom domain
- When the application is not accessible at
http://localhost:3000
- Example: If your app is accessible at
https://short.ly, setAPP_URL=https://short.ly
- Type: Optional (all four must be set together to enable authentication)
- Purpose: Enable OIDC-based user authentication
- Behavior:
- If all four variables are set: Authentication is enabled, users must log in to create short links
- If any are missing: Authentication is disabled, anyone can create short links
- Supported Providers: Any OIDC-compliant provider (Google, Auth0, Keycloak, etc.)
- Setup:
- Register your application with your OIDC provider
- Set the redirect URI to:
{APP_URL}/api/auth/callback - Generate a JWT secret key (see below)
- Configure all four environment variables
Generating JWT Secret:
The JWT_SECRET is used to sign and verify JWT tokens for user sessions using the HS512 algorithm. For HS512 (HMAC with SHA-512), the minimum recommended key size is 64 bytes (512 bits) to match the algorithm's security strength.
Generate a secure random secret key using one of the following methods:
-
Using OpenSSL (recommended):
openssl rand -base64 64
This generates 64 random bytes, which results in an 88-character base64-encoded string.
-
Using Node.js/Bun:
node -e "console.log(require('crypto').randomBytes(64).toString('base64'))"This generates 64 random bytes, which results in an 88-character base64-encoded string.
-
Using Python:
python3 -c "import secrets; print(secrets.token_urlsafe(64))"This generates 64 random bytes using URL-safe base64 encoding.
Important: The secret must be at least 64 bytes (not characters) to meet HS512 security requirements. The base64-encoded string will be approximately 88 characters long. Keep the secret secure and never commit it to version control.
-
Prerequisites: Install Bun (v1.3.3 or higher)
-
Clone the repository
git clone git@github.com:skrylnikov/cutly.git cd cutly -
Install dependencies
bun install
-
Set up environment variables
cp env.example .env.local
Edit
.env.localwith your configuration (see Environment Variables section). -
Set up the database
bun run db:push
-
Start the development server
bun run dev
The application will be available at
http://localhost:3000
The project uses Prisma for database management. Available commands:
# Generate Prisma client
bun run db:generate
# Push schema changes to database (development)
bun run db:push
# Run migrations
bun run db:migrate
# Open Prisma Studio (database GUI)
bun run db:studio
# Seed database (if configured)
bun run db:seedThe application uses two main models:
- ShortLink: Stores original URLs, short IDs, and metadata
- Click: Tracks clicks on short links with IP, user agent, and timestamp
GET /- Home page with URL shortening formGET /:shortId- Redirect to original URL (tracks click)
GET /api/auth/login- Initiate OIDC login flowGET /api/auth/callback- OIDC callback handlerGET /api/auth/logout- Logout user
# Development
bun run dev # Start development server
# Building
bun run build # Build for production
bun run serve # Preview production build
# Code Quality
bun run lint # Run linter
bun run format # Format code
bun run check # Run linter and formatter
bun run type-check # TypeScript type checking
# Database
bun run db:generate # Generate Prisma client
bun run db:push # Push schema changes
bun run db:migrate # Run migrations
bun run db:studio # Open Prisma Studiocutly/
├── prisma/ # Prisma schema and migrations
├── public/ # Static assets
├── src/
│ ├── components/ # React components
│ ├── generated/ # Generated Prisma client
│ ├── lib/ # Utility functions and libraries
│ ├── routes/ # TanStack Router routes
│ ├── app.tsx # Application entry point
│ ├── db.ts # Database client
│ └── router.tsx # Router configuration
├── Dockerfile # Docker image definition
├── compose.yaml # Docker Compose configuration
└── package.json # Dependencies and scripts
- Database locked: Ensure only one instance is accessing the database file
- Migration errors: Check that the database file is writable
- Path issues: Use absolute paths in Docker environments
- OIDC not working: Verify all four OIDC-related variables are set correctly (
OIDC_ISSUER,OIDC_CLIENT_ID,OIDC_CLIENT_SECRET, andJWT_SECRET) - JWT errors: Ensure
JWT_SECRETis set and is a secure random string generated from at least 64 bytes (approximately 88 base64 characters) to meet HS512 security requirements - Callback errors: Ensure
APP_URLmatches your public URL - Redirect URI mismatch: Check that the redirect URI in your OIDC provider matches
{APP_URL}/api/auth/callback
- Change the port in
compose.yamlor use environment variables - Update
APP_URLif using a different port
Licensed under the MIT License.
