Skip to main content
The Service Orders Management System uses Pinia for state management. Pinia is the official state management library for Vue 3 and is integrated into Nuxt 3 via the @pinia/nuxt module.

Pinia Integration

Pinia is configured in nuxt.config.ts as a module:
nuxt.config.ts
export default defineNuxtConfig({
  modules: [
    '@pinia/nuxt',
    // ... other modules
  ],
})
With @pinia/nuxt, all stores are automatically imported and available throughout your application without manual imports.

Authentication Store

The auth store manages user authentication state, including the current user and access token.

Store Implementation

stores/auth.ts
import { defineStore } from 'pinia'

export const useAuthStore = defineStore('auth', {
    state: () => ({
        user: null,
        token: null as string | null
    }),

    actions: {
        setUser(userData: any, token: string) {
            this.user = userData
            this.token = token
            localStorage.setItem('access_token', token)
        },
        logout() {
            this.user = null
            this.token = null
            localStorage.removeItem('access_token')
        },
        initializeAuth() {
            const storedToken = localStorage.getItem('access_token')
            if (storedToken) {
                this.token = storedToken
            }
        }
    }
})

Store Structure

The state function returns the initial state object:
state: () => ({
    user: null,           // Current user data
    token: null          // JWT access token
})
State properties:
  • user: Stores user information after successful login
  • token: JWT token for API authentication
Actions are methods that can modify the state:setUser(userData, token)
  • Stores user data and token in state
  • Persists token to localStorage
  • Called after successful login
logout()
  • Clears user and token from state
  • Removes token from localStorage
  • Called when user logs out
initializeAuth()
  • Restores token from localStorage on app load
  • Maintains authentication across page refreshes

Using Stores in Components

In Pages

Here’s how the auth store is used in the login page:
pages/index.vue
<script setup>
import { useRouter } from 'vue-router';
import { ref } from 'vue';
import { useAuthStore } from '~/stores/auth';

const form = ref({
    email: '',
    password: ''
})

const api = useApi()

const checkLogin = async () => {
    validateForm()
    
    if (!formFlag.value) {
        return
    }
    
    await api.post('/login', form.value).then(
        (response) => {
            // Store the token in localStorage
            localStorage.setItem('access_token', response.data.access_token)
            window.location.href = '/serviceorders'
        }).catch(
            (error) => {
                if (error.response.data.message) {
                    alert(`Validacion Incorrecta: ${error.response.data.message}`)
                }
                else {
                    alert(`Validacion Incorrecta`)
                }
                
                form.value.email=""
                form.value.password=""
        })
}
</script>

In Middleware

The auth store is used in route middleware to protect authenticated routes:
middleware/auth.ts
import { useAuthStore } from "~/stores/auth"

export default defineNuxtRouteMiddleware((to, from) => {
    const auth = useAuthStore()
    if (!auth.token) {
        return navigateTo('/')
    }
})

onMounted(() => {
    const auth = useAuthStore()
    auth.initializeAuth()
})
How it works:
  1. Middleware checks if user has a token
  2. If no token exists, redirects to login page
  3. On mount, restores token from localStorage

In Layouts

The logout functionality in the default layout uses the auth store:
layouts/default.vue
<template>
  <div class="row">
    <!-- Header content -->
    <div class="col-2 p-5 d-flex justify-content-end header">
      <div class="btn-group dropstart">
        <button type="button" class="btn dropdown-toggle" data-bs-toggle="dropdown">
          <img src="../public/user-regular.svg" alt="" style="width: 32px; height: 32px;">
        </button>
        <ul class="dropdown-menu">
          <li>
            <a class="dropdown-item" href="#" @click="logout()">
              <img src="../public/right-from-bracket-solid.svg" alt="">
              Cerrar Sesión
            </a>
          </li>
        </ul>
      </div>
    </div>
  </div>
  <slot />
</template>

<script setup>
const api = useApi()

const logout = async () => {
    await api.post('/logout')
    localStorage.removeItem('access_token')
    window.location.href = '/'
}
</script>

Store Best Practices

Keep State Minimal

Only store data that needs to be shared across multiple components

Use Actions

Always modify state through actions, never directly mutate state

Persist Critical Data

Use localStorage for data that should survive page refreshes (like tokens)

Initialize on Load

Restore persisted state when the app loads using initialization actions

Common Patterns

Accessing Store State

<script setup>
const authStore = useAuthStore()

// Access state directly
console.log(authStore.token)
console.log(authStore.user)
</script>

Calling Store Actions

<script setup>
const authStore = useAuthStore()

// Call actions
authStore.setUser(userData, token)
authStore.logout()
authStore.initializeAuth()
</script>

Reactive Store State in Templates

<template>
  <div v-if="authStore.token">
    Welcome, {{ authStore.user?.name }}
  </div>
</template>

<script setup>
const authStore = useAuthStore()
</script>

Creating New Stores

To create a new store, follow this pattern:
stores/newStore.ts
import { defineStore } from 'pinia'

export const useNewStore = defineStore('storeName', {
    state: () => ({
        // Define your state properties
        data: null,
        loading: false,
        error: null
    }),

    getters: {
        // Define computed properties
        hasData: (state) => state.data !== null
    },

    actions: {
        // Define methods that modify state
        async fetchData() {
            this.loading = true
            try {
                const api = useApi()
                const response = await api.get('/endpoint')
                this.data = response.data
            } catch (err) {
                this.error = err
            } finally {
                this.loading = false
            }
        }
    }
})
Store naming convention: Use useXxxStore for the function name and a descriptive string for the store ID.

Next Steps

Composables

Learn about reusable composable functions

API Integration

Understand API calls and authentication

Build docs developers (and LLMs) love