share-links
share-links is a small HTTP service with two jobs. It turns the state a browser app keeps in its URL fragment into a short link, and it stores images that the same app uploads, each with its own delete token. It is one Node process with no dependencies outside the standard library: node:http, node:sqlite, node:zlib and node:crypto.
Source: github.com/Aquilabot/share-links (MIT).
What kind of project this is
Section titled “What kind of project this is”It is a self-hosted service, not an npm library. package.json is marked "private": true, has version 1.0.0 and declares no dependencies. You clone the repository and run src/main.mjs with Node 24 or later, or build the container image from the Dockerfile with Docker Compose. Nothing is published to a package registry.
The README describes its provenance this way: extracted from the share service of a private hobby project and rewritten as a standalone service.
The problem it addresses
Section titled “The problem it addresses”An app that keeps its whole state in the URL fragment can share that state by sharing the URL, but the URL grows with the state. share-links stores the fragment once and returns a ten-character id. Opening /s/<id> sends the browser back to the app with the fragment restored. The app can also upload PNG, JPEG or WebP images, reference them by URL, and delete one later with the token returned at upload.
The service is meant to run on the same origin as the app. A reverse proxy routes /api/links, /api/images, /api/health and /s/ of the app’s origin to it. It sends no CORS headers, and it accepts POST and DELETE from a browser only when Origin and Sec-Fetch-Site say the request comes from that origin.
How a short link works
Section titled “How a short link works”-
The app gzips a JSON object (for example with
CompressionStream('gzip')), encodes it as base64url, prepends#state=v1.and POSTs the result astext/plainto/api/links. -
The service checks the prefix, the canonical base64url spelling, a single gzip member and a JSON object. It stores the payload in
links.sqliteunder a random id and answers201withidandurl. The same payload posted again answers200with the same id. -
Someone opens
/s/<id>. The service answers302toPUBLIC_ORIGIN+LINK_TARGET_PATH+ the payload. The first opening clears the link’s expiry, so an opened link stays. -
A link nobody opens within 30 days (
UNOPENED_TTL_MS) expires. An hourly sweep deletes expired rows.
How an image upload works
Section titled “How an image upload works”-
The app POSTs the raw image bytes to
/api/imageswithContent-Typeset toimage/png,image/jpegorimage/webp. -
The service reads the type and dimensions from the file’s own header bytes. The declared type must match, each side must be 16 to 2048 pixels, and the body must be at most 1.5 MiB.
-
A new image is stored in
images.sqliteand the answer is201withid,urland a randomtoken. Only the SHA-256 of the token is kept. The same bytes uploaded again answer200with the existing id and no token. -
DELETE /api/images/<id>with the headerX-Delete-Tokenremoves it. A wrong token and an unknown id get the same404.
What it does not do
Section titled “What it does not do”- It does not terminate TLS. The reverse proxy does.
- It does not authenticate uploaders. Abuse is bounded by per-client and global rate limits, a shared memory budget for request bodies, and count and byte caps on each store.
- It does not scan for malware and never decodes pixels. A file with a valid header and broken image data is stored as is. Re-encoding in the browser, which also drops EXIF metadata, is the client’s job.
- It does not serve other origins. There is no CORS, and a preflight gets
405. - It does not list, edit or expire images. They stay until their token is used or an operator purges them with
src/admin.mjs. - It does not defend against an attacker on the host. The threat model trusts the proxy, the operator and the host.
Where to go next
Section titled “Where to go next”Written from the code and tests of each repository. If a page and the code disagree, the code is right and the page has a bug. Open an issue.
© 2026 Jean Carlo Benites Porras. Code samples are MIT, like the projects they come from.