Building a serverless Nuxt app using GraphQL and Hasura Remote Joins
This tutorial was written by Travis Reynolds and published as part of the Hasura Technical Writer Program - an initiative that supports authors who write guides and tutorials for the open source Hasura GraphQL Engine.
In this guide, we will build a simple Instagram-style Nuxt app, where users can upload images, like and also add comments to other people's images. Images will be stored on an API, and we will use Hasura Remote Joins to transform image URLs so we can query the exact-sized images we want from the database, instead of having to transform the URL client side with a dedicated library.
This post is a part of our Remote Joins series. Remote Joins in Hasura allows you to join data across tables and remote data sources. Data Federation using Hasura Remote Joins is now available starting from the v1.3 beta release. Jump on our discord or comment on github and let us know what you think!
Prerequisites
We will be using a pre-built Nuxt app as the frontend, so some experience with VueJS and SSR frameworks will be helpful - although not necessary.
For the backend, we will use:
- Hasura for GraphQL APIs & Remote Joins
- Heroku
- Azure Functions to create a remote schema and events handler,
- Auth0 for authentication
- Cloudinary to host our images.
Let's get started!
Firstly, download the sample repository. In this, we have a couple of folders:
- Migrations - We will use these to setup the tables for our database
- Functions - The Function app we will deploy to Azure
- Uncover - The Nuxt frontend app
Setting up the Database
We will be using Heroku to deploy a Hasura instance. There is already a nice quick start guide on deploying to Heroku - Follow this, but we need to change one thing. Currently, the Remote Joins feature is in beta, so we will need to use the Docker image of the beta release. As of this writing, it is hasura/graphql-engine:v1.3.0
. Make a note of this, then edit the Dockerfile FROM
value to the PR version.
FROM hasura/graphql-engine:v1.3.0
We can now deploy this to Heroku - follow the rest of the quick start guide.
Once Hasura is deployed, follow this next article on securing the Hasura console.
Nice work! We can now visit our app at <app-name>.herokuapp.com/console
. My app is called Uncover App, so I can reach the console at https://uncover-app.herokuapp.com/console. Go ahead and login with your admin secret (from the quick start guide above).
We need to add one more environment variable to Heroku: this will decide what role any unauthorized users will be given - so we can add permissions accordingly:
HASURA_GRAPHQL_UNAUTHORIZED_ROLE = public
Now we need to apply the migrations to Hasura. As we are using the PR docker image, we will need to download the Hasura CLI for that version. Setup Hasura CLI referring to instructions from docs.
I am using Linux, and I have downloaded the CLI to my uncover folder.
Now we can apply these migrations to Hasura - simply run the following command, replacing your endpoint and admin secret:
./cli-hasura-linux-amd64 migrate apply --endpoint <your-endpoint> --admin-secret <your-secret>
This command will take a few seconds to run, and once it is done, you should be able to see all the tables needed for our app in the Hasura console.
Setting Up Auth0
Next we need to setup Auth0 to manage authentication - refer to the official guide to configure this. Add http://localhost:3000/login/callback
as the Allowed Callback URLs and Allowed Web Origins.
Make sure to note the created application ID and Domain - we will need these later.
Cloudinary
We will be using Cloudinary to host our images, and to run transformations on them. Visit their website to create an account. Once you are logged in, make a note of your cloud name.
We will need to create an upload preset to allow unsigned uploads from our app, so click on the settings icon in the navbar. Now select the Upload tab, and scroll down to the Upload presets: tab. Click Enable unsigned uploading, and make a note of the name of the preset that has just been created - it's mode will be Unsigned.
We can now start setting up our functions app, which will run our 'remote' GraphQL server.
I will be using Azure + VSCode for this, however you can use Zeit Now, AWS Lambda, etc.
Azure Setup
If you already have an active account and subscription with Azure, you can skip these next steps.
Visit https://azure.microsoft.com/en-gb/free, and click Start for free. Azure gives you a number of services for free with an active subscription, including 1 million function executions a month - which will be more than enough for our demo app.
You can either create a new Microsoft account, or login to an existing one. I will create a new account for this. Just follow the few steps to add an email address, password, and email verification.
At the next step, you will continue to setup your free account. Fill out the necessary details, then you will need to add a phone number, and a card for verification -
Don't worry, you won't be charged at all for this demo app. You won’t be charged unless you upgrade.
Agree to the T&C's (after carefully reading them through, of course...) and click Sign up. On the next screen, Click Go to the Portal, and you should be greeted with a screen like this:
We can leave this for now, we will be managing our Function app from VS Code.
Functions CLI
To run our function app locally, we will need to install the 'Core Tools' packages. Instruction for this can be found here.
Once installed, open Code, and head to the extensions page. Search for Azure Functions ms-azuretools.vscode-azurefunctions
and install this package. You will notice that another extension that this package requires is also installed, Azure Account.
Once these have finished installing, press F1, and type Sign In. You should see an option called Azure: Sign In - navigate to this, and press enter. A browser window will open, asking you to authenticate with an Azure account. Choose the account we have just setup, and you will be redirected to a page confirming the sign in.
Great, we are all set up! Now we can deploy our Function app.
Open Code in a folder which will hold the function, and select the Azure icon in the left hand menu.
As yet, we haven't added any subscriptions for the extension to use. Click Select Subscriptions, and choose a subscription to show. Mine was called Free Trial, so I selected that, and clicked OK.
You will see your subscription shown in the side panel now. Right click on that, and select Create Function app in Azure. In the popup, name your Function app (note this will also be part of your URL), and select a location. Hit enter, and the extension will set up a complete new Function app for you, configuring all the resource groups/storage accounts etc necessary. You will have to wait a few minutes for the setup to complete, but when it is done, you can deploy your app.
If you want to configure this manually, you can select the Create Function app in Azure (Advanced) create option.
As the functions are built with Webpack, we need to run a build command before deploying. But before that, we need to set some ENVs. Create a new file called local.settings.json
, and paste the following contents into it:
{
"IsEncrypted": false,
"Values": {
"CLOUDINARY_NAME": "<unsigned upload preset name>"
}
}
Open a terminal, cd into our functions folder, and run the below commands:
cd functions
yarn # Or npm i
yarn build # Or npm run build
Now you will see an index.js in our root graphql folder.
Open the Azure sidebar again, and select the blue upload arrow in the Functions section. Once you have clicked this, a popup will appear, asking what folder we want to deploy. Select the functions folder, then select the Functions app you have just created.
Click Deploy in the next confirmation box, and our app will start to deploy our functions.
Once this process has finished, a notification will appear confirming a successful deploy. Make sure to click on View Output, and copy down the Http Trigger URL that is shown - this is the URL to our GraphQL server. Next, we need to upload our local settings (ENVs) to our Function app. Tap F1, and type in Azure Functions: Upload LocalSettings. Hit enter, and make sure to choose the local.settings.json
from our functions folder. Select our function app, and press enter. Now the extension will add the local settings to the remote Function app.
Remote Join
Visit your Hasura Console, and click on the Remote Schemas tab. Click Add, and type in a name for your remote schema - GraphQL will do. Then paste your Function app URL (https://<app-name>.azurewebsites.net/graphql) into the URL input. Now we can click Add Remote Schema, and we have joined our two GraphQL schemas!
Next, click on the data tab. Browse to the images table, and select the Relationships tab. At the bottom of the page, there is a section called Remote Relationships. Here comes the fun part...
Click Add, name the relationship transform_url
, and select our GraphQL remote schema.
Now we have to configure it. We want to use the transform_image_url
resolver, so select that. Now we need to pass it our url to transform. To do so, find the url
argument, and select it. Make sure the columns that appear are set to From Column
and url
- then click save.
Here, we are telling Hasura that we want to use the url
column from our images table as an argument for url
on the remote schema.
Nuxt Frontend
Next step is to run our Nuxt app.
Open the uncover directory in VS Code, and add a new .env
file at the root of the project.
You will need to add the below values:
APP_URL=http://localhost:3000
AUTH0_CLIENT_DOMAIN=<auth0-application-domain>
AUTH0_CLIENT_ID=<auth0-application-id>
APOLLO_ENDPOINT=<hasura-endpoint (https://<app-name>.herokuapp.com/v1/graphql)>
APOLLO_WSS_ENDPOINT=<hasura-wss-endpoint (wss://<app-name>.herokuapp.com/v1/graphql)>
CLOUDINARY_PRESET=<unsigned-upload-preset>
CLOUDINARY_NAME=<account-name>
Now we can run the app!
cd uncover
yarn # or npm i
yarn develop # or npm run develop
You can view the app on http://localhost:3000
.
The GraphQL query that is used to get data from the remote join looks something like this:
query AllImages {
images {
id
created_at
title
description
altText
placeholder: transform_url (width: 300, height: 200, quality: 20, gravity: "center", crop: "fill") # Remote Join Field
card: transform_url (width: 600, height: 400, gravity: "center", crop: "fill") # Remote Join Field
full: transform_url (width: 1200, height: 800, gravity: "center", crop: "fill") # Remote Join Field
user {
profile {
username
last_name
first_name
}
}
}
}
We have a few different queries here. Firstly, we are querying for all images in the database. Then, we fetch data that we will use in our app - for example, the title, description, the user it belongs to, and their profile. Then we query the transform_url
field multiple times.
Firstly, we query it to get a placeholder image url - this is a small and low quality image that can be loaded quickly. It will be shown first as the higher quality image loads (For example, like Medium.com images). Then we query for the card image - this is a higher quality image that will be shown in the timeline. Finally, we have the full image. This will be used in the lightbox when a user clicks on an image.
Summary
Remote Joins is very useful here, as we can query it multiple times under different aliases to get different transformed urls, using a serverless function to do the heavy-lifting. If we did not use a remote join, we would have firstly had to install the Image CDN specific client library, which are often very large - for instance the vue-cloudinary
package will add ~22kb to our Nuxt app. Then when we had queried the image url, we would need to go through each image object, and use the client library to produce the transform_urls each time we view an image - this can take valuable load time.
Instead, we immediately have all the information needed to start showing images.
About the author
Travis Reynolds is a freelance frontend engineer from Portsmouth, UK, but he often tinkers around with NodeJS and GraphQL as well - he likes to learn new things that may help him in future projects. You can check out his infrequently-updated blog at travisreynolds.dev.