Multi-tenant URL shortener for teams. Organizations share one workspace to create short links, track analytics, and collaborate with role-based access. Includes a REST API for programmatic link creation.
- Organizations β Multi-tenant workspaces with
owner,admin, andmemberroles - Custom & random slugs β Pick a slug or let the server generate one
- Link lifecycle β Scheduled activation, expiration dates, click caps that auto-disable
- REST API β Create links programmatically via API keys (no browser required)
- API key management β Owner-only dashboard to create, list, and revoke keys
- Analytics β Org-wide and per-link: clicks over time, devices, referrers, countries, recent activity
- Click recording β Every redirect logs IP, user agent, referrer, device, country; bots flagged and excluded from metrics
- Members & invitations β Invite teammates, promote/demote roles, cancel pending invitations
- Organization settings β Rename workspaces, leave, delete (owner-only)
| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router, Server Components) |
| Language | TypeScript (strict) |
| Database | PostgreSQL 18 with Drizzle ORM |
| Auth | Better Auth (email/password, organizations) |
| UI | Tailwind CSS v4 + shadcn/ui |
| Charts | Recharts |
| Monorepo | Turborepo + Bun workspaces |
bun installCopy the example env file and fill in a secret:
cp apps/web/.env.example apps/web/.envThe defaults work with the bundled Docker Postgres:
DATABASE_URL=postgres://postgres:password@localhost:5432/LinkTrim
BETTER_AUTH_SECRET=<generate a random string, minimum 32 characters>
BETTER_AUTH_URL=http://localhost:3001
CORS_ORIGIN=http://localhost:3001bun run db:start # starts Postgres 18 via Docker
bun run db:push # pushes Drizzle schema to the databasebun run devOpen http://localhost:3001. Sign up, create an organization, and start creating links.
Once you have an API key (created from the dashboard by the org owner), you can create links without the browser.
curl -X POST http://localhost:3001/api/links \
-H "Authorization: Bearer lt_your_api_key" \
-H "Content-Type: application/json" \
-d '{"organizationSlug":"my-org","originalUrl":"https://example.com"}'The slug field is optional. If omitted, a random 8-character slug is generated.
Optional fields: slug, clickCap, expiresAt, scheduledAt.
API keys are managed through the dashboard at /orgs/[slug]/api-keys. Only the organization owner can create or revoke keys. Keys are shown once at creation and cannot be recovered.
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/links |
Create a link |
| GET | /api/links |
List links for an org |
| PATCH | /api/links |
Toggle link active state |
Seed a ready-made organization with members, links, and ~30k realistic click records:
bun run db:seed # owner = first user in the database
bun run db:seed you@example.com # owner = a specific userCreates a demo_org workspace with 7 links, 3 demo members, a pending invitation, and click data spanning several weeks. The seed is idempotent.
| Table | Purpose |
|---|---|
user |
User accounts (email/password auth) |
session |
Active sessions, tracks active_organization_id |
account |
Auth provider links (email/password in this case) |
verification |
Email verification tokens |
organization |
Tenant workspaces (name, slug, logo) |
member |
Org membership with roles (owner/admin/member) |
invitation |
Pending org invitations |
link |
Short links (slug, URL, click cap, expiry, status) |
click |
Every redirect (IP, user agent, device, country) |
api_key |
API keys for programmatic access (hashed, revocable) |
Schema definitions: packages/db/src/schema/
LinkTrim/
βββ apps/
β βββ web/ # Next.js application
β βββ src/
β βββ app/ # App Router pages & API routes
β βββ components/ # App-level components
β βββ context/ # React contexts (organization)
β βββ hooks/ # Custom hooks (links, analytics)
β βββ lib/ # Auth client, roles, slugs, API key auth
β βββ types/ # TypeScript type definitions
βββ packages/
β βββ auth/ # Better Auth server configuration
β βββ db/ # Drizzle schema, migrations, seed scripts
β βββ ui/ # Shared shadcn/ui components & styles
β βββ env/ # Validated environment variables (zod)
β βββ config/ # Shared TypeScript config
βββ turbo.json # Turborepo task pipeline
βββ package.json # Workspace root scripts
| Command | Description |
|---|---|
bun run dev |
Start all applications in development mode |
bun run dev:web |
Start only the web app (port 3001) |
bun run build |
Build all applications |
bun run check-types |
Type-check all packages |
bun run db:start |
Start the PostgreSQL container |
bun run db:stop |
Stop the PostgreSQL container |
bun run db:down |
Stop and remove the PostgreSQL container |
bun run db:push |
Push schema changes to the database |
bun run db:generate |
Generate Drizzle migration files |
bun run db:migrate |
Apply generated migrations |
bun run db:studio |
Open Drizzle Studio in the browser |
bun run db:seed |
Seed demo data (db:seed <email> to set owner) |
- Fork the repository.
- Create a feature branch:
git checkout -b feat/my-feature - Make your changes and verify with
bun run check-types. - Open a pull request describing the change.
Keep changes small, preserve multi-tenant isolation, and never weaken authentication or authorization.