Creating Feedback Form on Vercel using Vue and Golang
Tags: vercel, vue, go, webdev
Published: 2020-10-25 08:32:13

You might be wondering why someone wants to set up a feedback form from scratch. It's just a feedback form and it's so simple.

I am well aware that there are a lot of Saas applications I could have used to set up such a form, yet I took a far complex approach to achieve a simple task. But by doing this, along the way I learned a few things. Which is what matters in this case, since the sole purpose of going through the trouble is to get some amount of idea and hands-on on technologies like, using Vercel, Vue, etc. I already had a working knowledge of Golang. I had heard a lot about how Vercel makes deploying frontend application so simple, so wanted to give it a try.

### Components

The frontend of the form is written in Vue.js, which, on submit sends a POST request to a serverless application written in Golang. Go application sends an email to me using Sendgrid. This Vue application and Golang app are both deployed on Vercel.

### Creating the Frontend

Create a new Vue project.
```bash
npx @vue/cli create -d my-feedback-form && cd my-feedback-form
```
If the command does not work, you probably don't have Nodejs installed, you will need to [install it](https://nodejs.org/en/download/package-manager/). 

Although, we can use `npm run serve` to run our development server, let's install Vercel cli as well. Vercel cli will help us to test our Golang serverless application as well.

```bash
npm i -g vercel
```
Start the development server.
```bash
vercel dev -d
```
It will ask you a series of questions. Go with the default options. You may need to provide your email address. Open your web browser and go to http://localhost:3000, you should the Vue welcome page.

I have used bootstrap as well to make the form look a little better. Install bootstrap by running.
```bash
npm i bootstrap-vue bootstrap
```

Update the `src/main.js` file with the following content.

```javascript
import Vue from 'vue'
import App from './App.vue'
import BootstrapVue from 'bootstrap-vue'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'

Vue.use(BootstrapVue)
Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')
```

Now, Create a file at `src/components/Form.vue` with the following content.
```html
<template>
  <div class="row justify-content-center">
    <div>
      <h2 v-if="!status">Please provide your feedback!</h2>

      <b-form v-if="!status" @submit="sendForm">
        <b-form-group>
          <b-form-input type="text" name="name" v-model="name" placeholder="Enter your name..."></b-form-input>
        </b-form-group>
        <b-form-group>
          <b-form-input type="email" name="email" v-model="email" placeholder="Enter your email..." required>
          </b-form-input>
        </b-form-group>
        <b-form-group>
          <b-form-textarea type="text" name="message" v-model="message" placeholder="Leave a feedback..." required>
          </b-form-textarea>
        </b-form-group>
        <b-form-group>
          <b-button block variant="primary" type="submit">Send</b-button>
        </b-form-group>
      </b-form>

      <h2 v-if="status === 'success'">Thank you, your feedback is submitted!</h2>
      <h2 v-if="status === 'error'">Oops, something went wrong. Please try again.</h2>
    </div>
  </div>
</template>

<script>
  export default {
    name: 'Form',
    data: function () {
      return {
        status: null,
        name: null,
        email: null,
        message: null
      }
    },
    methods: {
      sendForm: function (event) {
        event.preventDefault()
        fetch('/api/sendmail', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            Accept: 'application/json'
          },
          body: JSON.stringify({ name: this.name, email: this.email, message: this.message })
        })
          .then((response) => {
            if (response.status < 299) {
              this.status = 'success'
            }
            else {
              this.status = 'error'
            }
          })
      }
    }
  }
</script>
```
Update the `src/App.vue` file with the following content.

```html
<template>
  <div id="app">
    <Form />
  </div>
</template>

<script>
  import Form from './components/Form.vue'

  export default {
    name: 'App',
    components: {
      Form
    }
  }
</script>
```
We don't need `src/components/HelloWorld.vue`, it can be removed.

Check your browser, you should see the feedback form. On filling the form and clicking *Submit*, you should receive an error saying *Oops, something went wrong. Please try again*. It's because on clicking submit, the Vue application is sending a POST request on the `/api/sendmail` endpoint. We do not have it implemented.

