Offline-first made easy with GraphQL, Amplify DataStore and Vue

  • Introduction to Offline-first, PWAs and Amplify DataStore
  • Setting up a new project with the Vue CLI
  • Creating a new GraphQL API
  • Amplify DataStore: setup, data models and usage
  • Creating the UI with Vue: the chatroom, sending and deleting messages; and doing real-time with subscriptions
  • Making Chatty a PWA
  • Adding a PWA custom configuration
  • Improving UX while offline
  • Publishing your app via the AWS Amplify Console
  • Installing Chatty in the Desktop and Mobile
  • Cleaning up cloud services

Introduction to Offline-first, PWAs and Amplify DataStore

Offline-first is an approach to software development, where an application is built to function, with or without an internet connection. Using this approach, data is stored locally on the user’s device and periodically uploaded and replicated into the cloud. Features include:

  • Offline ready: application works offline. This usually comes with some limitations regarding feature set and device capabilities. Eg: PWAs require an install step where the user needs to be online to download all the necessary application assets.
  • Great user experience: application loads fast and transitions seamlessly from online to offline improving user retention. The user is in control regarding app updates, data synchronisation, data conflict resolution and connectivity. Eg: App shell; user is informed about connectivity changes.
  • Native-like features: application behaves similar to a native app. Eg: PWAs are able to run on multiple operating systems as standalone applications and don’t require the user to interact with the browser.
  • Reliable storage: user can exit the application at any moment without losing data. Application stores user data securely on-device and synchronises with the cloud transparently when possible.

Progressive Web Apps

Progressive Web Apps combine the benefits from native apps, while being able to run on more platforms, leveraging browser technologies. PWA features allow Web apps to close the gap with native applications while creating similar user experiences. PWA features:

  • Works offline via service worker.
  • Great performance via app shell and pre-cached assets.
  • Access to device APIs via Web APIs.
  • Supports push notifications via service worker.
  • Works in Web, Mobile and Desktop via Web app manifest.

Amplify DataStore

Amplify DataStore, is an on device persistent repository for interacting with local data, and able to automatically synchronise via GraphQL. Using Amplify DataStore, you can provide a great offline-first experience for your users, while using a simple programming model.

  • In offline mode, we use the DataStore API to manage the data on the device via the Storage Engine using GraphQL. The local Data Store being used will vary depending on the user device. This can either be IndexedDB in the browser or SQL Lite in Android and iOS.
Amplify DataStore data flow in offline mode
  • In online mode, Amplify DataStore, will synchronise with the cloud using the Sync Engine via GraphQL to automatically synchronise with your GraphQL API. At the query level, this is implemented as an AWS AppSync resolver accessing Amazon DynamoDB, AWS Lambda or Amazon ElasticSearch.
Amplify DataStore data flow in online mode
  • Optimistic concurrency, this mode rejects any incoming changes if there’s a mismatch in the tracking version between the incoming change and the item being changed. A further step on the client is required to resolve the conflict.
  • Auto merge (default), this mode doesn’t require further steps to resolve conflicts. It accommodates mutations on a field-by-field basis as long as scalar types don’t clash. If they do, they are ignored but all other fields are merged, if possible.
  • Custom, this mode uses AWS Lambda to allow more control and complex use cases.

Setting up a new project with the Vue CLI

Before moving to the next section, please complete the steps described in “Build your first full-stack serverless app with Vue”. Here you will set up the initial project, familiarise with Amplify CLI and add an authorisation flow so users can register themselves via an automated verification code sent to their email and login.

Creating a new GraphQL API

First, we are going to create the GraphQL API to service Chatty chatroom. To create it, we will use the following command:

amplify add api
  • Please select from one of the below mentioned services GraphQL
  • Provide API name: ChattyAPI
  • Choose the default authorization type for the API API key
  • Enter a description for the API key: (empty)
  • After how many days from now the API key should expire (1–365): 7
  • Do you want to configure advanced settings for the GraphQL API Yes, I want to make some additional changes.
  • Configure additional auth types? No
  • Configure conflict detection? Yes
  • Select the default resolution strategy Auto Merge
  • Do you want to override default per model settings? No
  • Do you have an annotated GraphQL schema? No
  • Choose a schema template: Single object with fields (e.g., “Todo” with ID, name, description)
  • Do you want to edit the schema now? Yes
