---
title: 'Build a User Management App with Nuxt 3'
description: 'Learn how to use Supabase in your Nuxt 3 App.'
---
<$Partial path="uiLibCta.mdx" />
<$Partial path="quickstart_intro.mdx" />

If you get stuck while working through this guide, you can find the [full example on GitHub](https://github.com/supabase/supabase/tree/master/examples/user-management/nuxt3-user-management).
<$Partial path="project_setup.mdx" variables={{ "framework": "nuxt", "tab": "frameworks" }} />
## Building the app
Build the Vue 3 app from scratch.
### Initialize a Nuxt 3 app
We can use [`nuxi init`](https://nuxt.com/docs/getting-started/installation) to create an app called `nuxt-user-management`:
```bash
npx nuxi init nuxt-user-management
cd nuxt-user-management
```
Then install the only additional dependency: [Nuxt Supabase](https://supabase.nuxtjs.org/). We only need to import Nuxt Supabase as a dev dependency.
```bash
npm install @nuxtjs/supabase --save-dev
```
And finally we want to save the environment variables in a `.env`.
All we need are the API URL and the key that you copied [earlier](#get-api-details).
<$CodeTabs>
```bash name=.env
SUPABASE_URL="YOUR_SUPABASE_URL"
SUPABASE_KEY="YOUR_SUPABASE_PUBLISHABLE_KEY"
```
$CodeTabs>
These variables will be exposed on the browser, and that's completely fine since we have [Row Level Security](/docs/guides/auth#row-level-security) enabled on our Database.
Amazing thing about [Nuxt Supabase](https://supabase.nuxtjs.org/) is that setting environment variables is all we need to do in order to start using Supabase.
No need to initialize Supabase. The library will take care of it automatically.
### App styling (optional)
An optional step is to update the CSS file `assets/main.css` to make the app look better.
You can find the full contents of this file [in the example repository](https://github.com/supabase-community/nuxt3-quickstarter/blob/main/assets/main.css).
<$CodeTabs>
```typescript name=nuxt.config.ts
import { defineNuxtConfig } from 'nuxt'
// https://v3.nuxtjs.org/api/configuration/nuxt.config
export default defineNuxtConfig({
modules: ['@nuxtjs/supabase'],
css: ['@/assets/main.css'],
})
```
$CodeTabs>
### Set up Auth component
Set up a Vue component to manage sign-ins and sign-ups. We'll use Magic Links, so users can sign in with their email without using passwords.
<$CodeTabs>
```vue name=/components/Auth.vue
```
$CodeTabs>
### User state
To access the user information, use the composable [`useSupabaseUser`](https://supabase.nuxtjs.org/composables/usesupabaseuser) provided by the Supabase Nuxt module.
### Account component
After a user is signed in we can allow them to edit their profile details and manage their account.
Create a new component called `Account.vue`.
<$CodeTabs>
```vue name=components/Account.vue
```
$CodeTabs>
## Profile photos
Next, add a way for users to upload a profile photo. Supabase configures every project with [Storage](/docs/guides/storage) for managing large files like photos and videos.
### Create an upload widget
Start by creating a new component:
<$CodeTabs>
```vue name=components/Avatar.vue
```
$CodeTabs>
### Launch!
With all the components in place, update `app.vue`:
<$CodeTabs>
```vue name=app.vue
```
$CodeTabs>
Once that's done, run this in a terminal window:
```bash
npm run dev
```
And then open the browser to [localhost:3000](http://localhost:3000) and you should see the completed app.

At this stage you have a fully functional application!
## Add a server route
So far the app authenticates the user on the client. For protected API endpoints or server-rendered data, you need a server route that verifies the session.
[`@supabase/server`](https://supabase.github.io/server/) handles the full flow through a single middleware: it validates the JWT locally (using your project's asymmetric signing keys, no round-trip to the Auth server), attaches an RLS-scoped Supabase client and the user's claims to the request, and rejects unauthenticated requests with a 401 before your handler runs.
```bash
npm install @supabase/server
```
<$CodeTabs>
```typescript name=server/api/profile.get.ts
import { withSupabase } from '@supabase/server/adapters/h3'
import { defineHandler } from 'h3'
export default defineHandler({
middleware: [withSupabase({ auth: 'user' })],
handler: async (event) => {
const { supabase, userClaims } = event.context.supabaseContext
const { data, error } = await supabase
.from('profiles')
.select('username, website, avatar_url')
.eq('id', userClaims.id)
.single()
if (error) {
throw createError({ statusCode: 500, statusMessage: error.message })
}
return data
},
})
```
$CodeTabs>
For an unauthenticated route, pass `auth: 'none'`. For app-wide auth, register `withSupabase({ auth: 'user' })` as a Nuxt server middleware at `server/middleware/supabase.ts` instead. See the [h3/Nuxt adapter docs](https://supabase.github.io/server/adapters/h3) for typing, route overrides, and the full API.