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



A fill session in Next.js App Router — drafts, `showWhen`, and snapshot validation.

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

    <Tabs items="[&#x22;pnpm&#x22;, &#x22;npm&#x22;, &#x22;yarn&#x22;, &#x22;bun&#x22;]">
      <Tab value="pnpm">
        `bash pnpm add @dimah-form/server @dimah-form/react `
      </Tab>

      <Tab value="npm">
        `bash npm install @dimah-form/server @dimah-form/react `
      </Tab>

      <Tab value="yarn">
        `bash yarn add @dimah-form/server @dimah-form/react `
      </Tab>

      <Tab value="bun">
        `bash bun add @dimah-form/server @dimah-form/react `
      </Tab>
    </Tabs>
  </Step>

  <Step>
    ### Define a form [#define-a-form]

    ```ts title="lib/forms/feedback.ts"
    import { defineForm } from "@dimah-form/server";

    export const feedbackForm = defineForm({
      title: "Product Feedback",
      slug: "feedback",
      fields: [
        { id: "fullName", type: "text", label: "Full name", required: true },
        {
          id: "rating",
          type: "select",
          label: "Rating",
          required: true,
          options: [
            { value: "great", label: "Great" },
            { value: "ok", label: "Okay" },
            { value: "poor", label: "Poor" },
          ],
        },
        {
          id: "improvements",
          type: "text",
          label: "What could we improve?",
          required: true,
          showWhen: { field: "rating", equals: "poor" },
        },
      ],
    });
    ```

    ```ts title="lib/forms/index.ts"
    import { feedbackForm } from "./feedback";

    export const forms = {
      feedback: feedbackForm,
    };
    ```
  </Step>

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

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

    export const form = dimahForm({
      database: memoryAdapter(),
      forms,
      basePath: "/api/form",
    });

    export type Form = typeof form;
    ```

    `memoryAdapter()` is process-local. For SQL, see [Database](https://form.dimah.dev/docs/database).
  </Step>

  <Step>
    ### Mount the handler [#mount-the-handler]

    ```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);
    ```
  </Step>

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

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

    import { createFormClient } from "@dimah-form/react";
    import type { Form } from "@/lib/form";

    export const formClient = createFormClient<Form>({
      basePath: "/api/form",
    });

    export const { useFormResponse } = formClient;
    ```
  </Step>

  <Step>
    ### Build the fill UI [#build-the-fill-ui]

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

    import { emptyToNull, fieldLabel, fieldOptions } from "@dimah-form/react";
    import type { FormSnapshot } from "@dimah-form/react";
    import { useFormResponse } from "@/lib/form-client";
    import type { Form } from "@/lib/form";

    export function Questionnaire({ form }: { form: FormSnapshot }) {
      const q = useFormResponse<Form["$Infer"]["answers"]["feedback"]>({
        snapshot: form,
        autosave: { debounceMs: 1000 },
      });

      if (q.inactive) return <p>This form is inactive.</p>;
      if (q.status === "submitted") return <p>Submitted.</p>;

      return (
        <form
          onSubmit={(e) => {
            e.preventDefault();
            void q.submit();
          }}
          className="mx-auto max-w-md space-y-4"
        >
          <h1 className="text-xl font-bold">{form.title}</h1>

          {q.visibleFields.map((field) => {
            const binding = q.field(field.id);
            const value = typeof binding.value === "string" ? binding.value : "";

            return (
              <div key={field.id} className="space-y-1">
                <label className="block text-sm font-medium">
                  {fieldLabel(field)}
                  {binding.required && <span className="ml-1 text-red-500">*</span>}
                </label>
                {field.type === "select" ? (
                  <select
                    value={value}
                    disabled={binding.disabled}
                    onChange={(e) => binding.onChange(emptyToNull(e.target.value))}
                    className="w-full rounded border p-2"
                  >
                    <option value="">Select...</option>
                    {fieldOptions(field).map((opt) => (
                      <option key={opt.value} value={opt.value}>
                        {opt.label}
                      </option>
                    ))}
                  </select>
                ) : (
                  <input
                    value={value}
                    disabled={binding.disabled}
                    onChange={(e) => binding.onChange(emptyToNull(e.target.value))}
                    className="w-full rounded border p-2"
                  />
                )}
                {binding.error && (
                  <p className="text-xs text-red-500">{binding.error}</p>
                )}
              </div>
            );
          })}

          {q.error && <p className="text-sm text-red-500">{q.error}</p>}

          <div className="flex gap-2">
            <button
              type="button"
              disabled={q.pending !== undefined || q.locked}
              onClick={() => void q.saveDraft()}
              className="rounded border px-4 py-2"
            >
              {q.pending === "save" ? "Saving..." : "Save draft"}
            </button>
            <button
              type="submit"
              disabled={q.pending !== undefined || q.locked}
              className="rounded bg-black px-4 py-2 text-white"
            >
              {q.pending === "submit" ? "Submitting..." : "Submit"}
            </button>
          </div>
        </form>
      );
    }
    ```
  </Step>

  <Step>
    ### Render on a page [#render-on-a-page]

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

    export default async function Page() {
      const snapshot = await form.api.getForm({
        query: { formId: "feedback" },
      });

      if (!snapshot) notFound();

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

***

## Next Steps [#next-steps]

<Cards>
  <Card title="Architecture" href="/docs/architecture" description="Package boundaries, snapshots, and the request pipeline." />

  <Card title="Forms" href="/docs/forms" description="defineForm, field rules, and showWhen." />

  <Card title="React Client" href="/docs/react" description="useFormResponse: visibility, drafts, and submit." />

  <Card title="Database" href="/docs/database" description="Keep memoryAdapter, or add SQL with FumaDB." />
</Cards>
