Stashly is a personal link memory system for saving pages, extracting page metadata, generating embeddings, and finding content by meaning instead of exact keywords. The repository is a monorepo with a NestJS backend, a Next.js web app, and a browser extension that shares the same backend API.
Stashly lets a signed-in user save a URL, fetches metadata from that page, stores the content in PostgreSQL, and creates a vector embedding so the link can later be found with semantic search. The web app supports email/password auth and Google sign-in. The browser extension mirrors the same core flows and can be used to save or search without opening the web dashboard.
The repository has evolved beyond the earlier Ollama-based design in some of the older docs. The current embedding service uses the Google Gemini embeddings API and requires a Gemini API key. PostgreSQL with pgvector is still the storage layer for vectors, and the application still uses local scraping, local scoring, and local persistence for the rest of the pipeline.
User authentication is handled by the NestJS auth module. Email/password accounts are stored in PostgreSQL with hashed passwords, while Google sign-in creates or reuses a user record and returns a JWT. Protected link routes use JWT guards, and the frontend stores the access token and user identity in browser local storage.
When a link is saved, the backend verifies the user, prevents duplicate URLs for the same user, scrapes the target page, stores the link, and then asynchronously generates an embedding. Search works by embedding the query, calculating vector similarity with pgvector, blending that with keyword relevance, and returning the best matches for the current user. Query enrichment exists for a few short abbreviations such as dsa, cp, ml, and os.
| Area | Stack | Purpose |
|---|---|---|
| Frontend | Next.js 16.2.2, React 19.2.3, TypeScript | User dashboard and auth flows |
| Backend | NestJS 10, Passport, JWT, class-validator, Prisma | REST API, auth, validation, persistence |
| Database | PostgreSQL 16, pgvector, Prisma | User and link storage with vector search |
| Embeddings | Google Gemini embeddings API | Create 768-dimensional vectors |
| Browser Extension | Manifest V3 extension | Alternate lightweight client |
| Containerization | Docker and Docker Compose | Local and production orchestration |
Users can create an account with email and password, sign in with existing credentials, or authenticate with Google. The web app stores the JWT, user ID, and email in local storage. The extension does the same through its popup flow and Google OAuth redirect handling.
The frontend or extension sends the URL to the backend. The backend checks that the user exists, prevents duplicates for the same user and URL, scrapes metadata from the page, stores the record, and queues embedding generation. The saved record includes the original URL, optional title and summary, extracted keywords, raw extracted text, and user association.
Search accepts a freeform query string. The service embeds the query, evaluates similarity against stored link embeddings, mixes in keyword relevance, applies result filtering, and returns the top matches for the active user.
Users can fetch all saved links, delete a single saved link, or delete every link in their own account. There is also an admin reprocess route that can refresh metadata and embeddings for a user’s saved links if the request satisfies the admin guard.
The repository uses environment variables rather than hard-coded secrets. The most important values are listed below.
| Variable | Used By | Purpose |
|---|---|---|
| DATABASE_URL | Backend and Prisma | PostgreSQL connection string |
| PORT | Backend | API listen port, defaults to 3000 |
| FRONTEND_ORIGIN | Backend | CORS allowlist origin for the web app |
| NEXT_PUBLIC_API_BASE_URL | Web app and extension | Base URL for API requests |
| JWT_SECRET | Auth module | Signs and verifies access tokens |
| GOOGLE_CLIENT_ID | Google strategy | OAuth client identifier |
| GOOGLE_CLIENT_SECRET | Google strategy | OAuth client secret |
| GOOGLE_CALLBACK_URL | Google strategy | OAuth callback URL |
| GEMINI_API_KEY | Embedding service | Generates embeddings with Gemini |
| GEMINI_EMBEDDING_MODEL | Embedding service | Optional primary model override |
| NODE_ENV | Docker and app config | Runtime mode |
The top-level development compose file also expects a PostgreSQL user, password, and database name of postgres, postgres, and stashly respectively.
The default local ports are 3000 for the backend, 3001 for the web app, 5432 for PostgreSQL, and 11434 for the Ollama container declared in development compose.
| Script | Purpose |
|---|---|
| start:dev | Runs backend and frontend together in parallel |
| start:backend | Starts the NestJS backend in watch mode |
| start:frontend | Starts the Next.js web app on port 3001 |
| build | Builds backend and web workspaces |
| lint | Lints backend and web workspaces |
| format | Formats backend and web workspaces |
| prisma:generate | Generates Prisma client code |
| prisma:migrate:dev | Creates and applies a development migration |
| prisma:migrate:deploy | Applies migrations in a production-style environment |
| test | Runs backend unit tests |
| test:e2e | Runs backend end-to-end tests |
The web app is a Next.js App Router application with three main pages: the home dashboard, the auth page, and the OAuth callback page. The dashboard provides save, search, view-all, and delete actions. The auth page handles login, registration, and Google sign-in. The callback page reads the token returned from Google auth, stores it, and returns the user to the dashboard.
The UI currently uses browser local storage for session persistence and redirects unauthenticated users back to the auth page.
The extension is a Manifest V3 popup that supports local credential sign-in, Google sign-in through the browser identity flow, saving the active page URL, semantic search, viewing all saved items, deleting items, and a simple theme toggle. By default it targets the deployed backend URL configured in popup.js and in the manifest host permissions.
If you self-host the backend, both the popup API base URL and the extension host permissions need to match the backend origin.
The development compose file brings up PostgreSQL with pgvector and an Ollama container. The database container initializes pgvector support through the sql script in docker/initdb.
The production compose file adds the backend and web services on top of PostgreSQL. It wires the backend to PostgreSQL, exposes the backend on port 3000, and exposes the web app on port 3001.
The repository contains several supporting documents beyond this README.
The repository metadata currently marks the project as UNLICENSED.