TypeScript rewrite of Classifast, originally built with Python/FastAPI.
Classifast is a web application that provides easy classification of any text input according to international product and service standards like UNSPSC, NAICS, ISIC, ETIM, HS, CN. Built with NestJS and server-rendered React, it offers fast, accurate semantic search capabilities for automated yet intelligent categorization.
- Fast Classification: Semantic search using advanced embedding models
- High Accuracy: Confidence scores for each classification result
- Multiple Standards: Support for UNSPSC, ETIM, and NAICS classification standards
- Modern Interface: Clean, responsive design built with Tailwind CSS
- SEO Optimized: Structured data, meta tags, and performance optimized
- Global standard for product and service categorization
- Improves spend analytics and procurement processes
- Version: UNv260801 (August 14, 2023)
- B2B open standard for technical product classification
- Specialized for electrical and technical products
- Version: 10.0 (2024-12-10)
- Industry classification for business activities
- Essential for government contracting and reporting
- Backend: NestJS (TypeScript) with server-rendered React pages
- Frontend: TypeScript, Tailwind CSS, HTMX
- Vector Database: Qdrant for semantic search
- Embedding Models: Hugging Face Inference with Qwen embeddings
- Deployment: Docker containerized
Visit the working instance at classifast.com to try.
- Clone the repository
- Install dependencies:
npm ci - Set environment variables for embedding inference, API keys, and payments/quota behavior
- Run the frontend watcher and the NestJS app:
npm run dev
The offline Python scripts in utilities/ need their own virtualenv with
utilities/requirements.txt.
Relevant embedding environment variables:
HF_TOKENHF_INFERENCE_PROVIDERdefaults toautoHF_EMBEDDING_MODELdefaults toQwen/Qwen3-Embedding-8BHF_EMBEDDING_DIMSdefaults to2048
Reranking is served by OpenRouter and uses its own API key and model:
OPENROUTER_API_KEYOPENROUTER_RERANK_MODELdefaults tovoyageai/rerank-3OPENROUTER_RERANK_TIMEOUT_SECONDSdefaults to30
Embedding inference uses the configured HF_INFERENCE_PROVIDER. Reranking is
sent to OpenRouter's /api/v1/rerank endpoint with the OPENROUTER_API_KEY.
The classifier page also has an off-by-default Expand query beta switch.
When enabled, google/gemini-3.1-flash-lite adds a short description after the
original query for embedding and reranking. The beta model inputs put each
classification instruction after that semantic text. Exact ID matches skip the
model call. Code-like queries also skip it after an exact miss. The original
text stays in the input, result heading, URL, and ID lookup. If OpenRouter is
unavailable or the API key is absent, classification uses the original text and
the ordinary instruction-first format. Provider failures are not cached. Shared
beta searches use ?enhance_query=1 in the URL.
Relevant Qdrant environment variables:
QDRANT_URLis preferred when set. Full HTTP(S) URLs are preserved; a bare hostname is treated as HTTPS.QDRANT_HOSTandQDRANT_PORT(defaultlocalhost:6333) are the fallback whenQDRANT_URLis unset.QDRANT_API_KEYis optional for unprotected local Qdrant deployments.
Relevant payment and quota environment variables:
POLAR_ACCESS_TOKENPOLAR_WEBHOOK_SECRETPOLAR_PRO_PRODUCT_IDCHECKOUT_GRACE_TTLdefaults to300
Frontend JS/CSS files under app/static/ are build artifacts. They are generated from app/assets/ and are intentionally not committed to git.
For production, deploy from the repo root with the provided Dockerfile. It
builds the NestJS app on node:24-bookworm-slim. Builder stages compile the
frontend assets and the server, and the runtime stage receives only dist/,
app/static/, and the production node_modules. The container runs
node dist/main.js as the node user with NODE_ENV=production. Its
HEALTHCHECK requests /health every 10 minutes.
For Coolify, use these settings:
- Source directory: repo root
- Build pack: Dockerfile
- Dockerfile path:
./Dockerfile - Include Source Commit in Build: disabled
The image contains no .env file, so set every variable in Coolify. The app
reads the same variable names as the Python app it replaced, with these
differences:
- The app listens on
HOSTandPORT, which default to0.0.0.0and8001. The Python image hard-coded both and ignored the variables. Leave them unset, or keepPORTequal to the port that Coolify exposes. TheHEALTHCHECKreads the same two variables. - The app does not start when Qdrant is unreachable or a collection fails the
schema check. Without
HF_TOKENit starts, but/healthanswers 503 and the container becomes unhealthy, as with the Python image. - Google crawler verification uses the first
X-Forwarded-Foraddress, as the Python app did through uvicorn's--forwarded-allow-ips "*". SetGOOGLE_CRAWLER_TRUST_CF_CONNECTING_IP=trueto use Cloudflare'sCF-Connecting-IPinstead, which a client cannot forge through Cloudflare. Set it only when the origin receives requests through Cloudflare alone. - The app does not log one line per request, as uvicorn did. It logs startup, warnings, errors, and classification steps.
- The app accepts request headers up to 64 KiB (
--max-http-header-sizein theCMD). Node's default of 16 KiB was below uvicorn's limit.
docker-compose.yaml runs the same image for local or manual container runs.
It pins HOST and PORT, because its port mapping and health check expect
port 8001. The recommended Coolify production path is the Dockerfile build directly.
To roll back, open the application's Rollback tab in Coolify and redeploy an earlier image. The repository no longer contains the Python app; its last version is in the git history before the commit that deleted it.
Application startup validates every configured Qdrant collection, vector size, and required payload index without modifying Qdrant. Startup fails when the schema contract is invalid. Prepare and verify Qdrant explicitly before a deployment:
npm ci
npm run qdrant:indexes -- apply
npm run qdrant:indexes -- checkqdrant:indexes builds the Nest project and runs the TypeScript CLI in
src/cli/sync-payload-indexes.ts. Add --collection NAME (repeatable) to
limit either command to configured collections. apply is a live migration
operation: it backfills normalized ID payloads and creates or replaces payload
indexes. check is read-only. Run apply from a controlled maintenance
environment, verify the resolved Qdrant target before confirming the
operation, then deploy only after check succeeds. The CLI loads the
repository .env; values already exported by the shell or supplied by the
container take precedence. It exits 0 on success, 1 on a schema violation or a
failed Qdrant operation, and 2 on invalid arguments.
Each application process runs one classification at a time and admits up to
four waiting classifications. When the queue is full, fragment and RapidAPI
requests receive HTTP 503. Fragment callers already over quota receive the
paywall instead, without taking a queue slot. Server-rendered pages still return
200 to visitors and load results in the browser, while verified Google crawlers
receive HTTP 503 with Retry-After. An active classification retains its turn
through query enhancement and every pipeline stage. This bounds queued work
and keeps health checks, webhooks, and cached-page handling responsive.
GET /HomepageGET /{classifier_type}Classification page (unspsc, etim, naics)GET /{classifier_type}/fragmentClassification results fragmentGET /healthHealth check endpoint
- Run the unit tests (frontend and NestJS):
npm test - Run the NestJS end-to-end tests:
npm run test:e2e - Run the typecheck:
npm run typecheck - Run the HTTP contract suite against a public-mode instance:
npm run test:contract:nest(needsnpm run buildandnpm run build:serverfirst)
utilities/test_*.py scripts are manual smoke/debug helpers for live integrations and are intentionally separate from the main regression suite.
- Structured data markup (JSON-LD)
- FAQ schema for common questions
- Optimized meta descriptions and titles
- Breadcrumb navigation
- Semantic HTML structure
- Performance optimized loading