# Easy User Management with Nuxt and Supabase

Managing users and authentication in a web application can be a complex task. In this blog, we will explore how to implement user management using **Nuxt 3** and **Supabase**. [Supabase](https://supabase.com/) is built on top of PostgreSQL and offers CRUD API, Realtime API, and Auth API, making it an ideal choice for our user management needs. While we will mostly focus on the Supabase part of the implementation and skip most of the UI aspects of the application.

## Project Setup

Let's start by setting up a new Nuxt project:

```bash
npx create-nuxt-app nuxt-supabase-auth
cd nuxt-supabase-auth
```

Next, we install the required dependencies, `@nuxtjs/supabase`, and `@supabase/supabase-js`, to easily integrate Supabase with Nuxt:

```bash
npm i
npm install @nuxtjs/supabase --save-dev
npm install @supabase/supabase-js
```

To configure the Nuxt project with Supabase, we modify the `nuxt.config.ts` file:

```ts
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@nuxtjs/supabase'],
})
```

Before we can proceed, we need to create a Supabase project. Visit [https://supabase.com/dashboard/projects](https://supabase.com/dashboard/projects), create an account, and start a new project. Once the project is set up, obtain the `Project URL` and `API Key`. Create a `.env` file in the project's root directory and add the credentials:

```plaintext
SUPABASE_URL="https://example.supabase.co"
SUPABASE_KEY="<your_key>"
```

Replace `SUPABASE_URL` with your `Project URL` and `SUPABASE_KEY` with the corresponding `API Key`. These environment variables will be used by the Nuxt Supabase module to connect to the database.

Update `app.vue` to use the `NuxtPage` component:

```plaintext
// app.vue
<template>
  <NuxtPage />
</template>
```

Next, create a landing page at `pages/index.vue` with a profile button (which redirects users to their profile page) and a logout button:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1690838606695/28f15d9a-101b-4d6d-a61e-ba3dda1fe2e8.png align="center")

For user logout functionality, we utilize the `useSupabaseClient()` composable, composable which gives us access to the [Supabase client](https://supabase.com/docs/reference/javascript/initializing) anywhere in our application.

```plaintext
// pages/index.vue
const client = useSupabaseClient();

const logout = async () => {
  const { error } = await client.auth.signOut();
  console.log('logout')
  if (error) throw error
  navigateTo('/login')
}
```

## User Sign-Up

To implement user sign-up, we create a login page at `pages/login.vue` and add a login form:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1690838674468/a4e80051-bff4-46ef-bcbb-9a711225694e.png align="center")

We introduce a `ref` named `email`, bound to our input tag. Additionally, we create a `computed property`, `ValidEmail`, using regex to validate the email's format.

```ts
// pages/login.vue
const email = ref<string>('');

const ValidEmail = computed<boolean>(() => {
    if (/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(email.value)) {
        return true;
    }
    return false;
})
```

For user sign-in, we use `auth.signInWithOtp`, which sends a **magic link** to the provided email. The user is redirected to the `/confirm` page upon clicking the link. The `loading` and `msg` refs are used to update the UI as needed.

```ts
// pages/login.vue
const client = useSupabaseClient();
const loading = ref<boolean>(false);
const msg = ref<string>('');

const SignInUser = async () => {
    loading.value = true
    const { error } = await client.auth.signInWithOtp({
        email: email.value,
        options: {
            emailRedirectTo: `${location.origin}/confirm`,
        }
    })
    if (error) {
        msg.value = error.message;
        loading.value = false
        throw error
    } else {
        msg.value = "Check your email for the login link!"
    }
    loading.value = false
}
```

Supabase gives us access to [multiple providers](https://supabase.com/docs/guides/auth#providers) which you can use to sign in users. For this example, we would use `signInWithOtp` which logs in an existing user or adds the user to `auth.users` and logs them in if they are new.

We use the `useSupabaseUser()` hook to get the current user object and `watchEffect()` to redirect the user to the homepage upon successful login.

```ts
// pages/login.vue
const user = useSupabaseUser()
watchEffect(() => {
    if (user.value) {
        navigateTo('/')
    }
})
```

Finally, we create the `/confirm` route, where users will be redirected when clicking the magic link:

```plaintext
//pages/confirm.vue
<template>
    <div class="flex w-full justify-center pt-20 text-3xl">
        Redirecting...
    </div>
</template>
  
<script setup>
const user = useSupabaseUser()
watchEffect(() => {
    if (user.value) {
        navigateTo('/')
    }
})
</script>
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1690838987738/339e9b6e-2816-4cea-864a-bb099422d50b.png align="center")

Remember to configure the `Authentication -> URL Configuration -> Redirect URLs` with [`https://localhost:3000/confirm`](https://localhost:3000/confirm) (update [`localhost`](http://localhost) with your domain once deployed).

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1690838799181/6f1e8e27-08d9-4ace-94cf-03f0a464676a.png align="center")

## Middleware

To restrict access to certain routes to only logged-in users, we can use **Nuxt middleware**. Create a `middleware` folder in the root directory and add an `auth.ts` file:

```ts
// middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
    const user = useSupabaseUser();
    if (!user.value) {
        return navigateTo("/login");
    }
});
```

You can add this middleware to any page you want to restrict access to by using:

```plaintext
// pages/index.vue
definePageMeta({
  middleware: 'auth',
});
```

## Profile Table

While Supabase stores some user data under `user.auth`, we may want to store additional user-related data in our own table. Let's create a `profile` table in Supabase:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1690839196126/5133b0c2-4c72-4726-ac65-4d99f456651e.png align="center")

Make the `uid` a foreign key connecting to the `id` column of the `auth.users` table to maintain data integrity.

## Creating Triggers

When a user signs up, we want to automatically create a profile for them. We can automate this using PostgreSQL functions and triggers.

1. Head over to your Supabase dashboard -&gt; "Database" -&gt; "Functions". Create a new function called `create_profile_for_user` with the return type set as a trigger. Use the following SQL code:
    

```sql
begin
insert into
  public.profiles (uid, name, email)
values
  (new.id, "Some Name", new.email);
return new;
end;
```

2. Check "Show advanced settings" and select "SECURITY INVOKER" to ensure the function has the required privileges to access the `auth.users` table.
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1690839672461/75c94f6f-375d-4151-9fce-16f7bdd4775f.png align="center")

3. Now, navigate to "Triggers" and create a new trigger named `create_user_profile_trigger` with the following settings:
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1690839724206/da4c4045-29a5-423a-97a4-ab04499034f9.png align="center")

Make sure to add the function we created as the **Function to Trigger**

## Profile Page

With everything set up, we can now create a profile page to display a user's data. Let's create a `pages/profile.vue`:

```plaintext
// page/profile.vue
definePageMeta({
    middleware: 'auth',


});
const client = useSupabaseClient()
const user = useSupabaseUser();

const userName = ref<string>('');
const userEmail = ref<string>('');

const { data } = await client
    .from('profiles')
    .select(`name, email`)
    .eq('uid', user.value?.id)
    .single()

if (data) {
    userName.value = data.name
    userEmail.value = data.email
}
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1690839475676/cdb81280-a849-4e70-a0fe-81478d22af34.png align="center")

## Typesafety (Optional)

Supabase provides us with auto-generated TypeScript types for our database tables. To generate these types, create a `types/supabase.ts` file and run:

```bash
npx supabase gen types typescript --project-id "$PROJECT_REF" --schema public > types/supabase.ts
```

Replace `$PROJECT_REF` with the Reference ID found in the general settings of your project dashboard.

To use these types, import `Database` type and add it to the client object:

```typescript
import { Database } from '../types/supabase';
const client = useSupabaseClient<Database>();
```

## Conclusion

In this blog, we explored how to set up user management using Nuxt 3 and Supabase. We covered user sign-up, authentication, restricting access to certain routes, and using middleware for enhanced security. Additionally, we automated profile creation for new users using PostgreSQL triggers. By combining the power of Nuxt 3 and Supabase, we've created a robust user management system with an awesome developer experience.

The source code for this tutorial is available here: [https://github.com/AnkushSarkar10/nuxt-supabase-auth](https://github.com/AnkushSarkar10/nuxt-supabase-auth)

Thank you for reading! 🎉