type Chatty @model {
id: ID!
user: String!
message: String!
createdAt: AWSDateTime
}

Pushing your GraphQL API to the cloud

Let’s run the push command to create the GraphQL API:

amplify push
  • Are you sure you want to continue? Yes
  • Do you want to generate code for your newly created GraphQL API Yes
  • Choose the code generation language target javascript
  • Enter the file name pattern of graphql queries, mutations and subscriptions src/graphql/**/*.js
  • Do you want to generate/update all possible GraphQL operations — queries, mutations and subscriptions Yes
  • Enter maximum statement depth [increase from default if your schema is deeply nested] 2
amplify console api
  • Please select from one of the below mentioned services GraphQL

Amplify DataStore setup

Run the command below to install the necessary dependencies:

npm install --save @aws-amplify/core @aws-amplify/datastore

Data model generation

Generate the data models to manage our messages for our ChattyAPI.

amplify codegen models
<amplify-app>
|_ src
|_ models

Creating a message

Now that the GraphQL API and data models are created, we can begin using the DataStore API. The first thing we’ll do is create a new message passing the generated data model to save.

import { DataStore } from "@aws-amplify/datastore";
import { Chatty } from "./models";
await DataStore.save(new Chatty({
user: "amplify-user",
message: "Hi everyone!",
createdAt: new Date().toISOString()
}))

Querying data

Let’s see how we can query the data using Amplify DataStore. In order to query our data model we will use a query and a predicate to indicate that we want all records.

import { DataStore, Predicates } from "@aws-amplify/datastore";
import { Chatty } from "./models";
const messages = await DataStore.query(Chatty, Predicates.ALL);

Creating the UI with Vue

Now, let’s look at how we can create the UI to create and display messages for Chatty chatroom.

<template>
<div v-for="message of sorted" :key="message.id">
<div>{{ message.user }} - {{ moment(message.createdAt).format('YYYY-MM-DD HH:mm:ss')}})</div>
<div>{{ message.message }}</div>
</div>
</template>
<script>
import { DataStore, Predicates } from "@aws-amplify/datastore";
import { Chatty } from "./models";
import moment from "moment";
export default {
name: 'app',
data() {
return {
user: {},
messages: [],
}
},
computed: {
sorted() {
return [...this.messages].sort((a, b) => -a.createdAt.localeCompare(b.createdAt));
}
},
created() {
// authentication state managament
onAuthUIStateChange((state, user) => {
// set current user and load data after login
switch (state) {
case AuthState.SignedIn: {
this.user = user;
this.loadMessages();
break;
}
}
});
},
methods: {
moment: () => moment(),
loadMessages() {
DataStore.query(Chatty, Predicates.ALL).then(messages => {
this.messages = messages;
});
},
}
}
</script>

Creating a message

Now, let’s look at how we create new messages.

<template>
<form v-on:submit.prevent>
<input v-model="form.message" placeholder="Enter your message..." />
<button @click="sendMessage">Send</button>
</form>
</template>
<script>
export default {
data() {
return {
form: {},
};
},
methods: {
sendMessage() {
const { message } = this.form
if (!message) return;
DataStore.save(new Chatty({
user: this.user.username,
message: message,
createdAt: new Date().toISOString()
})).then(() => {
this.form = { message: '' };
this.loadMessages();
}).catch(e => {
console.log('error creating message...', e);
});
},
}
}
</script>

Deleting all messages

One of the main advantages of working using Amplify DataStore is being able to run a series of GraphQL mutations without having to use a series of individual operations. See below how we can use delete together with a predicate to remove all messages.

DataStore.delete(Chatty, Predicates.ALL).then(() => {
console.log('messages deleted!');
});

Real-time with GraphQL subscriptions

Next, let’s see how we can create a GraphQL subscription. To do so, we will listen for changes of data in our API, and update the state whenever a new piece of data comes through. When the component is destroyed, we will unsubscribe to avoid memory leaks.

