dimah-survey
Build the backend

Mount the server

Create isolated fill and editor handlers, then mount them in your runtime.

dimahSurvey() creates one audience at a time. Every instance exposes a Fetch handler for HTTP and a typed api for in-process server calls. Both audiences must use the same SurveyStore.

Mount fill and editor on separate paths. A handler never exposes operations from the other audience.

Create the instances

lib/survey.ts
import {
  SURVEY_API_BASE_PATH,
  SURVEY_EDITOR_API_BASE_PATH,
  dimahSurvey,
  guardAnonymous,
  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: guardAnonymous(),
});

basePath is part of the request contract and must match the corresponding browser client's baseURL. The defaults are /api/survey for fill and /api/admin/survey for editor.

A fill instance without guard throws at startup. An editor guard returns nothing or throws. See Security.

memoryAdapter() is for tests and local development. Choose SQL or a custom SurveyStore in Persistence.

Mount the HTTP handlers

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);

Mount the editor the same way at app/api/admin/survey/[[...path]]/route.ts with toNextJsHandler(editor).

Each framework adapter forwards the same Fetch handler; it never interprets SurveyJS JSON.

RuntimeExport
Next.js@dimah-survey/server/next → toNextJsHandler
Express@dimah-survey/server/express → toExpressHandler
Hono@dimah-survey/server/hono → toHonoHandler
Fastify@dimah-survey/server/fastify → toFastifyHandler
Elysia@dimah-survey/server/elysia → toElysiaHandler
SvelteKit@dimah-survey/server/svelte-kit → toSvelteKitHandler
Node.js@dimah-survey/server/node → toNodeHandler

The Node-based adapters need the unread request stream. Mount Express before express.json(). Configure Fastify so its JSON parser does not consume these paths before the adapter.

import express from "express";
import { toExpressHandler } from "@dimah-survey/server/express";
import { fill } from "./survey";

const app = express();
app.all("/api/survey/*", toExpressHandler(fill));
app.use(express.json());

Repeat the same adapter setup for the editor path. Never send both audiences through one instance.

Call the API on the server

fill.api and editor.api take { body } or { query }. They do not perform an HTTP round trip. Pass headers or a Request when the guard needs cookies or other caller context.

await editor.api.publishSurvey({
  body: { id: "welcome" },
});
import { headers } from "next/headers";

await fill.api.startResponse({
  body: { surveyId: "welcome" },
  headers: await headers(),
});

Without forwarded request data, an in-process call has no session cookie.

Call from the browser

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

const fillClient = createFillClient({ baseURL: "/api/survey" });
await fillClient.startResponse({ surveyId: "welcome" });

Browser clients take flat objects and perform HTTP. Import them from @dimah-survey/react in React code or @dimah-survey/core elsewhere. Do not send a server instance to the client bundle.

See the complete method and route map in HTTP protocol.

On this page