APIs and backends

Short-link API with per-country click stats

A TypeScript Fastify API that shortens URLs, redirects with 302, and counts clicks by country code behind an API key.

typescriptfastifysqlitezodvitestnodejs

Built 1 time, downloaded 0 times.

Install from your terminal

Run this in your project folder. It signs you in, shows what changes and asks before it overwrites a file.

npx claude-lab add short-link-api-with-per-country-click-stats-fbd8df
CLAUDE.md
# the app

## Purpose

This project is a URL shortener API written in TypeScript. It creates short codes for links, redirects visitors with HTTP 302, and counts clicks per ISO country code. It never stores IP addresses.

## Stack

- Node 22 with TypeScript in strict mode
- Fastify for the HTTP server
- better-sqlite3 for storage
- Zod for input validation
- Vitest for tests
- ESLint and Prettier for style

## Commands

- `npm run dev`: start the server in watch mode
- `npm test`: run the full Vitest suite
- `npm run typecheck`: run TypeScript without emitting files
- `npm run lint`: run ESLint
- `npm run build`: compile to `dist/`
- `npm run smoke`: start the server and run the end-to-end check

Run the `verify` skill before saying any work is done.

Use the `test-runner` subagent for the full test suite so long output stays out of the main session.

## Layout

- `src/app.ts` builds the Fastify instance and registers routes.
- `src/server.ts` starts the app on `PORT` (default 3000).
- `src/routes/` holds one file per resource. Keep handlers thin.
- `src/db.ts` is the only file that touches SQLite.
- `src/geo.ts` wraps the GeoIP lookup behind a `GeoResolver` interface.
- `test/` mirrors `src/`. Route tests live in `test/routes/`.
- `SPEC.md` defines what done means. `PLAN.md` lists the build phases.
- `REVIEW.md` holds review rules. Imported below.

@REVIEW.md

## Rules

- Use parameterized SQL only. Never build SQL with string concatenation.
- Never store, log, or return IP addresses. Store only the two-letter country code, or `ZZ` when unknown.
- Never commit `.env`. Secrets come from environment variables named in `.env.example`.
- Never use `any`. Never add unused exports.
- Keep functions under 40 lines.
- Do not weaken or delete a failing test to make a check pass. Fix the cause.
- Ask before adding any dependency, changing the database schema, or changing a public endpoint path.
- Do not commit unless the user asks. Phase ends use the `commit-commands` plugin.

## Responses

- All responses are JSON.
- Errors use the shape `{error: string, message: string}` with the correct HTTP status code.
- Unknown codes return 404. Duplicate aliases return 409. Missing or wrong API keys return 401.

## Non-default conventions

- Redirects use HTTP 302, not 301. A 301 would be cached by browsers, so later visits would skip the server and never be counted.
- GeoIP failures must never block a redirect. Log the failure without any IP address.
- The API key is compared in constant time.

## Scope

Out of scope for now: user accounts, a web UI, rate limiting, and custom domains. Do not add them without asking.

## Working style

- Read `SPEC.md` and the relevant phase in `PLAN.md` before writing code.
- Make the smallest change that meets the spec.
- After each change, run the checks, read the output, and report the exit codes.
Use for my project