Route
src/routes/contacts.route.ts
Use GET /contacts for reading and POST /contacts for writing through separate Controllers.
defineRoute({ method: 'POST', path: '/contacts',
controller: createContact, view: renderContacts })
RCMVA contact form that reads existing records and writes validated submissions without allowing SQL or storage concerns into the Controller or View.
Open the Database form demonpx wrangler d1 create rcmv-contacts.wrangler.jsonc as DB.schema.sql, then run npx wrangler d1 execute rcmv-contacts --file=schema.sql --local.--remote only when you are ready to update the production database.Cloudflare D1 getting started ↗ · D1 Wrangler command reference ↗
"d1_databases": [{
"binding": "DB",
"database_name": "rcmv-contacts",
"database_id": "paste-generated-id"
}]Use GET /contacts for reading and POST /contacts for writing through separate Controllers.
defineRoute({ method: 'POST', path: '/contacts',
controller: createContact, view: renderContacts })Turn the submitted form into a command for the Model and choose the result to present.
const input = Object.fromEntries(await context.request.formData())
const result = await saveContact(input, context.environment.DB)
return controller.ready(result.data)Validate with Zod, perform the parameterized database operation, and normalize the returned record.
const contact = ContactInput.parse(input)
const row = await db.prepare('INSERT INTO contacts ... RETURNING *')
.bind(contact.name, contact.email).first()
return model.found(Contact.parse(row))Render the form, current records, errors, empty states, and confirmation—all without knowing SQL.
return html(<ContactsPage
contacts={outcome.data.contacts}
saved={outcome.data.saved}
/>)