# Quickstart (https://survey.dimah.dev/docs/quickstart)



This guide creates the smallest complete flow: publish a SurveyJS document,
start a response, render its frozen definition, save page progress, and submit
it.

<Callout type="warn">
  This quickstart uses process-local memory and a fixed respondent. Before
  production, use a durable store, derive identity from the session, and guard
  the editor route.
</Callout>

<Steps>
  <Step>
    ### Install [#install]

    <CodeBlockTabs defaultValue="npm">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="npm">
          npm
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="pnpm">
          pnpm
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="yarn">
          yarn
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="bun">
          bun
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="npm">
        ```bash
        npm i @dimah-survey/server @dimah-survey/react survey-core survey-react-ui
        ```
      </CodeBlockTab>

      <CodeBlockTab value="pnpm">
        ```bash
        pnpm add @dimah-survey/server @dimah-survey/react survey-core survey-react-ui
        ```
      </CodeBlockTab>

      <CodeBlockTab value="yarn">
        ```bash
        yarn add @dimah-survey/server @dimah-survey/react survey-core survey-react-ui
        ```
      </CodeBlockTab>

      <CodeBlockTab value="bun">
        ```bash
        bun add @dimah-survey/server @dimah-survey/react survey-core survey-react-ui
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    `survey-react-ui` renders questions. dimah-survey owns only the lifecycle
    around the SurveyJS document.
  </Step>

  <Step>
    ### Create the server [#create-the-server]

    Create one instance for respondents and one for trusted editor operations. They
    must share the same store.

    ```ts title="lib/survey.ts"
    import {
      SURVEY_API_BASE_PATH,
      SURVEY_EDITOR_API_BASE_PATH,
      dimahSurvey,
      guardRespondent,
      memoryAdapter,
    } from "@dimah-survey/server";

    const database = memoryAdapter();

    export const editor = dimahSurvey({
      audience: "editor",
      database,
      basePath: SURVEY_EDITOR_API_BASE_PATH,
    });

    export const fill = dimahSurvey({
      audience: "fill",
      database,
      basePath: SURVEY_API_BASE_PATH,
      guard: guardRespondent("demo"),
    });
    ```

    The fill guard stamps `"demo"` onto every response operation. With the default
    `"one-open"` policy, another start resumes the open draft instead of creating a
    second one.
  </Step>

  <Step>
    ### Mount both audiences [#mount-both-audiences]

    ```ts title="app/api/survey/[[...path]]/route.ts"
    import { toNextJsHandler } from "@dimah-survey/server/next";
    import { fill } from "@/lib/survey";

    export const { GET, POST, PUT, PATCH, DELETE } = toNextJsHandler(fill);
    ```

    ```ts title="app/api/admin/survey/[[...path]]/route.ts"
    import { toNextJsHandler } from "@dimah-survey/server/next";
    import { editor } from "@/lib/survey";

    export const { GET, POST, PUT, PATCH, DELETE } = toNextJsHandler(editor);
    ```

    The paths match the default base paths: `/api/survey` for fill and
    `/api/admin/survey` for editor. See [Mount the server](https://survey.dimah.dev/docs/integration.md) for
    other runtimes.
  </Step>

  <Step>
    ### Publish a SurveyJS document [#publish-a-surveyjs-document]

    Use the in-process editor API from server code. Saving changes `draftJson`;
    publishing explicitly copies that draft to `publishedJson`.

    ```ts title="lib/welcome.ts"
    import { isAPIError } from "@dimah-survey/server";
    import { editor } from "@/lib/survey";

    const definition = {
      title: "Welcome",
      pages: [
        {
          name: "welcome",
          elements: [
            { type: "text", name: "name", title: "Your name", isRequired: true },
            {
              type: "rating",
              name: "score",
              title: "How was it?",
              isRequired: true,
            },
          ],
        },
      ],
    };

    export async function ensureWelcomeSurvey() {
      try {
        await editor.api.getSurvey({ query: { id: "welcome" } });
      } catch (error) {
        if (!isAPIError(error) || error.code !== "SURVEY_NOT_FOUND") throw error;

        await editor.api.saveSurvey({
          body: { id: "welcome", slug: "welcome", draftJson: definition },
        });
        await editor.api.publishSurvey({ body: { id: "welcome" } });
      }
    }
    ```

    `getSurvey` throws `SURVEY_NOT_FOUND` when the row is missing; it does not
    return `null`.
  </Step>

  <Step>
    ### Create the browser client [#create-the-browser-client]

    ```ts title="lib/fill-client.ts"
    "use client";

    import { createFillClient } from "@dimah-survey/react";

    export const fillClient = createFillClient({
      baseURL: "/api/survey",
    });
    ```

    Browser clients take flat input objects. In-process APIs use `{ body }` or
    `{ query }`.
  </Step>

  <Step>
    ### Render the response snapshot [#render-the-response-snapshot]

    ```tsx title="components/fill.tsx"
    "use client";

    import dynamic from "next/dynamic";
    import { useSurveyResponse } from "@dimah-survey/react";
    import { fillClient } from "@/lib/fill-client";

    import "survey-core/survey-core.min.css";

    const Survey = dynamic(
      () => import("survey-react-ui").then((mod) => mod.Survey),
      { ssr: false },
    );

    export function Fill({ responseId }: { responseId: string }) {
      const { model, error, saveError, stale, reload } = useSurveyResponse({
        client: fillClient,
        responseId,
      });

      if (error) return <p>{error.message}</p>;
      if (!model) return null;

      return (
        <>
          {saveError ? <p>{saveError.message}</p> : null}
          {stale ? <button onClick={reload}>Reload</button> : null}
          <Survey model={model} />
        </>
      );
    }
    ```

    The hook loads `response.definition`, constructs a `Model`, and binds partial
    save and submit. It never renders UI. `partial` defaults to `"page"`, so moving
    to the next page persists the complete `survey.data` object.
  </Step>

  <Step>
    ### Start the response [#start-the-response]

    ```tsx title="app/page.tsx"
    import { Fill } from "@/components/fill";
    import { fill } from "@/lib/survey";
    import { ensureWelcomeSurvey } from "@/lib/welcome";

    export default async function Page() {
      await ensureWelcomeSurvey();
      const response = await fill.api.startResponse({
        body: { surveyId: "welcome" },
      });

      return <Fill responseId={response.id} />;
    }
    ```

    The first request creates a draft whose definition is a copy of the published
    document. Refreshing resumes that draft. A later publish cannot rewrite it.
  </Step>
</Steps>

## Make it production-ready [#make-it-production-ready]

* Replace `memoryAdapter()` with the SQL store or your own durable
  `SurveyStore`. See [Persistence](https://survey.dimah.dev/docs/persistence.md).
* Read the respondent from your session and protect the editor audience. See
  [Security](https://survey.dimah.dev/docs/security.md).
* Add Creator autosave without coupling it to publish. See
  [Survey Creator](https://survey.dimah.dev/docs/creator.md).
