# Server (https://form.dimah.dev/docs/server)



`@dimah-form/server` is the Node / Edge engine: routing, snapshots, guards, and validation. Mount it on your framework, or call `form.api` in a server component with no extra HTTP hop.

***

## Initializing `dimahForm()` [#initializing-dimahform]

Create a centralized `dimahForm()` instance in your project:

```ts title="lib/form.ts"
import { dimahForm, memoryAdapter } from "@dimah-form/server";
import { forms } from "@/lib/forms";

export const form = dimahForm({
  database: memoryAdapter(), // or db(formDb) from @dimah-form/db
  forms,
  basePath: "/api/form", // Default base path
});

export type Form = typeof form;
```

`database` is required. A SQL database is not. `memoryAdapter()` is enough to run. For production SQL, see [Database](https://form.dimah.dev/docs/database).

***

## HTTP Framework Adapters [#http-framework-adapters]

`@dimah-form/server` provides dedicated adapters for all major JavaScript backend frameworks:

<Tabs items="[&#x22;Next.js&#x22;, &#x22;Hono&#x22;, &#x22;Express&#x22;, &#x22;Fastify&#x22;, &#x22;SvelteKit&#x22;, &#x22;Elysia&#x22;, &#x22;Node.js&#x22;, &#x22;Web Fetch&#x22;]">
  <Tab value="Next.js">
    Mount a catch-all route handler in the Next.js App Router:

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

    export const { GET, POST, PUT, PATCH, DELETE } = toNextJsHandler(form);
    ```
  </Tab>

  <Tab value="Hono">
    ```ts title="src/index.ts"
    import { Hono } from "hono";
    import { toHonoHandler } from "@dimah-form/server/hono";
    import { form } from "./lib/form";

    const app = new Hono();

    app.on(
      ["GET", "POST", "PUT", "PATCH", "DELETE"],
      "/api/form/*",
      toHonoHandler(form),
    );

    export default app;
    ```
  </Tab>

  <Tab value="Express">
    ```ts title="src/server.ts"
    import express from "express";
    import { toExpressHandler } from "@dimah-form/server/express";
    import { form } from "./lib/form";

    const app = express();

    // Mount dimahForm BEFORE express.json() so it can read raw stream payloads
    app.all("/api/form/*", toExpressHandler(form));

    app.use(express.json());
    app.listen(3000, () => console.log("Server listening on port 3000"));
    ```
  </Tab>

  <Tab value="Fastify">
    ```ts title="src/server.ts"
    import Fastify from "fastify";
    import { toFastifyHandler } from "@dimah-form/server/fastify";
    import { form } from "./lib/form";

    const app = Fastify();

    app.all("/api/form/*", toFastifyHandler(form));

    await app.listen({ port: 3000 });
    ```
  </Tab>

  <Tab value="SvelteKit">
    ```ts title="src/routes/api/form/[...path]/+server.ts"
    import { toSvelteKitHandler } from "@dimah-form/server/svelte-kit";
    import { form } from "$lib/server/form";

    const handler = toSvelteKitHandler(form);

    export const GET = handler;
    export const POST = handler;
    export const PUT = handler;
    export const PATCH = handler;
    export const DELETE = handler;
    ```
  </Tab>

  <Tab value="Elysia">
    ```ts title="src/index.ts"
    import { Elysia } from "elysia";
    import { toElysiaHandler } from "@dimah-form/server/elysia";
    import { form } from "./lib/form";

    new Elysia().all("/api/form/*", toElysiaHandler(form)).listen(3000);
    ```
  </Tab>

  <Tab value="Node.js">
    ```ts title="src/server.ts"
    import { createServer } from "node:http";
    import { toNodeHandler } from "@dimah-form/server/node";
    import { form } from "./lib/form";

    const handler = toNodeHandler(form);

    const server = createServer((req, res) => {
      if (req.url?.startsWith("/api/form")) {
        return handler(req, res);
      }
      res.statusCode = 404;
      res.end("Not Found");
    });

    server.listen(3000);
    ```
  </Tab>

  <Tab value="Web Fetch">
    Use standard `Request` / `Response` directly (Cloudflare Workers, Deno, Bun):

    ```ts title="src/worker.ts"
    import { form } from "./lib/form";

    export default {
      fetch(request: Request) {
        if (new URL(request.url).pathname.startsWith("/api/form")) {
          return form.handler(request);
        }
        return new Response("Not Found", { status: 404 });
      },
    };
    ```
  </Tab>
</Tabs>

***

## Direct Server API (`form.api`) [#direct-server-api-formapi]

In addition to serving HTTP requests, `dimahForm()` provides a typed, internal API (`form.api`) that you can call directly in Next.js Server Actions, React Server Components, or backend services without any HTTP network overhead:

```tsx title="app/survey/[slug]/page.tsx"
import { form } from "@/lib/form";
import { notFound } from "next/navigation";
import { headers } from "next/headers";
import { Questionnaire } from "@/components/questionnaire";

interface PageProps {
  params: Promise<{ slug: string }>;
}

export default async function SurveyPage({ params }: PageProps) {
  const { slug } = await params;

  // Direct server call — runs validation and security guards internally
  const snapshot = await form.api.getForm({
    query: { formId: slug },
    headers: await headers(), // Forwards session cookies/headers to guard
  });

  if (!snapshot) {
    notFound();
  }

  return <Questionnaire form={snapshot} />;
}
```

### Available `form.api` Methods [#available-formapi-methods]

| Method                | Payload                                          | Returns          | Description                                         |
| :-------------------- | :----------------------------------------------- | :--------------- | :-------------------------------------------------- |
| **`getForm`**         | `{ query: { formId } }`                          | `FormSnapshot`   | Retrieves form definition by ID or slug.            |
| **`listForms`**       | `{ query?: { status?, limit?, offset? } }`       | `FormList`       | Returns paginated list of forms.                    |
| **`saveForm`**        | `{ body: FormSnapshot }`                         | `FormSnapshot`   | Upserts dynamic form in database.                   |
| **`deleteForm`**      | `{ body: { formId } }`                           | `{ ok: true }`   | Deletes a dynamic form (must have no responses).    |
| **`startResponse`**   | `{ body: { formId, respondentId?, resume? } }`   | `ResponseRecord` | Starts response session and freezes snapshot.       |
| **`getResponse`**     | `{ query: { responseId } }`                      | `ResponseRecord` | Fetches a response record.                          |
| **`listResponses`**   | `{ query?: ListResponsesQuery }`                 | `ResponseList`   | Lists response summaries or full records.           |
| **`saveDraft`**       | `{ body: { responseId, answers, updatedAt? } }`  | `ResponseRecord` | Patches partial answers on an active draft.         |
| **`submitResponse`**  | `{ body: { responseId, answers?, updatedAt? } }` | `ResponseRecord` | Validates visible answers and finalizes response.   |
| **`abandonResponse`** | `{ body: { responseId, updatedAt? } }`           | `ResponseRecord` | Marks draft as abandoned.                           |
| **`reopenResponse`**  | `{ body: { responseId, updatedAt? } }`           | `ResponseRecord` | Unlocks submitted/abandoned response back to draft. |
| **`deleteResponse`**  | `{ body: { responseId } }`                       | `{ ok: true }`   | Deletes response record.                            |

***

## Next Steps [#next-steps]

<Cards>
  <Card title="Database" href="/docs/database" description="Required adapter: memory, optional FumaDB, or a custom store." />

  <Card title="Auth" href="/docs/auth" description="Secure form operations and respondent data with guard hooks." />

  <Card title="Configuration" href="/docs/configuration" description="Explore all available dimahForm configuration parameters." />
</Cards>
