Homepage

Building a Contact Form with Records

Last edit: Jun 06, 2024

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

Upon successful invocation, you will be able to see the newly created record in Database Management UI:

Screenshot of records inside Contact table

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 view
  • GET /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:

Questions?

We are always happy to help with any questions you may have.

contact us