/
API documentation page
Cached repository-owned Markdown plus endpoint metadata; restart/watch to refresh.
Responses
-
200 HTML documentation.
A small REST service: GitHub commit search and a Mongoose-backed URL shortener.
/
Cached repository-owned Markdown plus endpoint metadata; restart/watch to refresh.
/api/
Returns this endpoint catalog. Shared API failures use { error, code, requestId } with an X-Request-ID header.
/health/live
Process is responding; does not probe optional providers.
/health/ready
Traffic is accepted only after database/index readiness. Readiness becomes false on draining or loss of DB connection.
/api/github/getCommits/:word
Public GitHub commit-message phrase search. The trimmed input is quoted; quotes, backslashes and control characters are rejected. No owner/repo restriction, credentials or cache.
| Name | Type | Required | Description |
|---|---|---|---|
| word | string | yes | Message phrase, 1-200 characters. |
| page (query) | number | no | Page 1-1000; default 1. Search is also subject to GitHub result limits. |
| per_page (query) | number | no | Page size 1-100; default 30. |
/api/github/getCommitsByRepoAndOwner/:owner/:repo
Public commit list for a validated owner/repository. Upstream pagination is explicit; native GitHub array shape is retained.
| Name | Type | Required | Description |
|---|---|---|---|
| owner | string | yes | Account/organization name, 1-39 characters. |
| repo | string | yes | Repository name, 1-100 letters, digits, dots, underscores or hyphens. |
| page (query) | number | no | Page 1-1000; default 1. Search is also subject to GitHub result limits. |
| per_page (query) | number | no | Page size 1-100; default 30. |
/api/url
Owner bearer authorization required. Ascending immutable _id order, projected fields and an opaque next cursor.
| Name | Type | Required | Description |
|---|---|---|---|
| limit (query) | number | no | 1-100; default 25. |
| after (query) | string | no | 24 lowercase hexadecimal characters; use pagination.next from the previous response. |
/api/url/create
Owner bearer authorization required. Global exact original-URL reuse without canonicalization. New numeric codes use a cryptographically random 47-bit allocation space, with unique indexes and five bounded attempts.
| Name | Type | Required | Description |
|---|---|---|---|
| original_url (body) | string | yes | Absolute HTTP(S) URL, at most 2048 characters, no embedded credentials, whitespace or control characters. |
/api/url/:short_url
Public lookup by issued numeric short code; no redirect and no list access.
| Name | Type | Required | Description |
|---|---|---|---|
| short_url | number | yes | Nonnegative safe integer: canonical digits or legacy four-digit zero padding. Existing issued codes remain supported. |
/api/url/delete/:short_url
Owner bearer authorization required. Deletes the stored mapping; a missing mapping returns 404.
| Name | Type | Required | Description |
|---|---|---|---|
| short_url | number | yes | Nonnegative safe integer: canonical digits or legacy four-digit zero padding. Existing issued codes remain supported. |
The Express API behind my site. Two small services that each solve a real problem I had, kept behind a shared middleware stack and covered by tests.
All endpoints below are mounted under /api/ (see app.js and api/routes/index.js). The JSON route catalog is served at GET /api/.
| Endpoint | Purpose |
|---|---|
GET /api/github/getCommits/:word |
Search public commit messages by phrase |
GET /api/github/getCommitsByRepoAndOwner/:owner/:repo |
Commits for a specific repository |
GET /api/url · POST /api/url/create · GET /api/url/:short_url · DELETE /api/url/delete/:short_url |
Mongoose-backed URL shortener |
The Google Gemini API has been retired. Legacy /api/gemini and /api/gemini/*
endpoints now respond with HTTP 410 Gone and { "error": "Google Gemini API has been retired." }.
The backend no longer calls Google, accepts image uploads, or stores chat sessions.
API requests use helmet, CORS, compression, and express-rate-limit, with a shared
error-handling middleware for errors that escape controllers. Controllers, services and models
stay in separate layers: controllers handle HTTP, services own the outbound calls, models own the data.
To get started with the project, clone the repository and install the dependencies:
git clone https://github.com/Khanos/khanos.backend.git
cd khanos.backend
npm ci --no-audit --no-fund
Use Node 24.x and npm 11.x. Copy .env.example to .env, supply a random owner
credential locally, and configure the intended MongoDB database. Never store the owner
credential in browser code, logs, chat, examples or Git. For example, generate random bytes
in a local terminal and put the result directly into your secret manager or untracked .env.
OWNER_API_TOKEN must have 32-256 non-whitespace ASCII characters; use at least 32 random bytes.
NODE_ENV is authoritative; ENV is an optional legacy alias that must agree when both are
present. Production uses DB_NAME; development uses TEST_DB_NAME. No fallback between them
is allowed. BIND_HOST is the actual bind address (default 0.0.0.0, preserving the former
wildcard listener); the old log-only HOST variable is retired. PORT defaults to 3000.
GitHub uses an HTTPS base URL ending in /, a total 5-second request deadline, and at most two
attempts for network failures or upstream 502/503/504. It does not retry rate limits, other
HTTP failures, bad JSON or cancellation. No GitHub credentials or cache are configured.
Before running against existing data, follow the separately authorized
read-only preflight and index migration. Startup awaits
MongoDB and verifies uniqueness indexes before accepting traffic; it never creates them.
Missing required indexes are reported as the safe startup code URL_INDEXES_MISSING.
The explicit operator command node scripts/url-index-migration.js is read-only by
default; --apply creates only missing approved indexes after another preflight.
It requires a separately authorized target, backup and quiesced writers as documented
in the migration procedure; startup never runs the command.
npm start
npm run dev # Node watch; restarting also refreshes cached documentation
app.js exports createApp without configuration loading, database or listener side effects.
server.js explicitly loads configuration and starts the lifecycle. TEST=true is only an
app-factory testing option: it disables the limiter but cannot start a server. Production
rejects test mode. Numeric configuration is parsed and range checked. Signals drain requests,
then await disconnect within SHUTDOWN_TIMEOUT_MS (default 10 seconds); failed bounded cleanup
logs a safe outcome and exits nonzero. HTTP headers/requests also have bounded timeouts.
Authorization. Public lookup
remains at GET /api/url/:short_url. No cookies, sessions, uploads or generation are enabled.__v is omitted. Existing numeric codes stay valid; new codes have a random 47-bit space.
Original URL reuse is global and exact, with no case/path/query normalization.0042 resolves code 42).
Other noncanonical formats remain invalid. URL responses, including authorization failures,
use Cache-Control: no-store and vary on Authorization.{ error: false, message: "URLs found", data: [...] } and adds
pagination: { limit, next }. Default limit=25, maximum 100; send after=<next> to continue.
Ordering uses immutable _id. This is live cursor pagination, not a frozen snapshot.q=repo/<word>.
Trimmed text is quoted as a phrase; quotes/backslashes/control characters are rejected.
Search keeps the GitHub { total_count, incomplete_results, items, ... } object; repository
commits keep the array shape. Both accept page (1-1000) and per_page (1-100, default 30).
Search is additionally subject to GitHub's own result limits. Clients request further pages
explicitly; no upstream pagination links/headers or private-repository authentication are
exposed. See GitHub commit search.{ error: <safe message>, code, requestId }: invalid input or JSON
is 400, missing records/routes 404, oversized bodies 413, unsupported encoding 415, rate limits
429, unexpected failure 500, DB unavailability 503, and classified GitHub failures 404/502/503/504.
The retired Gemini 410 shape is unchanged. Documentation errors use HTML with correct status.
These status corrections replace earlier 200/null and validation-as-500 behavior intentionally.Every request gets a generated X-Request-ID. Structured logs allow only request IDs, route
templates, methods, status/duration and safe dependency operation/outcomes. They exclude
raw URLs, search text, original URLs, headers, cookies, credentials and exception/provider bodies.
GET /health/live reports process liveness; GET /health/ready reports database/drain readiness.
Neither exposes configuration; neither includes optional GitHub availability. Health is exempt
from the global IP limiter (default 50 requests per 5 minutes). No metrics endpoint is exposed;
status/duration/dependency logs provide the initial operational evidence. Deployment alerting,
trusted proxy hops, TLS termination and restrictive CORS require actual ingress requirements.
khanos.frontend is the application consumer. Its public browser reads omit credentials;
its same-origin administration API authenticates the owner separately and forwards this
backend's bearer token from server secrets only. The backend continues to enforce its own
owner boundary, regardless of frontend login or CORS. Deploy both compatibility changes
after provisioning frontend secrets and completing the existing database/index readiness
procedure. No index, data or issued-code migration is added by the padding compatibility fix.
# MongoDB 8 must be installed, or MONGOD_BIN must identify a local mongod binary.
# Tests create their own process, loopback port and temporary directory.
npm test -- --runInBand
npm run test:integration
npm run lint
git diff --check
The database suite never reads .env, CONNECTION_URL, DB_NAME or TEST_DB_NAME and never
uses a configured production/test database. A missing mongod fails the suite instead of
silently skipping real constraints. It owns and removes only its disposable instance.
Tests use native ESM (Jest 29 needs Node's experimental VM modules flag, set by npm scripts),
real app/router contracts, synthetic credentials, mocked provider failures, and an isolated
HTTP upstream for native fetch timeout/status/encoding tests. Coverage thresholds remain 99%.
A focused no-coverage run uses TEST=true NODE_OPTIONS=--experimental-vm-modules with Jest;
npm run test:watch supports normal watch iteration.
CI targets verified default branch main and retains master compatibility, installs from
lockfile without audit metadata upload, checks declared engines, verifies a pinned MongoDB
binary checksum, and runs lint/full tests. No deployment or GitHub protection changes are made.
Passing this suite proves local contracts and disposable database invariants, not production
data cleanliness, ingress policy, provider quotas or hosted CI success. The implementation
status file records separately performed live public GitHub checks.
The project has the following structure:
Licensed under the GNU Lesser General Public License v3.0.