Building a Contact Form with Records
This guide will help you understand the process of building a contact form backed up with Record. Whenever you need to build a simple contact form, blog functionality, or complex API endpoint, you can define any data structure you need with Records.
Requirements
Steps
Building a Contact Form with Records is a nine-step process:
Step 1: Define Contact – Table
To define a new Table, create a YAML file in the schema
directory. By default, the name of your table will be the same as your file name, hence create a file app/schema/contact.yml
. To build a Contact Form, you'll need an email of a person who submits the request (to be able to reply to them) along with their name, and the description of a problem. All of those Properties should be strings, hence the table should look as follows:
app/schema/contact.yml
name: contact
properties:
- name: name
type: string
- name: email
type: string
- name: description
type: string
For more information on Properties please visit the Properties Documentation.
Step 2: Define GraphQL to persist record in contact
table
Now that you have the definition of a Contact
table in place, you can create a GraphQL mutation that will persist the record in the database. To do it, you would need to use record_create GraphQL mutation. Create app/graphql/contacts/create.graphql
file with the following content:
app/graphql/contacts/create.graphql
mutation contact_create($name: String!, $email: String!, $description: String!) { # we define arguments as mandatory by adding "!" after the type
record_create(
record: {
table: "contact"
properties: [
# we use "value" because the property type is a string, if it was for example an int you would use value_int etc.
{ name: "name", value: $name }
{ name: "email", value: $email }
{ name: "description", value: $description }
]
}
) {
id
# we use "property" because the property type is a string, if it was for example an int, you would use property_int
name: property(name: "name")
email: property(name: "email")
description: property(name: "description")
# for each new record we automatically store created_at so you don't have to remember about it
created_at
}
}
You can develop and run GraphQl queries and mutations using pos-cli gui serve:
![Screenshot of Create Contact GraphQL](https://cdn.staging.oregon.platform-os.com/instances/13/assets/images/developer-guide/records/building-contact-form-with-records/create-contact.png?updated=1716277311)
Upon successful invocation, you will be able to see the newly created record in Database Management UI:
![Screenshot of records inside Contact table](https://cdn.staging.oregon.platform-os.com/instances/13/assets/images/developer-guide/records/building-contact-form-with-records/contact-table.png?updated=1716277311)
Step 3: Fetch saved data with GraphQL
Now that you have created the Contact
Table in the previous step, you can proceed to fetch this data with GraphQL. To define contacts/find query, create file app/graphql/contacts/find.graphql
:
query contacts_find($id: ID) {
records(
filter: {
table: { value: "contact" },
id: { value: $id }
},
per_page: 100
) {
results {
id
name: property(name: "name")
email: property(name: "email")
description: property(name: "description")
}
}
}
To check if the above query works properly you can head to the next section to learn how to embed query results with page view or use pos-cli gui
Step 4: Render Records within page
Display contacts as a list with the possibility to get to the contact details view.
As a first step, you need two pages that will respond to given paths:
GET /contacts
for list viewGET /contacts/:id
for detail view
The first endpoint will be defined by app/views/pages/contacts/index.liquid
app/views/pages/contacts/index.liquid
{% liquid
graphql g = "contacts/find"
assign contacts = g.records.results
%}
<h1>List View</h1>
<table>
<tr>
<th>ID</th>
<th>Email</th>
<th></th>
</tr>
{% for contact in contacts %}
<tr>
<td>{{ contact.id }}</td>
<td>{{ contact.email }}</td>
<td><a href="/contacts/{{ contact.id }}">Details</a></td>
</tr>
{% endfor %}
</table>
And the detail view will be defined by app/views/pages/contacts/show.liquid
app/views/pages/contacts/show.liquid
---
slug: contacts/:id
---
{% liquid
graphql g = "contacts/find", id: context.params.id
assign contact = g.records.results.first
%}
<h1>Detail view</h1>
<p>{{ contact.name }}</p>
<p>{{ contact.email }}</p>
<p>{{ contact.description }}</p>
For more information, visit the Pages documentation. In a real life, you will also want to ensure that only administrator has access to contact requests and you will want to implement authentication and authorization. Moreover, you usually want to separate business logic from presentation layer. For real life scenario, we recommend not having any HTML directly in the page and use Liquid Partials for presentation purposes. We recommend to treat Page as controller, which at the end calls one partial to present the view.
Step 5: Build Contact Form with HTML
In Step 2: Define GraphQL to persist record in contact
table , you have created a GraphQL file called contacts/create
(which corresponds to the relative path of the file in graphql
directory). In this step we will build a form, which allows the user to submit their contact request information . Add a new page app/views/pages/contacts/new.liquid
<form action="/contacts" method="post">
<input type="hidden" name="authenticity_token" value="{{ context.authenticity_token }}" />
<label for="name">Name</label>
<input name="contact[name]" id="name" type="text">
<label for="email">Email</label>
<input name="contact[email]" id="email" type="email">
<label for="description">Description</label>
<textarea name="contact[description]" id="description"></textarea>
<button>Submit</button>
</form>
Note
Authenticity token is used to prevent CSRF. We include it in the example because while working on platformOS developers tend to forget about it and as soon as they add authentication, their session is invalidated after submitting the form. You can read more about it in chapter about User Authentication.
Step 6: Invoking GraphQL server-side
In the previous step we have implemented a form, which uppon submission triggers POST request to /contacts endpoint. Form values will be accessible via context.params
. Because we have namespaced all input names, the values submitted by the user will be available via context.params.contact
(i.e. context.params.contact.name will return value the User has entered). First, we have to create a Page, which will be invoked for the form submission:
app/views/pages/contacts/create.liquid
---
slug: contacts
method: post
---
{% liquid
graphql g = "contacts/create", name: context.params.contact.name, email: context.params.contact.email, description: context.params.contact.description
assign url = "/contacts/" | append: g.record_create.id
redirect_to url
%}
After submitting the form from Step 5, the browser will trigger a request to POST /contacts. platformOS via graphql Liquid Tag will invoke the contacts/create
GraphQL mutation with the parameter mapping that was defined (graphql Email argument will have a value equal to context.params.contact.email
etc.). After invoking GraphQL, we'll redirect the user to /contacts/<id>
, where id will be the value of the newly created record.
Note
Providing manual mapping for each argument in GraphQl can be cumbersone, this is why there is a magic args
argument defined. You can achieve the same result by using graphql g = "contacts/create", args: context.params.contact
.
To verify that everything works as expected, go to /contacts/new in your instance, fill the form and hit submit. You should be redirect to the show
Page defined in Step 4 and you should be able to see the information you have submitted in the form. You should also see the new entry in /contacts page.
Note
In practical scenarios, it
Step 7: Delete a Record
In order to remove record from the database, use record_delete GraphQL mutation.
Step 8: Update a Record
In order to update a record, use record_update GraphQL mutation. Only the properties explicitly defined in the GraphQL mutation will be updated. The properties you do not define as arguments will stay untouched.
Next steps
Congratulations! You have learned how to build forms and persist user input using Records. You can now explore our reference documentation for Properties and Tables: