✨ Introducing Gatsby Functions
In this Gatsby Cloud Functions reCAPTCHA project, we learn all about a brand new feature in Gatsby. That new feature is Gatsby Cloud Functions. Gatsby Cloud Functions let you code serverless (also known as lambda) functions right into your static Gatsby site. You might use them for processing form data or creating documents in your database via an API. Whatever you use them for, you will not have to worry about provisioning servers, maintaining them or even ensuring you have enough capacity to handle busy periods. All of that is taken out of your hands.
I got early access to Gatsby functions last month and set about building a demo site straight away. It is a static Gatsby MDX blog which lets users leave comments on blog posts . Using cloud functions, the site automatically triggers a rebuild so as to include fresh comments in the site's static content. All of that functionality was powered by serverless functions.
Today we'll look at something a little less ambitious, though still exciting. We are going to pair up Gatsby Cloud functions with the accessible Google reCAPTCHA. Read on to see how that is different from the annoying Captcha challenges of the past. We will bundle those features into a contact form for a Gatsby MDX blog.
If that sounds exciting, why don't we crack on? Before that though, as promised, we need to learn all about reCAPTCHA.
😕 How is a reCAPTCHA different to a CAPTCHA?
We have all probably done a CAPTCHA challenge at some point. You fill out a huge form, get to the bottom and have to run through a series of (seemingly) endless challenges! What's worse is that traditional CAPTCHAs can be inaccessible for partially sighted users . With reCAPTCHA, as users interact with the website, Google runs code behind the scenes, employing machine learning algorithms to determine if the interactions are typical of a human user or a bot. On form submission we can ask reCAPTCHA to generate a score from those user interactions giving us a metric to decide on whether we should perform some additional verification.
With that explained, let's build something interesting which you can use on your own site. We will look in more detail at the reCAPTCHA verification process as we go along. Most importantly it will demonstrate Gatsby Cloud functions.
🧱 What are we Building?
We will take a Gatsby v3 MDX starter blog and add a contact form using Formik. The submitted data will them be sent to our serverless function. Subsequently, the function will send us an email using SMTP to let us know someone has a message for us. We use SMTP to increase compatibility with whichever service you are already using, be it Mailtrain, Migadu, Postmark , Sendgrid or some other service. Wes Bos recommends using SMTP over a service's API in case you have to switch service at short notice. If you are using SMTP, it is just a case of swapping out credentials (rather than having to learn another API).
Now we know what we're doing, let's get a wriggle on! The first step will be to log into Google to generate reCAPTCHA API keys.
☁️ Gatsby Cloud Functions reCAPTCHA
Create reCAPTCHA API Keys
Firstly head over to Google to get API keys — at www.google.com/u/1/recaptcha/admin/create . We need to get reCAPTCHA v3 credentials. This includes a site key together with a secret key. So once you log in, enter a label for your site. Be sure to choose reCAPTCHA v3 and then enter the domains you need. You might want to add
localhost as a domain now and then remove it once you are satisfied things are working. Make a note of the two generated keys, we will need them in the next step.
Spin up a new Gatsby MDX Blog Site
Let's spin up a site to add those new keys to. We will use Gatsby Starter Climate which is a shell MDX blog site with a contact page . At the command line type:
The site has some environment variables which we need to define (we will add the new reCAPTCHA API keys shortly). Following best practices, these are not stored in the repo. Instead you just need to copy the example file, which is in the repo:
The last command spins up the site. You will see, as it stands, the blog is fully functional (open the site by going to localhost:8000 ). Before going on, take a look through the repo in your favourite code editor. Once you are comformable with how it all fits together, we'll carry on.
If you're ready, let's add those environment variables. Edit the
.env.production files, adding the following two lines to each:
Gatsby Cloud Functions reCAPTCHA: Set up the Contact Form
We'll use some boilerplate code to speed things up in creating an accessible form. Let's start by installing the extra packages we will need:
lodash.isobject (used in
FormikErrorFocus component, below) and
axios for posting the form data to the cloud function.
Let's create a basic contact form component. Create a component file at
src/components/ContactForm.jsx and add the following content:
src/components/ContactForm.jsx — click to expand code.
This won't work yet; we need to add a couple of imports first. I won't go into much detail explaining what we have here, since our main focus is Gatsby Cloud functions. Drop a comment below if you would like to see a separate post on creating accessible forms in Gatsby. Still, I should mention we have included a field just for bots! This a trick recommended by Netlify . The field is not visible in the browser and not announced by screen readers. It is present in the DOM though. It is pretty difficult for a human accidentally to find the field and enter a value. For that reason, whenever there is a value submitted for this field, it is quite safe to assume we are dealing with a bot. More on this when we create our cloud function code.
Next, we define accessible input fields. We do this using reusable components. Create a file for our input component at
src/components/InputField.jsx. Give it the following content:
src/components/InputField.jsx — click to expand code.
A Touch of Style
With our input components defined, let's touch them up a little with a spot of CSS. Create a style file at
src/components/InputField.module.scss and paste this minimal styling into it:
src/components/InputField.module.scss — click to expand code.
Let's not leave the contact form component out:
src/components/ContactForm.module.scss — click to expand code.
Finally we add some boilerplate code to improve user experience. This is just to help users out when there are form errors. For example if the user submits the form, forgetting to enter their email, the email field becomes focused and highlighted so they know exactly where the error is.
src/components/FormikErrorFocus.jsx — click to expand code.
Once again I won't talk much about this, but drop a comment below if you have a question or two!
reCAPTCHA Client API call
We're moving along quite quickly now! Next thing we'll look at is the client side Google Captcha API call. The way reCAPTCHA works, we need to make two calls to the API. The first, on the client side, is to get a token which identifies the user session. That token is valid for two minutes. As soon as we have the token we will send it to the cloud function so it can be used by the second API call, before it expires. We'll build up the code to implement this now. Edit the
src/components/ContactForm.jsx — click to expand code.
We've done a few things here so let's go through them one by one. First of all, we mentioned before that reCAPTCHA observes user behaviour up to the point they submit the form. That is done via the script we load in lines
66. We use React Helmet to place the script in the page's HTML head section. Next, when the user clicks the submit button, Formik calls
73). In the
handleSubmit function, we call the
grecaptcha function which, in turn, is defined in the script we just added via Helmet. This function contacts Google and fetches us a unique client token. Finally,
submitData. For now, that final function just prints the form data to the console. Let's change that next!
Submit Form Data
Finally for the client, we need to post the form data to our Gatsby Cloud function. To use Gatsby Cloud functions, we need to create an
api folder under
src. We will soon see how this is done. For now let's just finish the client side by posting the comment. Update the
src/components/ContactForm.jsx — click to expand code.
This is just a basic API call using axios. There is a fair bit of error handling code here, which is helpful for debugging. This is mostly boilerplate, nonetheless, let me know if you think it needs more explanation. You can also check the axios documentation for help . With the client side wrapped up, let's jump to the highlight of this project; the Gatsby Cloud function.
Gatsby Cloud Functions reCAPTCHA: Set up the Serverless Function
If you are using Gatsby 3.7 or newer there is no additional config to start using Gatsby Cloud functions. Let's actually define our serverless function then. On the client side, we post data to the
/api/submit-message route. So we need to create a file containing the cloud function, in our project with the matching path
src/api/submit-message.js. Let's do that now:
We probably completed the most technically challenging part of our project when we set up the contact form. In common with Netlify functions , Gatsby functions are fairly easy to implement, though the syntax differs slightly between the two services. One pro for Gatsby cloud functions is that it is easier to test functions locally, on the development server. We'll see this in a moment, but first let's go through the code we have just added.
Gatsby Cloud Function Anatomy
So the main function is the handler. This is what gets called when a user hits the API. In lines
11 of our serverless function, we see how we can check the user called our handler with the expected method (that is
POST as opposed to
GET etc.). Normally, we would want to revalidate all input data, within the cloud function. For the sake of brevity, we won't go into that here, though you can use similar functions to the ones used on the client side. In lines
33 we see how we can send different response codes back to the client browser. Apart from the response code, we can respond to the user's API call with an HTTP body. The body can be JSON data or a string:
We're going for string responses in our project. We just include JSON for future reference. There's not much left to do now. Next we will flesh out the code for the reCAPTCHA server side response. After that we fill out the SMTP email function and then we are all set for testing!
reCAPTCHA Serverless API call
This is the second call to the reCAPTCHA API. Our Gatsby Cloud functions reCAPTCHA call will send the reCAPTCHA token we got in the client. The Google server will respond with a score. That score is on the scale zero to one, with zero indicating it is very likely the user is a bot.
If that's all clear, let's press on and fill out the function body. Edit
In a production application, you would probably want to implement a method for additional user validation if the reCAPTCHA score is below a chosen threshold. Here, we will just pass the score through on the email we send ourselves with the user's message. Talking of email, let's now finish up by writing that final function.
Set up SMTP Email
We're now ready to write out the
sendEmail function. We mentioned earlier that we will use SMTP rather than any particular API to future proof our code. We will need SMTP credentials to be able to send email from the Gatsby Cloud function. As before, in line with best practise, we define those credentials in environment variables. Update
.dev.production with this additional variables:
The precise values you need will depend on your preferred service. The documentation for Sendgrid and Mailgun is linked. If you don't yet have an SMTP server to test with, get free SMTP test credentials from Ethereal .
Moving on swiftly, we will use nodemailer to send email from the lambda function, so let's install it:
Our final step here is to import nodemailer and to fill out the
The email we send here is a basic, plaintext email containing the bare essentials. If you are setting this up for a client, you might consider using an HTML email and formatting it nicely!
For the sake of clarity, let's have an overview of the message submission process:
- The user submits the form on our website.
- The information from the form is them sent by this Gatsby Cloud function. The email address which the function sends from will be whatever you chose for the value
SMTP_SENDERin the environment variables. You will need to have your service configured to send email from that address for this to work.
- Whenever a user submits a message on the contact form, you will receive an email containing the message details. The email is sent to whichever email address you set
CONTACT_EMAILto in your environment variables.
Hope that makes sense, but let me know if that needs more explanation.
Test it out
Well done for making it this far! Let's wrap up by sending a test comment from the front end. You can do this on your local development server, without the need to push your repo to your hosting service. Go to localhost:8000/contact/ and send a test message.
You can see the full code for this Gatsby Cloud Functions reCAPTCHA example on the Rodney Lab GitHub repo.
🙌🏽 How was it for you?
In this post we learned:
- how to use Gatsby functions,
- using reCAPTCHA to check for bots,
- how to send SMTP email using a cloud function.
I hope you have found this valuable and worth the time you invested. Let me know if there is anything in the post that I can improve on, for any one else creating this project. You can leave a comment below, @ me on twitter or try one of the other contact methods listed below.
🙏🏽 Gatsby Cloud Functions reCAPTCHA: Feedback
As I say, I hope you enjoyed following along and creating this project as well as learned something new. I also hope you will use this code in your own projects. I would love to hear what you are building with cloud functions as well as reCAPTCHA. Perhaps you are de-googling and will opt for HCaptcha instead of reCAPTCHA. Hopefully you can leverage something from this post in that endeavour. Let me know about your journey as well as the differences in using the two APIs. Read on to find ways to get in touch, further below. If you have found this post useful, even though you can only afford even a tiny contribution, please consider supporting me through Buy me a Coffee.
Finally, feel free to share the post on your social media accounts for all your followers who will find it useful. As well as leaving a comment below, you can get in touch via @askRodney on Twitter and also askRodney on Telegram . Also, see further ways to get in touch with Rodney Lab. I post regularly on SvelteKit as well as Gatsby JS among other topics. Also subscribe to the newsletter to keep up-to-date with our latest projects.