Building a Serverless Webapp with Amplify and Quasar - Day 2
We have been helping a customer to build an internal application using AWS Amplify for all-in-one hosting as well as CI/CD and Quasar as an UI framework. This work has led to a Meet-Up (virtual of course) where I helped as AWS expert.
If you want to follow along, these are my notes from the second session.
I assume that you have setup your environment like I showed in the first article.
If not, clone my repository at branch day1
(git clone --branch day1 https://github.com/toelke/MyFirstQuasarAmplifyApp.git
) and read on how to attach your own AWS account.
Should you already know all about quasar and web applications and are only here for AWS Amplify, click on “Finally. programming” above.
How to enable a new team-member
or: How to start by cloning a full repository
To start with a fresh check-out of a amplify repository, run amplify init
:
Note: It is recommended to run this command from the root of your app directory ? Do you want to use an existing environment? No ? Enter a name for the environment dev Using default provider awscloudformation ? Select the authentication method you want to use: AWS profile For more information on AWS Profiles, see: https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html ? Please choose the profile you want to use new-account Adding backend environment dev to AWS Amplify Console app: dufr2td9r9ied ⠏ Initializing project in the cloud...
The most important answer here is the No to the first question: If you answer “Yes” here, it will try to use an environment from the Amplify App that was rolled out by the initial owner of the code.
Since you will most likely have no access to that account, you will get the error Could not initialize ‘dev’: Access Denied
.
Of course, if we are talking about joining as a member of a team then you will want to answer “Yes”.
You will be asked which environment to use.
I recommend you use the environment “main”.
When everything is set up, you can create your own environment with amplify env add
.
This way you will have your backend to test without disturbing your team-mates.
Some theory about HTTP and dynamic Web Applicatiions
HTTP is a rather old protocol to exchange “hypertext”, that is text containing links to other text. When reading about the original ideas for hypertext I feel that the best implementation right now is Wikipedia: A host of text with cross links to other relevant pages. But I digress.
HTTP is now understood to be the basic protocol of “the internet”1.
It supports a number of operations (which are called “methods”) on content addressed by a path.
A path is expressed as a hierarchy of strings, delimited by /
just as in a file-system.
For example, to get this article, your browser had to GET
the path /2021/03/building-a-serverless-webapp-with-amplify-and-quasar-day-2.html
from our webserver (which AWS is nice enough to rent to us).
The most important HTTP operations are subsumed under the name CRUD
for “Create”, “Read”, “Update” and “Delete”.
With these four operations you can support almost any task.
The methods for C
, R
, U
and D
in HTTP are PUT
, GET
, PUT
, DELETE
respectively.
POST
is also sometimes used for C
and U
.
In the olden days™ any dynamic web application would have all its code on the server2.
Any user-input would be send to the server using a form-submission (either using the GET
or the POST
HTTP verb) which always resulted in a page-reload.
This required an actual application server to run the application code.
Since browsers now come with incredibly amount of javascript power, we switched to having much of the logic on the client.
The javascript code “talks” to HTTP-APIs such as databases more or less directly.
For hosting, you now only need your database, an API layer and some storage to host your static HTML, javascript and CSS from.
We are building a single page application (SPA) which means that all code and logic is encapsulated in a single HTML-file that the browser loads. All reactions to user actions are done in the browser and by exchanging data with APIs.
We will be using GraphQL as API-“Language”.
GraphQL installs a layer above HTTP, so we will not be using exactly HTTP-methods for CRUD.
All GraphQL-transactions POST
a request to the API-Server, even for a R
ead operation.
For storage (S3) we will be using pretty pure CRUD, though.
The directory structure of our project so far
|
The configuration and backend of amplify. See https://docs.amplify.aws/cli/reference/files | |
|
Everything in this directory will be put into the webserver as-is. This is the place to store images, fonts, … See also |
|
|
These files will be packed into the application using webpack.
Where you would access an image in |
|
|
These files will be collected together into something resembling the |
|
|
This is were you put custom components. Depending on how we structure the Photo App, we might put the code here that shows an image. |
|
|
This contains the CSS (or SCSS) of our web-page.
Right now it only contains the |
|
|
This directory exists if you choose to add “i18n” (short for internationalization) to the App. Here we can put translations of our messages later. |
|
|
This directory contains components that build up a page-layout.
If you look at the |
|
|
This contains the content-pages of our App. Currently they are basically empty. |
|
|
This configures the router of Vue. Here we will create “virtual” paths in our web-app, so that a user can share or bookmark a link to an album. The router will figure out from the link which album to show. You can read about the router here. It boils down to “When it says |
|
|
This contains files for the Vuex data store. You can use Vuex to transfer information about the current state of the App between components. |
|
|
This file configures Quasar. |
Finally, programming
If it is hard to follow along my instructions on what to change how, have a look at the diff
between day 1 and day 2.
As a reminder how Vue-files are structured: First there is a <template>
-section which contains the HTML-code for the component.
Next we have a <script>
-section where we can place code.
This section should export default
an object with certain methods (like data
, methods
, …).
Finally, we can have a <style>
-section containing CSS.
See also this introduction in the Quasar documentation.
Remove boilerplate from the frontend
Now to the frontend: Our App is very basic right now and supports no interaction. Also, the navigation is full of helpful links to the quasar documentation and other interesting stuff.
First, let’s remove the big quasar-logo in the middle of the page by editing pages/Index.vue
:
|
→ |
|
I also deleted the referenced file in assets/
.
As I wanted to keep the directory I then added a .keep
file to it.
This is necessary as git can’t track empty directories.
Now delete the component EssentialLink.vue
from the components-directory (again, replacing it with a .keep
-file).
Also remove the complete <q-list>
tag with all it’s content from layouts/MainLayout.vue
:
<q-drawer
v-model="leftDrawerOpen"
show-if-above
bordered
content-class="bg-grey-1"
>
<q-list>
<q-item-label
header
class="text-grey-8"
>
Essential Links
</q-item-label>
<EssentialLink
v-for="link in essentialLinks"
:key="link.title"
v-bind="link"
/>
</q-list>
</q-drawer>
Now delete the import EssentialLink from 'components/EssentialLink.vue'
, the const linksData = ...;
, the item essentialLinks
from the data model and the EssentialLink
from the component object.
Now the <script>
-part of the Vue-file should look like this:
<script>
export default {
name: 'MainLayout',
components: { },
data () {
return {
leftDrawerOpen: false,
}
}
}
</script>
When you take a look at the page now, it should basically look the same. The drawer should be empty and the big Quasar logo should be gone.
Add authentication to the backend
Now we tell amplify that we want to have user authentication:
amplify add auth
Which will ask us some questions:
Using service: Cognito, provided by: awscloudformation The current configured provider is Amazon Cognito. Do you want to use the default authentication and security configuration? Default configuration Warning: you will not be able to edit these selections. How do you want users to be able to sign in? Username Do you want to configure advanced settings? No, I am done. Successfully added auth resource myfirstquasaramplifyb2956c4b locally<>
When we now do an amplify push
, Amplify will create a Cognito Userpool for us.
In my case the userpool is called myfirstquasaramplifyb2956c4b_userpool_b2956c4b-dev
.
Note the -dev
at the end.
This is the name of my current backend.
We will see that with all the resources we are creating.
When I create a user in my userpool later, that user will only be valid for my environment -dev
.
This ensures that I do not come into conflict with things in production (main environment) or with infrastructure of my team-mates.
If you like, you can now commit all changes (git add . && git commit -m 'added auth'
) and push them (git push
).
In a few minutes time you should see that the userpool for -main
is created when Amplify runs the build as configured in the amplify.yml
.
Add authentication to the frontend
We start by installing two new dependencies:
npm install aws-amplify @aws-amplify/ui-vue
aws-amplify
is the library that we will use to communicate with Amplify resources such as Authentication or the API without having to program against the underlying AWS services.
@aws-amplify/ui-vue
is a collection of Vue components we can use in our App.
We will be using the authentication components.
See here for documentation.
When our App starts, we need to initialize the Amplify-library.
To get cleaner code, we will encapsulate much of the calls to Amplify into their own file in a new directory called services
.
Create a file src/services/cloud.js
:
import awsconfig from '../aws-exports';
import Amplify from '@aws-amplify/core';
import { Auth } from '@aws-amplify/auth';
export const configCloud = () => {
Amplify.configure(awsconfig);
Auth.configure(awsconfig);
};
We import the configuration file Amplify created for us and two libraries.
The function configCloud
will be called to initialize the Library when our App starts.
So open the file App.vue
and change it to be:
<template>
<div id="q-app">
<router-view />
</div>
</template>
<script>
import { configCloud } from "src/services/cloud";
export default {
name: "App",
mounted() {
configCloud();
},
};
</script>
Now we can begin to use the Amplify library.
Add this import to the <script>
-section of MainLayout.vue
:
import { onAuthUIStateChange } from "@aws-amplify/ui-components";
Also add two variables to the data
-function: loggedIn: false
and user: ""
.
These will store whether a user is logged in or not.
Add a new block next to data()
:
created() {
this.unsubscribeAuth = onAuthUIStateChange((authState, authData) => {
if (authState == "signedin") {
this.loggedIn = true;
this.user = authData.username;
} else {
this.loggedIn = false;
this.user = "";
}
});
},
beforeDestroy() {
this.unsubscribeAuth();
},
The function created()
will be called when our page is started.
It subscribes to updates of the Auth module of Amplify.
It will receive authState
s like “signedin
” when the user has successfully signed in or “signin
” when the user should be asked for his sign in information.
We use this information here to set the two state-variables loggedIn
and user
.
Likewise the function beforeDestroy()
is called when the component is deactivated and we use it to clear our subscription.
For our next trick, we will simply import AWS’ authentication-UI and install that in our page. Put the import
import '@aws-amplify/ui-vue';
at the top of the <script>
-section, add
formFields: [{ type: "email" }, { type: "password" }],
into the data of the component (I put it just after the user: "",
-line) and
<amplify-authenticator v-if="!loggedIn" username-alias="email">
<amplify-sign-up slot="sign-up" username-alias="email" :form-fields.prop="formFields" />
</amplify-authenticator>
just above the <q-page-container>
-component in the <template>
-section.
As you can see, we only show this component if loggedIn
is false, so if no user is logged in.
We configure the sign-up dialog to only ask for email and password, otherwise it will also ask for a phone number.
Add v-if="loggedIn"
to the <q-page-container>
to only show this if the user is logged in.
When you now open the page (run amplify serve
for a development-server), you should be asked for you password.
You can now register a user.
AWS will send you a confirmation mail.
If all of this is successful, you no longer see the login screen.
Next, we will do something so that it’s more evident that the login was successful.
A logout-button for example!
Add this block to the end of the <q-toolbar>
:
<div class="absolute-right" v-if="loggedIn">
{{ this.user }}
<q-btn @click="logout()" flat label="LogOut" class="right" />
</div>
This will show the current user and a logout button if a user is logged in and nothing otherwise.
When the logout-button is clicked the function logout()
is called; we should create that next.
Add a new block methods
to the export:
methods: {
async logout() {
const stat = await auth_logout();
if (stat.status == 'ok') {
this.loggedIn = false;
}
}
}
This calls a function auth_logout
that we will add to our cloud.js
and import:
// in MainLayout.vue
import { auth_logout } from 'src/services/cloud';
// in cloud.js
export const auth_logout = async () => {
try {
await Auth.signOut();
const result = await Auth.currentUserInfo();
return { status: 'ok', payload: {} };
} catch (error) {
return { status: 'error', payload: {} };
}
};
Now it is possible to register in our App, to log in and to log out again. This is enough for today :-D Next week we will add actual UI elements, perhaps even put our first data into storage. If you want to join us, take a look at our MeetUp page!
See you in the next article.
-
See https://twitter.com/brouhaha/status/1232139606981636096 ↩︎
-
I am deliberately ignoring Java Applets and Flash. ↩︎