Browser & server
@qratilabs/qrati-sdk has no separate /server or /browser entry — one package,
one createQrati(), works unmodified wherever it runs. It detects its
environment at the one point that actually differs (the S3 PUT transport) and
otherwise doesn’t care.
In the browser
Section titled “In the browser”import { createQrati } from '@qratilabs/qrati-sdk';
const qrati = createQrati(API_KEY, { uid, fname, lname });
await qrati.upload({ eventId, file, // from <input type="file"> or a drop zone onProgress: ({ percent }) => setProgress(percent),});The PUT to S3 goes out via XMLHttpRequest, so onProgress fires real,
byte-level upload.onprogress events. Requests carry an Origin header (the
browser adds it automatically) — the platform checks that origin against your
organization’s domain allowlist, same as qrati-connect-ts. See
API key security.
On the server (Node)
Section titled “On the server (Node)”import { createQrati } from '@qratilabs/qrati-sdk';import { readFile } from 'node:fs/promises';
const qrati = createQrati(process.env.QRATI_API_KEY!);
const buffer = await readFile('./photo.jpg');const file = new File([buffer], 'photo.jpg', { type: 'image/jpeg' });
await qrati.upload({ eventId, file, identity: { uid } });Identical call shape — createQrati, upload(), the same QratiIdentity.
Two practical differences:
- Progress: Node’s
fetch(undici) doesn’t expose upload progress, so the SDK falls back to a streaming PUT and callsonProgressonce, at 100%, when it finishes — not a series of incremental events like in the browser. - No domain check: a server-to-server call has no browser
Originheader, so the domain allowlist (a browser-only concept — there’s no “origin” for a backend process) doesn’t apply. A request authenticated with a valid API key and noOriginheader is treated as your own backend calling in, not a browser page to vet.
One client instance can safely serve many end-users this way — pass
identity per call instead of binding one at construction:
const qrati = createQrati(process.env.QRATI_API_KEY!);
export async function uploadOnBehalfOf(uid: string, eventId: string, file: File) { return qrati.upload({ eventId, file, identity: { uid } });}Requires Node 20+ (for global fetch, File, and Blob) — matches the
SDK’s engines.node requirement.
Same package either way
Section titled “Same package either way”There’s nothing to configure and no build-time branch to pick — bundlers
(Vite, webpack, Next.js) ship the browser path to the client and Node
resolves the same module server-side; createQrati() picks the right PUT
transport at call time by checking whether XMLHttpRequest exists.