Skip to content

Forms Domain

The forms domain provides dynamic form creation, submission validation, and data collection for TypeScript backends. It is structured in strict Clean Architecture layers with zero npm dependencies in the domain and application layers.

Terminal window
npx @backcap/cli add forms

The Form entity is the aggregate root. It holds a list of typed fields and supports adding new fields after creation.

import { Form } from "./domains/forms/domain/entities/form.entity";
import { FormField } from "./domains/forms/domain/value-objects/form-field.vo";
const field = FormField.create({ name: "email", type: "email", required: true }).unwrap();
const result = Form.create({ id: crypto.randomUUID(), name: "Contact", fields: [field] });
if (result.isOk()) {
const form = result.unwrap();
console.log(form.name, form.fields.length); // "Contact" 1
}
FieldTypeDescription
idstringUnique identifier (UUID)
namestringForm name
fieldsFormField[]List of form field value objects
createdAtDateTimestamp of creation

A form must have at least one field to be created. addField(field) returns a new Form instance (immutability).

import { FormField } from "./domains/forms/domain/value-objects/form-field.vo";
const field = FormField.create({
name: "color",
type: "select",
required: true,
options: ["red", "blue", "green"],
});

Supported types: "text", "email", "number", "boolean", "select". Select fields require a non-empty options array.

Error ClassConditionMessage
FormNotFoundNo form found for the given IDForm not found with id: "<id>"
FormValidationFailedSubmission data fails field validationForm validation failed: "<details>"
EventEmitted ByPayload
FormSubmittedSubmitForm use caseformId, submissionId, occurredAt

Registers a new form with typed fields.

const result = await formsService.createForm({
name: "Contact",
fields: [
{ name: "email", type: "email", required: true },
{ name: "message", type: "text", required: true },
],
});
// Result<{ formId: string; createdAt: Date }, Error>

Validates submitted data against the form’s field definitions and stores the submission.

const result = await formsService.submitForm({
formId: "form-123",
data: { email: "user@example.com", message: "Hello!" },
});
// Result<{ submissionId: string; submittedAt: Date }, Error>

Validation: required fields must be present, email fields checked with regex, number fields must be finite, select fields checked against options.

Paginates submissions for a form.

const result = await formsService.getSubmissions({
formId: "form-123",
limit: 50,
offset: 0,
});
// Result<{ submissions: Array<...>; total: number }, Error>
export interface IFormStore {
saveForm(form: Form): Promise<void>;
findFormById(id: string): Promise<Form | undefined>;
saveSubmission(formId: string, data: Record<string, unknown>): Promise<{ submissionId: string }>;
getSubmissions(formId: string, pagination: { limit: number; offset: number }): Promise<{ submissions: Array<...>; total: number }>;
}
import { createFormsService, IFormsService } from "./domains/forms/contracts";
const formsService: IFormsService = createFormsService({ formStore });
domains/forms/
domain/
entities/form.entity.ts
value-objects/form-field.vo.ts
events/form-submitted.event.ts
errors/form-not-found.error.ts
errors/form-validation-failed.error.ts
application/
use-cases/create-form.use-case.ts
use-cases/submit-form.use-case.ts
use-cases/get-submissions.use-case.ts
ports/form-store.port.ts
dto/create-form.dto.ts
dto/submit-form.dto.ts
dto/get-submissions.dto.ts
contracts/
forms.contract.ts
forms.factory.ts
index.ts
shared/
result.ts