Technology Stack
Nuxt - The Hybrid Vue Framework - https://v3.nuxtjs.org/
Capacitor - Drop Capacitor into any existing web project, framework or library. Convert an existing React, Svelte, Vue (or your preferred Web Framework) project to native mobile. - https://capacitorjs.com/
Ionic Framework - An open source mobile toolkit for building high quality, cross-platform native and web app experiences. Move faster with a single code base, running everywhere with JavaScript and the Web. - https://ionicframework.com/
Supabase - Supabase is an open source Firebase alternative. Start your project with a Postgres Database, Authentication, instant APIs, Realtime subscriptions and Storage. - https://supabase.com/
This is a series of videos walking you through how to get started with building a mobile application with Nuxt 3 and Ionic Capacitor. In this video, we add Supabase Account Creation and Authentication to the project and deploy it to a mobile device
Setup Supabase
-
Create Project at https://app.supabase.io
- set project name
- set database password
-
Get Project Keys and Database URL, you will need them in the nuxt project configuration
-
SUPABASE_URL
,SUPABASE_ANON_KEY
-
Setup And Configure Nuxt 3 Project
- Install Supabase Javascript Client
npm install @supabase/supabase-js
- Set environment variables, since nuxt 3 has dotEnv built we can set the values in my configuration and read them in from a
.env
file to be used in the nuxt 3 configuration
// nuxt.config.ts
import { defineNuxtConfig } from "nuxt";
// https://v3.nuxtjs.org/api/configuration/nuxt.config
export default defineNuxtConfig({
ssr: false,
dir: {
public: "public",
},
runtimeConfig: {
public: {
SUPABASE_URL: process.env.SUPABASE_URL,
SUPABASE_ANON_KEY: process.env.SUPABASE_ANON_KEY,
},
},
});
Sample .env format
SUPABASE_URL= "https://p_ffE-7s6JoHGUMAo.supabase.co"
SUPABASE_ANON_KEY="eyJhbGc.hodygUWbLitlddXjIG9i6XjYO_p_ffE-7s6JoHGUMAo"
Create Plugin and Composable For Managing Supabase Client
- Create Supabase Client in Project, we will treat this as a plugin so it can run at startup. We will use the
provide
functionality to make the supabase client available to a composable that can be accessed anywhere in the app
// plugins/sb-client.ts
export default defineNuxtPlugin((nuxtApp) => {
const config = useRuntimeConfig();
const supabase = createClient(config.public.SUPABASE_URL, config.public.SUPABASE_ANON_KEY);
// allow us to inject, see composables
// nuxtApp.vueApp.provide('supabase', supabase);
nuxtApp.provide('supabase', supabase);
});
The composable
// composables/useSupabase.ts
import type { SupabaseClient } from "@supabase/supabase-js";
export const useSupabase = (): SupabaseClient => {
const app = useNuxtApp();
const supabase = app.$supabase;
if (!app.$supabase) {
console.log('supabase', supabase);
throw new Error("Supabase Not Initialized Properly");
}
return supabase as SupabaseClient;
};
Using the Supabase Client In Application
The APIs Used
- Supabase Login
// use composable
const supabase = useSupabase();
// make api call
const { user, session, error } = await supabase.auth.signIn({
email: 'example@email.com',
password: 'example-password',
})
- Supabase Create Account
// use composable
const supabase = useSupabase();
// make api call
const { user, session, error } = await supabase.auth.signUp({
email: 'example@email.com',
password: 'example-password',
})
const { error } = await supabase.auth.signOut()
Protect Specific Routes In Nuxt Code
Middleware
You can see in the middleware section below how we can access the state information. Pages can specify what middleware to run before rendering. We need the protected pages to check for a user, using the middleware, before rendering. The middleware will direct the user to the login page of there is no supabase user available
// middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
const client = useSupabase();
if (!client.auth.user()) {
return "/login";
}
})
When added to the page, the middleware is called before the page is rendered; if the user in not authenticated then we redirect back to the Login Page
definePageMeta({
middleware: ["auth"]
})
Index Page - index.vue
<template>
<IonPage>
<IonHeader :translucent="true">
<IonToolbar>
<IonTitle>Home</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent class="ion-padding">
<h1>WELCOME HOME on IOS AND ANDROID</h1>
<IonButton @click="router.push('/about')">
Goto About Page
</IonButton>
<IonButton @click.prevent="doSignOut">
SIGN OUT
</IonButton>
<p>
{{user}}
</p>
</IonContent>
</IonPage>
</template>
<script setup lang="ts">
import {
IonPage,
IonHeader,
IonTitle,
IonToolbar,
IonContent,
IonButton
} from "@ionic/vue"
definePageMeta({
middleware: ["auth"]
})
const router = useRouter();
const client = useSupabase()
const user = client.auth.user();
// function
const doSignOut = async () => {
await client.auth.signOut();
router.replace("/login");
}
</script>
Login Page - login.vue
<template>
<IonPage>
<IonHeader :translucent="true">
<IonToolbar>
<IonTitle>LOGIN PAGE</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent class="ion-padding">
<IonItem>
<IonLabel position="floating">
EMAIL
</IonLabel>
<IonInput type="email" placeholder="Email" v-model="credentials.email"></IonInput>
</IonItem>
<IonItem>
<IonLabel position="floating">
PASSWORD
</IonLabel>
<IonInput type="password" placeholder="*****" v-model="credentials.password"></IonInput>
</IonItem>
<IonButton @click.prevent="doSignIn">
SIGN IN
</IonButton>
<IonButton @click.prevent="$router.push('/create-account')">
CREATE ACCOUNT
</IonButton>
</IonContent>
</IonPage>
</template>
<script setup lang="ts">
import {
IonPage,
IonHeader,
IonTitle,
IonToolbar,
IonContent,
IonButton,
IonItem,
IonInput,
IonLabel,
alertController
} from "@ionic/vue"
const router = useRouter();
const sbClient = useSupabase();
// local state
const credentials = ref<{ email: string, password: string }>({ email: "", password: "" })
// functions
const doSignIn = async () => {
const { email, password } = credentials.value
// make api call
const { user, session, error } = await sbClient.auth.signIn({
email,
password,
})
if (error) {
//https://ionicframework.com/docs/api/alert
const alert = await alertController
.create({
header: 'Error Alert',
subHeader: 'Error Signing In To Supabase',
message: error.message,
buttons: ['OK'],
});
await alert.present();
} else {
const alert = await alertController
.create({
header: 'Success',
subHeader: 'User Logged In To Supabase',
buttons: ['OK'],
});
await alert.present();
console.log(user);
router.replace("/");
}
}
</script>
Source Code
This code on branch "Part-3" in the github repo
aaronksaunders / ionic-capacitor-nuxt-video-app
Ionic Capacitor VueJS Nuxt3 Starter Template
Ionic Capacitor VueJS Nuxt3 Supabase Starter Template
- Blog Post: https://dev.to/aaronksaunders/how-to-build-a-nuxt3-ionic-capacitor-starter-app-4d3k
- Video: https://youtu.be/tDYPZvjVTcc
Code For Each Video
There is a seperate branch for each video in the series
- Part One: https://github.com/aaronksaunders/ionic-capacitor-nuxt-video-app
- Part Two: https://github.com/aaronksaunders/ionic-capacitor-nuxt-video-app/tree/part-2
- Part Three: https://github.com/aaronksaunders/ionic-capacitor-nuxt-video-app/tree/part-3
Look at the nuxt 3 documentation to learn more.
Setup
Make sure to install the dependencies:
# yarn
yarn install
# npm
npm install
# pnpm
pnpm install --shamefully-hoist
Development Server
Start the development server on http://localhost:3000
npm run dev
Production
Build the application for production:
npm run build
Locally preview production build:
npm run preview
Checkout the deployment documentation for more information.
Top comments (5)
Bugs: this does not work and breaks the application
client.auth.user();
Solution: change it to
client.auth.getUser();
Question: When adding device support like
android
orios
there is a small problem which seem to block the app from compiling. did you come across this problem. here is an extract.thanks for comment, tech changes... this post is over 6 months old and based on the previous version of supabase api. The api changed so that is why the code is no longer working. I suspect there is also a version issue with capacitor that you are running into
you are right. but all in all i like this post. it prompted me to create a template using some few things in your post and i have added other thins as well. it would have not been possible if it was not for your post. i will share a link with you when i finalize testing.
any advice.... How to protect API Key for SSR mode
This is maybe late but I think you can use
nuxt.config.ts
file to specify the API key. Like this:Hope this help.