Quickstart
Build a complete SurveyJS response flow in Next.js.
This guide creates the smallest complete flow: publish a SurveyJS document, start a response, render its frozen definition, save page progress, and submit it.
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.
Install
npm i @dimah-survey/server @dimah-survey/react survey-core survey-react-uisurvey-react-ui renders questions. dimah-survey owns only the lifecycle
around the SurveyJS document.
Create the server
Create one instance for respondents and one for trusted editor operations. They must share the same store.
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.
Mount both audiences
import { toNextJsHandler } from "@dimah-survey/server/next";
import { fill } from "@/lib/survey";
export const { GET, POST, PUT, PATCH, DELETE } = toNextJsHandler(fill);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 for
other runtimes.
Publish a SurveyJS document
Use the in-process editor API from server code. Saving changes draftJson;
publishing explicitly copies that draft to publishedJson.
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.
Create the browser client
"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 }.
Render the response snapshot
"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.
Start the response
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.
Make it production-ready
- Replace
memoryAdapter()with the SQL store or your own durableSurveyStore. See Persistence. - Read the respondent from your session and protect the editor audience. See Security.
- Add Creator autosave without coupling it to publish. See Survey Creator.