Nah.pet – "Rewriting paths with bad energy" ✨
Open-source URL shortener with custom domains and analytics.
- ✂️ URL Shortening with custom slugs
- 🔐 Password protection for sensitive links
- ⏰ Automatic link expiration
- 📊 Detailed analytics (clicks, geolocation, browsers)
- 🌐 Custom domains with complete isolation
- 👥 Admin system with manual approval
- 🔑 REST API with API key authentication
- 🌍 Multilingual interface
Managed with Paraglide JS and Weblate for type-safety.
The cat. prefix bypasses CORS restrictions from Cloudflare and other CDNs by using a dedicated subdomain.
-
Domain verification:
- DNS: TXT record with token
- File:
/.well-known/nah-pet-verification.txt
-
CNAME configuration:
Type: CNAME Name: cat.example.com Value: cat.nah.pet TTL: 300 -
Redirection:
https://cat.example.com/abc123 → CNAME → cat.nah.pet → nah.pet
Each custom domain is fully isolated—no access to system routes (
/admin,/login, etc.).
git clone https://github.com/anhostfr/nah.pet
cd link-shortener
cp .env.example .env
# Edit variables
# DATABASE_URL=postgresql://user:password@postgres:5432/nahpet
# PUBLIC_MAIN_DOMAIN=your-domain.com
# ADMIN_EMAIL=admin@example.com
# OAUTH_CLIENT_ID=your_oauth_id (optional)
# OAUTH_CLIENT_SECRET=your_oauth_secret (optional)
# PUBLIC_DOC=false (set to 'true' for public API docs)
# PUBLIC_ALLOW_REGISTRATION=true (set to 'false' to disable registration)
docker-compose up -dAccess:
- Web interface:
http://localhost:3000 - API:
http://localhost:3000/doc
bun install
createdb nahpet
cp .env.example .env # Edit variables
bunx prisma migrate deploy
bun run devBuilt with sveltekit-api from JacobLinCool, OpenAPI 3.0 specification.
GET /api/v1/links– List linksPOST /api/v1/links– Create a linkGET/PUT/DELETE /api/v1/links/{id}– Manage a linkGET /api/v1/stats– Global statisticsGET /api/v1/stats/{slug}– Link statisticsPOST /api/v1/links/bulk– Bulk operationsGET /api/v1/stats/export– Export data
npm install @openapitools/openapi-generator-cli -g
curl -o openapi.json https://your-domain.com/api/v1/openapi.json
openapi-generator-cli generate \
-i openapi.json \
-g typescript-axios \
-o ./sdk-typescriptcurl -H "Authorization: Bearer YOUR_API_KEY" \
"https://your-domain.com/api/v1/links"- Frontend/Backend: SvelteKit 5 + TypeScript
- Runtime: Bun 1.x
- Database: PostgreSQL + Prisma ORM
- Authentication: Lucia Auth + OAuth2
- Styling: TailwindCSS 4 + Shadcn/ui
- i18n: Paraglide JS
- API: Sveltekit-api (OpenAPI)
- Deployment: Docker + Docker Compose
src/
├── routes/ # Pages and API
├── lib/
│ ├── components/ # Svelte components
│ ├── server/ # Server logic
│ └── paraglide/ # Translations
└── api/v1/ # API definitions
bun run dev # Development
bun run build # Production
bun run check # TypeScript check
bun run format # Prettier formatting
bunx prisma studio
bunx prisma generate
bunx prisma migrate devDATABASE_URL=postgresql://postgres:password@localhost:5432/nahpet
PUBLIC_MAIN_DOMAIN=localhost:5173
ADMIN_EMAIL=admin@example.com
OAUTH_CLIENT_ID=your_oauth_client_id (optional)
OAUTH_CLIENT_SECRET=your_oauth_client_secret (optional)
# Access control (optional)
PUBLIC_DOC=false # Set to 'true' to make API docs public
PUBLIC_ALLOW_REGISTRATION=true # Set to 'false' to disable registration
![]() Dashboard |
![]() Analytics |
![]() Analytics (Slug) |
cp messages/en.json messages/es.json # Add Spanish
# Add "es" in project.inlang/settings.json- Fork the repository
- Create a branch
feature/my-feature - Develop with
bun run dev - Check with
bun run check+bun run format - Open a Pull Request
MIT – see LICENSE


