DEV Community

Aaron K Saunders
Aaron K Saunders

Posted on

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

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.

Top comments (5)

Collapse
 
mitch1009 profile image
Mitch Chimwemwe Chanza • Edited

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 or ios there is a small problem which seem to block the app from compiling. did you come across this problem. here is an extract.

[ERROR] An error occurred while running subprocess npm.

        npm i -E @capacitor/android@4.6.1 exited with exit code 1.

        Re-running this command with the --verbose flag may provide more information.
Enter fullscreen mode Exit fullscreen mode
Collapse
 
aaronksaunders profile image
Aaron K Saunders

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

Collapse
 
mitch1009 profile image
Mitch Chimwemwe Chanza

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.

Collapse
 
craxtrader profile image
Crax Trader • Edited

any advice.... How to protect API Key for SSR mode

Collapse
 
muadzmr profile image
Mu'adz Mohd Rosli

This is maybe late but I think you can use nuxt.config.ts file to specify the API key. Like this:

export default defineNuxtConfig({
  ...
  runtimeConfig: {
    apiKey: process.env.API_KEY,

    public: {
      // do not put it here
    }
  },
  ...
});
Enter fullscreen mode Exit fullscreen mode

Hope this help.