Express CSV Logo

Quickstart

Use this pre-built prompt to get started faster with Svelte.

Alternatively, follow these steps to manually add ExpressCSV to a Svelte app and receive imports in your existing backend.

  1. Install the SDK

    npm install @expresscsv/sdk
  2. Backend: create a session endpoint 🔒

    Keep your ExpressCSV secret key on the backend, call the session-creation endpoint, and return only the short-lived importer session token to the frontend.

    For example, in a Koa backend:

    import Koa from "koa";
    import Router from "@koa/router";
    
    const app = new Koa();
    const router = new Router();
    
    router.post("/api/expresscsv/session", async (ctx) => {
      const response = await fetch("https://api.expresscsv.com/v1/importer/sessions", {
        method: "POST",
        headers: {
          Authorization: `Bearer ${process.env.EXPRESSCSV_SECRET_KEY}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          metadata: {
            source: "products-page",
          },
        }),
      });
    
      const { token, expiresAt } = await response.json();
    
      ctx.body = { token, expiresAt };
    });
    
    app.use(router.routes());
    app.use(router.allowedMethods());
    app.listen(4000);
  3. Frontend: configure and open the importer

    <script lang="ts">
      import { CSVImporter, x } from "@expresscsv/sdk";
    
      let isOpen = false;
    
      const productSchema = x.row({
        sku: x
          .string()
          .label("SKU")
          .refine(/^[A-Z]{3}-\d{4}$/, { message: "Format must be ABC-1234" }),
        name: x.string().min(1).label("Product Name"),
        price: x.number().min(0).currency("USD").label("Price"),
        categories: x
          .multiselect([
            { label: "Apparel", value: "apparel" },
            { label: "Electronics", value: "electronics" },
            { label: "Home Goods", value: "home-goods" },
          ])
          .min(1)
          .label("Category"),
      });
    
      const importer = new CSVImporter({
        schema: productSchema,
        getSessionToken: async () => {
          const response = await fetch("/api/expresscsv/session", {
            method: "POST",
          });
          const { token } = await response.json();
          return token;
        },
        importIdentifier: "product-import",
        title: "Import products",
      });
    
      function openImporter() {
        isOpen = true;
    
        importer.open({
          chunkSize: 500,
          webhook: {
            url: "http://localhost:4000/webhooks/products-import",
            headers: {
              Authorization: "Bearer your-api-token",
            },
            metadata: {
              userId: "user-123",
            },
          },
          onComplete: () => {
            isOpen = false;
          },
          onCancel: () => {
            isOpen = false;
          },
          onError: (error) => {
            isOpen = false;
            console.error("Import error", error);
          },
        });
      }
    </script>
    
    <button type="button" disabled={isOpen} on:click={openImporter}>
      Import Products
    </button>

    This code runs in your Svelte app. The getSessionToken callback calls the backend session endpoint we made earlier right before opening the importer.

  4. Render the button in your app

    <script lang="ts">
      import ImportProductsButton from "./lib/ImportProductsButton.svelte";
    </script>
    
    <ImportProductsButton />
  5. Backend: receive webhook 📦

    Here's an example Koa backend. In a real app, keep your schema in a shared or core package, import it into both the frontend and your backend, and use @expresscsv/schemas to infer the webhook payload type:

    npm install @expresscsv/schemas
    import Koa from "koa";
    import Router from "@koa/router";
    import type { InferWebhookPayload } from "@expresscsv/schemas";
    import { productSchema } from "../shared/product-schema";
    
    const app = new Koa();
    const router = new Router();
    
    router.post("/webhooks/products-import", async (ctx) => {
      const payload = ctx.request.body as InferWebhookPayload<typeof productSchema>;
      const { records, metadata, delivery, chunkIndex, totalChunks } = payload;
    
      // Persist this chunk of validated records in your database.
      await db.products.insertMany({
        userId: metadata.userId,
        products: records,
      });
    
      ctx.body = { ok: true };
    });
    
    app.use(router.routes());
    app.use(router.allowedMethods());
    app.listen(4000);

    Return a 2xx response for each chunk. 5xx and 429 responses are retried automatically, while other 4xx responses are treated as permanent failures.

What's Next

  • Types — define field types, validation rules, and TypeScript types
  • Styling — theme the importer to match your app
  • Webhooks — deliver records to your backend
  • API Reference — full constructor, open(), and lifecycle reference