License: MIT SvelteKit TypeScript
Translation status Translation status
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
Translation status Translation status
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) docker-compose up -d
Access:
- 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-typescript
curl -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 dev
DATABASE_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)
Dashboard Analytics Screenshot
Analytics Analytics Slug Screenshot
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