Let's implement it using Golang. Vercel provides a powerful feature of adding our backend code in the same repository. It supports some of the popular languages and is deployed along with your front-end application as a serverless function.

### Creating the backend

There is no additional configuration needed to deploy a serverless api using Vercel. Code written in `api` directory will be read and deployed as a function on the same domain as your frontend, at path `/api`. Read more [here](https://vercel.com/docs/serverless-functions/introduction).

Create a directory named `api` and switch to it using 
```bash
mkdir api && cd api
```
Create a new file `sendmail.go`. 

Put the following code in the file.
```go
package handler

import (
	"encoding/json"
	"net/http"
	"os"

	"github.com/sendgrid/sendgrid-go"
	"github.com/sendgrid/sendgrid-go/helpers/mail"
)

//Feedback is feedback struct
type Feedback struct {
	Name    string
	Email   string
	Message string
}

//Handler is the default handler
func Handler(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path != "/api/sendmail" || r.Method != "POST" {
		http.Error(w, "404 not found.", http.StatusNotFound)
		return
	}

	var fb Feedback
	err := json.NewDecoder(r.Body).Decode(&fb)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	res, body, err := SendMail(fb)
	if err != nil {
		println("Error sending Email: %v", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.WriteHeader(res)
	w.Header().Set("content-type", "application/json")
	w.Write([]byte(body))
	return
}

//SendMail sends the email using Sendgrid
func SendMail(f Feedback) (res int, out string, err error) {
	from := mail.NewEmail(f.Name, f.Email)
	subject := "[Feedback] on something"
	to := mail.NewEmail("Liptan Biswas", "<your-email-address>")
	message := mail.NewSingleEmail(from, subject, to, "", f.Message)
	client := sendgrid.NewSendClient(os.Getenv("SENDGRID_API_KEY"))
	r, err := client.Send(message)
	if err != nil {
		return r.StatusCode, r.Body, err
	}
	return r.StatusCode, r.Body, nil
}
```

Initialize go modules and add dependencies to the `go.mod` file by running.
```bash
go mod init && go mod tidy
```

During serverless function deployment to Vercel, these dependencies are automatically discovered and installed.

Follow [Sendgrid documentation](https://sendgrid.com/docs/ui/account-and-settings/api-keys/) and obtain an API key from Sendgrid.

Come back to the project root and create a `.env` file and put your Sendgrid API key in that.
```ini
SENDGRID_API_KEY=<YOUR-API-KEY>
```
Make sure to append `.env` into `.gitignore` otherwise you may mistakenly commit it.

If you have Postman, fire it up and send a POST request to `http://localhost:3000/api/sendmail`. You may skip if you are confident that the API works.

![Postman Response](https://i.imgur.com/HSt1vKS.png)

You should see a 202 response if Sendgrid accepted your email.

Now, we can head back to the browser and submit the form to test it once, you should get both the emails in your inbox.

### Deploy to Vercel

Deploying your application to Vercel is the easiest step. Just run the command.
```bash
vercel --prod
```
and it will be deployed. You may want to push the code to git and connect the repo to Vercel. But, this works as well. While Vercel is deploying your application, go to the Vercel dashboard, and find your application. Navigate to Your App >> Settings >> Environment Variables. Add the `SENDGRID_API_KEY` environment variable.

You will need to redeploy for the env variable to take effect. Just, run the `vercel --prod` command again.

If you connect your repository to Vercel, you won't need to run this command every time you want to deploy. Just push the commit and Vercel will automatically pick up the change and deploy it. It will also comment the deployed URL on the commit.

![Vercel comment](https://i.imgur.com/jLqm4PB.png)

That's it.

Here's the Github Repo.

{% github https://github.com/liptanbiswas/pusti %}

Here's the live form. Do let me know your feedback.

{% codesandbox nih0r %}

References: [Vercel blog](https://vercel.com/guides/vue-js-html-forms-with-formcarry-vercel)