How To Build A Nuxt 3 Ionic Capacitor Starter App, Supabase Setup and Authentication

Aaron K Saunders - May 18 '22 - - Dev Community

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
Enter fullscreen mode Exit fullscreen mode
  • 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,
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

Sample .env format

SUPABASE_URL= "https://p_ffE-7s6JoHGUMAo.supabase.co"
SUPABASE_ANON_KEY="eyJhbGc.hodygUWbLitlddXjIG9i6XjYO_p_ffE-7s6JoHGUMAo"
Enter fullscreen mode Exit fullscreen mode

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);
});
Enter fullscreen mode Exit fullscreen mode

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;
};
Enter fullscreen mode Exit fullscreen mode

Using the Supabase Client In Application

The APIs Used

// use composable
const supabase = useSupabase();

// make api call
const { user, session, error } = await supabase.auth.signIn({
  email: 'example@email.com',
  password: 'example-password',
})
Enter fullscreen mode Exit fullscreen mode
// use composable
const supabase = useSupabase();

// make api call
const { user, session, error } = await supabase.auth.signUp({
  email: 'example@email.com',
  password: 'example-password',
})
Enter fullscreen mode Exit fullscreen mode
const { error } = await supabase.auth.signOut()
Enter fullscreen mode Exit fullscreen mode

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";
    }
  })
Enter fullscreen mode Exit fullscreen mode

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"]
})
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Source Code

This code on branch "Part-3" in the github repo

GitHub logo aaronksaunders / ionic-capacitor-nuxt-video-app

Ionic Capacitor VueJS Nuxt3 Starter Template

Ionic Capacitor VueJS Nuxt3 Supabase Starter Template


Code For Each Video

There is a seperate branch for each video in the series


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
Enter fullscreen mode Exit fullscreen mode

Development Server

Start the development server on http://localhost:3000

npm run dev
Enter fullscreen mode Exit fullscreen mode

Production

Build the application for production:

npm run build
Enter fullscreen mode Exit fullscreen mode

Locally preview production build:

npm run preview
Enter fullscreen mode Exit fullscreen mode

Checkout the deployment documentation for more information.






. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .