How To Build A Nuxt 3 Ionic Capacitor Starter App, Supabase Setup and Authentication
Tags: nuxt, ionic, supabase, vue
Published: 2022-05-18 20:28:50

## 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

{% youtube SgnNFiMlHJE %}
## Setup Supabase

- Create Project at **[https://app.supabase.io](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
```jsx
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

```javascript
// 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
```tsx
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](https://v3.nuxtjs.org/guide/directory-structure/plugins) so it can run at startup. We will use the `provide` functionality to make the supabase client available to a [composable](https://v3.nuxtjs.org/guide/directory-structure/composables/) that can be accessed anywhere in the app

```javascript
// 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

```javascript
// 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
    - [https://supabase.com/docs/reference/javascript/auth-signin](https://supabase.com/docs/reference/javascript/auth-signin)

```javascript
// 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
    - [https://supabase.com/docs/reference/javascript/auth-signup](https://supabase.com/docs/reference/javascript/auth-signup)

```javascript
// use composable
const supabase = useSupabase();

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

- Sign Out
    - [https://supabase.com/docs/reference/javascript/auth-signout](https://supabase.com/docs/reference/javascript/auth-signout)
    

```javascript
const { error } = await supabase.auth.signOut()
```


## Protect Specific Routes In Nuxt Code

### Middleware
You can see in the [middleware](https://v3.nuxtjs.org/guide/directory-structure/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

```javascript
// 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

```javascript
definePageMeta({
  middleware: ["auth"]
})
```

### Index Page - index.vue
```javascript
<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

```jsx
<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
{% github aaronksaunders/ionic-capacitor-nuxt-video-app  %}