Other Frameworks
Svelte / SvelteKit
Integrate Nexa into a SvelteKit app using server routes and Svelte stores.
SvelteKit's server routes (in src/routes/+server.ts) are the right place to call the Nexa API — the API key stays server-side and your Svelte components call your own routes.
Architecture
Browser (Svelte)
↓ fetch /api/nexa/...
SvelteKit server route (+server.ts)
↓ NexaClient
Nexa platform (nexa-ed.com)Install
pnpm add @nexa-ed/nodeServer routes
Submit a file
// src/routes/api/nexa/process/+server.ts
import { json } from "@sveltejs/kit";
import { NexaClient } from "@nexa-ed/node";
import { NEXA_API_KEY, NEXA_WEBHOOK_SECRET } from "$env/static/private";
const nexa = new NexaClient({
apiKey: NEXA_API_KEY,
webhookSecret: NEXA_WEBHOOK_SECRET,
});
export async function POST({ request }) {
try {
const body = await request.json();
if (!body?.fileUrl || !body?.fileId) {
return json({ error: "fileUrl and fileId are required" }, { status: 400 });
}
const result = await nexa.files.submit({ fileUrl: body.fileUrl, fileId: body.fileId });
return json(result);
} catch (err) {
return json({ error: "Failed to submit file for processing" }, { status: 500 });
}
}SSE progress proxy
// src/routes/api/nexa/progress/[fileId]/+server.ts
import { NEXA_API_KEY } from "$env/static/private";
import { proxyProgressStream } from "@nexa-ed/node";
export async function GET({ params }) {
try {
const stream = await proxyProgressStream(params.fileId, {
apiKey: NEXA_API_KEY,
});
return new Response(stream, {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"X-Accel-Buffering": "no",
},
});
} catch (err) {
return new Response(JSON.stringify({ error: "Failed to open progress stream" }), {
status: 502,
headers: { "Content-Type": "application/json" },
});
}
}Webhook endpoint
// src/routes/webhooks/nexa/+server.ts
import { NEXA_WEBHOOK_SECRET } from "$env/static/private";
import { verifyWebhookPayload } from "@nexa-ed/node";
export async function POST({ request }) {
try {
const rawBody = await request.text();
const signature = request.headers.get("x-nexa-signature");
const timestamp = request.headers.get("x-nexa-timestamp");
const event = await verifyWebhookPayload(
rawBody,
signature,
timestamp,
NEXA_WEBHOOK_SECRET,
);
if (event.event === "file.completed") {
// sync to your database here
console.log("File processed:", event.fileId);
}
return new Response(JSON.stringify({ received: true }), {
headers: { "Content-Type": "application/json" },
});
} catch (err) {
const message = err instanceof Error ? err.message : "Webhook error";
return new Response(JSON.stringify({ error: message }), {
status: 400,
headers: { "Content-Type": "application/json" },
});
}
}Svelte stores
File progress store
// src/lib/stores/fileProgress.ts
import { writable } from "svelte/store";
export interface ProgressState {
stage: string;
progress: number;
error: string | null;
}
export function createProgressStore(fileId: string) {
const { subscribe, set, update } = writable<ProgressState>({
stage: "idle",
progress: 0,
error: null,
});
const es = new EventSource(`/api/nexa/progress/${fileId}`);
es.onmessage = (e) => {
try {
const data = JSON.parse(e.data);
update((s) => ({ ...s, stage: data.stage, progress: data.progress }));
if (data.stage === "complete" || data.stage === "failed") {
if (data.stage === "failed") update((s) => ({ ...s, error: data.error }));
es.close();
}
} catch {
update((s) => ({ ...s, error: "Received malformed event" }));
es.close();
}
};
es.onerror = () => {
update((s) => ({ ...s, error: "Connection lost" }));
es.close();
};
function destroy() { es.close(); }
return { subscribe, destroy };
}Svelte component example
<!-- src/lib/components/FileProcessor.svelte -->
<script lang="ts">
import { onDestroy } from "svelte";
import { createProgressStore } from "$lib/stores/fileProgress";
let fileId: string | null = null;
let progressStore: ReturnType<typeof createProgressStore> | null = null;
let state = { stage: "idle", progress: 0, error: null as string | null };
let unsubscribe: (() => void) | null = null;
async function onFile(e: Event) {
const file = (e.target as HTMLInputElement).files?.[0];
if (!file) return;
try {
// 1. Upload file to your storage
const formData = new FormData();
formData.append("file", file);
const upload = await fetch("/api/upload", { method: "POST", body: formData });
if (!upload.ok) throw new Error(`Upload failed (${upload.status})`);
const { fileUrl, id } = await upload.json();
fileId = id;
// 2. Submit to Nexa
const submit = await fetch("/api/nexa/process", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ fileUrl, fileId }),
});
if (!submit.ok) throw new Error(`Submit failed (${submit.status})`);
// 3. Stream progress — unsubscribe the previous store first
unsubscribe?.();
progressStore = createProgressStore(id);
unsubscribe = progressStore.subscribe((s) => (state = s));
} catch (err) {
state = { stage: "error", progress: 0, error: (err as Error).message };
}
}
onDestroy(() => {
unsubscribe?.();
progressStore?.destroy();
});
</script>
<input type="file" accept=".pdf" on:change={onFile} />
{#if fileId}
<p>{state.stage}: {state.progress}%</p>
<progress value={state.progress} max={100} />
{#if state.error}<p class="error">{state.error}</p>{/if}
{/if}Environment variables
SvelteKit reads private env vars from $env/static/private. Add to your .env:
NEXA_API_KEY=nxa_live_...
NEXA_WEBHOOK_SECRET=nxa_live_... # same value as NEXA_API_KEY — Nexa signs webhooks with your API key