<script>
export default {
data() {
return {
subscription: undefined;
};
},
created() {
//Subscribe to changes
this.subscription = DataStore.observe(Chatty).subscribe(msg => {
console.log(msg.model, msg.opType, msg.element);
this.loadMessages();
});
},
destroyed() {
if (!this.subscription) return;
this.subscription.unsubscribe();
},
}
</script>
Real-time synchronisation using Chrome and Firefox clients side-by-side.

Making Chatty a PWA

The PWA CLI plugin can help us making Chatty a PWA by running a single command:

vue add @vue/pwa
Service worker serving assets from the cache while offline
yarn build
└── dist
├── css
│ └── app.<version>.css
├── img/icons
│ ├── android-chrome-<size>.png
│ └── ...
├── js
│ ├── app.<version>.png
│ └── ...
├── favicon.ico
├── index.html
├── manifest.json
├── precache-manifest.<version>.json
├── robots.txt
└── service-worker.js
cd dist
python -m SimpleHTTPServer 8887 // open localhost:8887
Chatty app while offline.

Adding a PWA custom configuration

Let’s fix the issue with our logo by using a custom configuration. Create vue.config.js with the following content at the root of your project:

// vue.config.js
const manifest = require('./public/manifest.json')
module.exports = {
pwa: {
name: manifest.short_name,
themeColor: manifest.theme_color,
msTileColor: manifest.background_color,
appleMobileWebAppCapable: 'yes',
appleMobileWebAppStatusBarStyle: 'black',
workboxPluginMode: 'InjectManifest',
workboxOptions: {
swSrc: 'src/service-worker.js',
}
}
}
// src/service-worker.js
workbox.core.setCacheNameDetails({ prefix: 'amplify-datastore' })
workbox.core.skipWaiting()
workbox.core.clientsClaim()
const cacheFiles = [{
"revision": "e653ab4d124bf16b5232",
"url": "https://aws-amplify.github.io/img/amplify.svg"
}]
self.__precacheManifest = cacheFiles.concat(self.__precacheManifest || [])
workbox.precaching.precacheAndRoute(self.__precacheManifest, {})

Improving UX while offline

We are almost done. Our app can survive offline reloads, can store messages while offline and can synchronise when back online thanks to Amplify DataStore.

// <div v-if="offline">You are offline.</div>
// <div v-bind:class="{ offline: offline }">
// App.vue
import { Hub } from 'aws-amplify';
export default {
data() {
return { offline: undefined };
},
created() {
this.listener = Hub.listen('datastore', {payload:{event}} => {
if (event === 'networkStatus') {
this.offline = !data.active;
}
})
}
}
Chatty PWA informing the user of changes in the network status.

Publishing your app via the AWS Amplify Console

The first thing you need to do is create a new repo for this project. Once you’ve created the repo, copy the URL for the project to the clipboard and initialise git in your local project:

git init
git remote add origin repo@repoofyourchoice.com:username/project-name.git
git add .git commit -m 'initial commit'git push origin master
AWS Amplify Console deployment steps.

Installing the Chatty app in the Desktop and Mobile

Due to the inclusion of the Web app manifest in our index.html page, you can now install the Chatty app in the Desktop and Mobile devices in Windows, Mac and Linux. The way it works, changes slightly depending the device and operating system you are using. Try it out following the steps in Add to Home screen.

Use add to Home screen to install in Desktop and Mobile.

Cleaning up cloud services

If at any time, you would like to delete a service from your project and your AWS Account, you can do this by running:

amplify delete

Conclusion

Congratulations! You successfully built your first offline-first app using Vue and Amplify. You created a chatroom PWA that stores data on the device while offline and when online synchronises in real-time with other devices using Amplify DataStore, AWS AppSync, GraphQL and Amazon DynamoDB. Thanks for following this tutorial.

Thanks for reading!

Have you got any questions about this tutorial or AWS Amplify? Feel free to reach me anytime at @gerardsans.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Gerard Sans

Gerard Sans

Helping Devs to succeed #AI #web3 / ex @AWSCloud / Just be AWSome / MC Speaker Trainer Community Leader @web3_london / @ReactEurope @ReactiveConf @ngcruise