Working with PATs, GraphQL Mutations, and User Submitted Content with GraphCMS
Tags: graphql, node, serverless, headlesscms
Published: 2020-04-02 14:56:33

---
title: Working with PATs, GraphQL Mutations, and User Submitted Content with GraphCMS
published: true
description: Use serverless functions with GraphQL mutations to allow user submitted content.
tags: graphql, node, serverless, headlesscms
---

Let's take the following datamodel for a product that has many reviews.

```graphql
type Product {
  id: ID!
  name: String!
  reviews: [Review!]!
}

type Review {
  id: ID!
  name: String!
  comment: String
}
```

With GraphCMS, when you define this schema in your project, we automatically generate the applicable queries and mutations for the `Product` and `Review` models.

Quite often you will have interactive content that users can either subscribe to view, download, like or comment on. With the schema above, products can have many reviews, and a typical scenario would be to allow users to submit reviews directly from your website or follow up email.

However in the context of a headless CMS, allowing users to directly submit data to your GraphQL endpoint is risky, and gives curious users ability to inspect requests and do some naughty things you might not expect.

This is why GraphCMS has Permanent Auth Tokens, so only requests with a `Authorization` header will be let through. PATs can be scoped to queries, mutations, or both.

So how do we solve this problem? The answer is to create a custom endpoint or GraphQL layer to forward requests with the PAT.

If you open the GraphCMS API Playground, you'll see within the docs there is a generated mutation named `createReview`.

To create a product review, we'll need the `name`, `comment` and a `productId` that we can use to `connect` an existing `Product` entry.

Let's have a look at the mutation:

```graphql
mutation ($productId: ID!, $name: String!, $comment: String) {
  createReview(data: {name: $name, comment: $comment, product: {connect: {id: $productId}}}) {
    id
    name
    comment
  }
}
```

Now let's create a quick function that uses [`graphql-request`](https://www.npmjs.com/package/graphql-request) to send this mutation with the required variables.

_We'll use Next.js + API routes to create our example, but the same concept applies to any framework + tech stack._

```js
// In pages/api/submit-review.js
import { GraphQLClient } from 'graphql-request';

const client = new GraphQLClient(process.env.GRAPHCMS_URL, {
  headers: {
    Authorization: `Bearer ${process.env.GRAPHCMS_TOKEN}`,
  },
});

const mutation = `mutation ($productId: ID!, $name: String!, $comment: String) {
  createReview(data: {name: $name, comment: $comment, product: {connect: {id: $productId}}}) {
    id
    name
  }
}
`;

export default async (req, res) => {
  const { productId, name, comment } = JSON.parse(req.body);

  const variables = { productId, name, comment };

  try {
    const data = await client.request(mutation, variables);

    res.status(201).json(data);
  } catch (err) {
    res.send(err);
  }
};

```

Then all that's left to do is create a form that submits the data to `/api/submit-review`.

```js
// In components/ReviewForm.js
import { useState } from 'react';
import fetch from 'isomorphic-unfetch';
import { useForm, ErrorMessage } from 'react-hook-form';

const ReviewForm = ({ productId }) => {
  const [submitted, setSubmitted] = useState(false);
  const { formState, register, errors, handleSubmit } = useForm();
  const { isSubmitting } = formState;

  const onSubmit = async ({ name, comment }) => {
    try {
      const response = await fetch('/api/submit-review', {
        method: 'POST',
        body: JSON.stringify({ productId, name, comment }),
      });

      setSubmitted(true);
    } catch (err) {
      console.log(err)
    }
  };

  if (submitted) return <p>Review submitted. Thank you!</p>;

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <input
          name="name"
          placeholder="Name"
          ref={register({ required: 'Name is required' })}
        />
        <ErrorMessage name="name" errors={errors} />
      </div>
      <div>
        <textarea name="comment" placeholder="Comment" ref={register} />
      </div>
      <div>
        <button type="submit" disabled={isSubmitting}>
          Create review
        </button>
      </div>
    </form>
  );
};

export default ReviewForm;
```

_You'll want to display any errors if there are any, but I'll leave that to you. 😉_

All that's left to do is is render the `<ReviewForm productId="..." />` component on our page and pass in the `productId` prop.

{% youtube uzdRGQwF-UI %}