Skip to content

dropex7/snipurl

Repository files navigation

SnipURL

SnipURL is a NestJS backend for creating short links.

The project currently supports user authentication, creating short links, listing links owned by the current user, public redirects by short code, and click tracking.

Tech Stack

  • NestJS
  • TypeScript
  • PostgreSQL
  • Prisma
  • JWT access and refresh tokens
  • Passport JWT
  • bcryptjs
  • nanoid
  • Zod
  • Docker Compose for local Postgres and Redis

Redis is included in local infrastructure and dependencies, but is not used by the application yet.

Current Features

  • Register and login with email/password.
  • Return access tokens in JSON responses.
  • Store refresh tokens in httpOnly cookies and keep hashed refresh tokens in the database.
  • Refresh access tokens with the refresh cookie.
  • Logout by clearing the stored refresh token and refresh cookie.
  • Create short links for authenticated users.
  • Generate unique short codes with retry on unique constraint collision.
  • Set link expiration to one year from creation.
  • List links created by the authenticated user.
  • Public redirect by short code.
  • Track clicks with IP and user agent.
  • Return 404 for missing or inactive links.
  • Return 410 for expired links.

Project Structure

src/
  auth/        Authentication, JWT strategy, guards, auth endpoints
  common/      Shared decorators and types
  links/       Authenticated link creation and link listing
  prisma/      Prisma client provider
  redirect/    Public short-code redirect flow
prisma/
  schema.prisma
  migrations/

Environment Variables

Create a .env file in the project root:

DATABASE_URL="postgresql://postgres:postgres@localhost:5432/snipurl"
JWT_SECRET="replace-with-a-long-random-secret"
JWT_EXPIRES_IN="15m"
JWT_REFRESH_SECRET="replace-with-another-long-random-secret"
JWT_REFRESH_EXPIRES_IN="7d"
APP_URL="http://localhost:3000"
FRONTEND_URL="http://localhost:5173"

APP_URL is used to build returned short URLs, for example http://localhost:3000/abc1234.

Environment variables are validated when the application starts. Missing or invalid required values will stop the app before it accepts requests. Validation is implemented with Zod.

Local Setup

Install dependencies:

pnpm install

Start local infrastructure:

docker compose up -d

Run database migrations:

pnpm exec prisma migrate dev

Start the API in development mode:

pnpm run start:dev

The API runs on:

http://localhost:3000

Swagger documentation is available at:

http://localhost:3000/docs

Health check:

http://localhost:3000/health

Useful Scripts

pnpm run start:dev   # start Nest in watch mode
pnpm run build       # build the app
pnpm run lint        # run ESLint with fixes
pnpm run test        # run unit tests
pnpm run db:studio   # open Prisma Studio

API

Register

POST /auth/register
curl -X POST http://localhost:3000/auth/register \
  -H "Content-Type: application/json" \
  -c cookies.txt \
  -d '{
    "email": "example@example.com",
    "password": "example"
  }'

Response:

{
  "accessToken": "..."
}

The refresh token is stored in an httpOnly cookie.

Login

POST /auth/login
curl -X POST http://localhost:3000/auth/login \
  -H "Content-Type: application/json" \
  -c cookies.txt \
  -d '{
    "email": "example@example.com",
    "password": "example"
  }'

Refresh Tokens

POST /auth/refresh
curl -X POST http://localhost:3000/auth/refresh \
  -b cookies.txt \
  -c cookies.txt

In browser clients, send this request with credentials enabled so the refresh cookie is included.

Logout

POST /auth/logout
Authorization: Bearer <accessToken>
curl -X POST http://localhost:3000/auth/logout \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Create Short Link

POST /links
Authorization: Bearer <accessToken>
curl -X POST http://localhost:3000/links \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -d '{
    "originalUrl": "https://example.com/some/long/page"
  }'

Response:

{
  "id": "...",
  "shortUrl": "http://localhost:3000/abc1234"
}

List My Links

GET /links
Authorization: Bearer <accessToken>
curl -X GET http://localhost:3000/links \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Response:

[
  {
    "id": "...",
    "originalUrl": "https://example.com/some/long/page",
    "shortUrl": "http://localhost:3000/abc1234",
    "isActive": true,
    "expiresAt": "2027-05-02T19:00:00.000Z",
    "createdAt": "2026-05-02T19:00:00.000Z"
  }
]

Redirect

GET /:shortCode

Example:

curl -i http://localhost:3000/abc1234

Behavior:

  • redirects to the original URL when the link is active and not expired;
  • stores a click record;
  • returns 404 Not Found when the link does not exist or is inactive;
  • returns 410 Gone when the link is expired.

Data Model

The database has three main models:

  • User: account, password hash, hashed refresh token.
  • Link: original URL, unique short code, active flag, expiration date, owner.
  • Click: redirect event with IP, user agent, optional country, and timestamp.

Notes

  • country is not populated yet. It can be added later through GeoIP or platform/CDN headers after deployment.
  • Redis is not used yet. It can later support rate limiting, caching, queues, or analytics buffering.
  • Tests are not implemented yet.
  • Planned deployment target: Fly.io for the API, Neon for Postgres, Upstash for Redis, and Vercel for the frontend.

About

SnipURL is a NestJS backend for creating short links.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors