Back to Course |
Vue.js 3 + Laravel 11 + Vite: SPA CRUD

Show User Data and Logout

In this lesson, we will show the user's name and email and will build the Log out button.

user details in the navigation


Composable Method

First, in the auth Composable, let's add a reactive user object.

resources/composables/auth.js:

import { ref, reactive } from 'vue'
import { useRouter } from "vue-router";
 
const user = reactive({
name: '',
email: '',
})
// ...

Then, after successful login, we need to assign the values to that object.

resources/composables/auth.js:

import { ref, reactive, inject } from 'vue'
import { useRouter } from 'vue-router';
 
const user = reactive({
name: '',
email: '',
})
 
export default function useAuth() {
// ...
 
const loginUser = (response) => {
user.name = response.data.name
user.email = response.data.email
 
localStorage.setItem('loggedIn', JSON.stringify(true))
router.push({ name: 'posts.index' })
}
// ...
return { loginForm, validationErrors, processing, submitLogin }
return { loginForm, validationErrors, processing, submitLogin, user }
}

Now, let's build a method to use the API route /user to get the user.

resources/composables/auth.js:

import { ref, reactive, inject } from 'vue'
import { useRouter } from 'vue-router';
 
const user = reactive({
name: '',
email: '',
})
 
export default function useAuth() {
// ...
const loginUser = (response) => {
user.name = response.data.name
user.email = response.data.email
 
localStorage.setItem('loggedIn', JSON.stringify(true))
router.push({ name: 'posts.index' })
}
 
const getUser = () => {
axios.get('/api/user')
.then(response => {
loginUser(response)
})
}
 
return { loginForm, validationErrors, processing, submitLogin, user }
return { loginForm, validationErrors, processing, submitLogin, user, getUser }
}

When this method getUser is called, the user will be reassigned and this method can be called on any page later.

Now, in the main app.js, in the root component, we will have a bit complex structure with the setup method. And inside of this setup we will call the getUser method at the moment of onMounted().

resources/js/app.js:

import './bootstrap';
 
import { createApp } from 'vue'
import { createApp, onMounted } from 'vue'
 
import router from './routes/index'
import VueSweetalert2 from 'vue-sweetalert2';
import useAuth from './composables/auth';
 
createApp({
setup() {
const { getUser } = useAuth()
onMounted(getUser)
}
})
.use(router)
.use(VueSweetalert2)
.mount('#app')

Data from Composable into Vue Component

And now, in the Authenticated layout, we can import auth Composable and get the user from it.

resources/js/layouts/Authenticated.vue:

<template>
// ...
</template>
 
<script setup>
import { computed } from 'vue';
import { useRoute } from 'vue-router'
import useAuth from '@/composables/auth';
 
const route = useRoute()
const { user } = useAuth()
 
const currentPageTitle = computed(() => route.meta.title)
</script>

Let's add the user's name and email in the navigation bar.

resources/js/layouts/Authenticated.vue:

<template>
<div class="min-h-screen bg-gray-100">
<nav class="bg-white border-b border-gray-100">
<!-- Primary Navigation Menu -->
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<div class="flex">
// ...
 
<!-- Navigation Links -->
<div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
<router-link :to="{ name: 'posts.index' }" active-class="border-b-2 border-indigo-400" class="inline-flex items-center px-1 pt-1 text-sm font-medium leading-5 text-gray-900 focus:outline-none focus:border-indigo-700 transition duration-150 ease-in-out">
Posts
</router-link>
<router-link :to="{ name: 'posts.create' }" active-class="border-b-2 border-indigo-400" class="inline-flex items-center px-1 pt-1 text-sm font-medium leading-5 text-gray-900 focus:outline-none focus:border-indigo-700 transition duration-150 ease-in-out">
Create Post
</router-link>
</div>
</div>
<div class="flex items-center">
<div class="flex">
<div>
<div>Hi, {{ user.name }}</div>
<div class="text-sm text-gray-500">{{ user.email }}</div>
</div>
</div>
</div>
</div>
</div>
</nav>
 
// ...
</div>
</template>
 
<script setup>
// ...
</script>

If you log in, you should see your user name and email in the top-right corner.

users name and email


Logout Button

Now let's make the logout button. On the backend part, we will reuse the destroy method from the same AuthenticatedSessionController in Laravel Breeze. But instead of redirecting, we will return a response with no content.

app/Http/Controllers/Auth/AuthenticatedSessionController.php:

use Illuminate\Http\RedirectResponse;
use Symfony\Component\HttpFoundation\Response;
 
class AuthenticatedSessionController extends Controller
{
// ...
public function destroy(Request $request): RedirectResponse|Response
{
Auth::guard('web')->logout();
 
$request->session()->invalidate();
 
$request->session()->regenerateToken();
 
if ($request->wantsJson()) {
return response()->noContent();
}
 
return redirect('/');
}
}

And again, it's the same logic as we did with the login. We need to add the route for the destroy method.

routes/web.php:

Route::post('login', [\App\Http\Controllers\Auth\AuthenticatedSessionController::class, 'store']);
Route::post('logout', [\App\Http\Controllers\Auth\AuthenticatedSessionController::class, 'destroy']);
 
Route::view('/{any?}', 'dashboard')
->where('any', '.*');

In the auth Composable now we need to create a logout method.

resources/js/composables/auth.js:

import { ref, reactive } from 'vue'
import { useRouter } from 'vue-router';
 
const user = reactive({
name: '',
email: '',
})
 
export default function useAuth() {
// ...
 
const logout = async () => {
if (processing.value) return
 
processing.value = true
 
axios.post('/logout')
.then(response => router.push({ name: 'login' }))
.catch(error => {
swal({
icon: 'error',
title: error.response.status,
text: error.response.statusText
})
})
.finally(() => {
processing.value = false
})
}
 
return { loginForm, validationErrors, processing, submitLogin, user, getUser }
return { loginForm, validationErrors, processing, submitLogin, user, getUser, logout }
}

There's nothing new about this method that we haven't done already before. If processing already is happening, we just return, there's nothing to do.

Then, we set processing to true and make an Axios HTTP Post request. If the request is successful we just redirect to the login page.

The only difference from what we did earlier: if there is an error, we show it in the sweetalert modal. And finally set processing to false.

To make the sweetalert work we again need to inject it.

resources/js/composables/auth.js:

import { ref, reactive } from 'vue'
import { ref, reactive, inject } from 'vue'
import { useRouter } from 'vue-router';
 
const user = reactive({
name: '',
email: '',
})
 
export default function useAuth() {
const processing = ref(false)
const validationErrors = ref({})
const router = useRouter()
const swal = inject('$swal')
const loginForm = reactive({
email: '',
password: '',
remember: false
})
 
// ...
return { loginForm, validationErrors, processing, submitLogin, user, getUser }
return { loginForm, validationErrors, processing, submitLogin, user, getUser, logout }
}

Next, we need to use processing and logout in the Authenticated Vue component and add the logout button.

resources/components/layouts/Authenticated.vue:

<template>
<div class="min-h-screen bg-gray-100">
<nav class="bg-white border-b border-gray-100">
<!-- Primary Navigation Menu -->
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<div class="flex">
// ...
 
<!-- Navigation Links -->
<div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
<router-link :to="{ name: 'posts.index' }" active-class="border-b-2 border-indigo-400" class="inline-flex items-center px-1 pt-1 text-sm font-medium leading-5 text-gray-900 focus:outline-none focus:border-indigo-700 transition duration-150 ease-in-out">
Posts
</router-link>
<router-link :to="{ name: 'posts.create' }" active-class="border-b-2 border-indigo-400" class="inline-flex items-center px-1 pt-1 text-sm font-medium leading-5 text-gray-900 focus:outline-none focus:border-indigo-700 transition duration-150 ease-in-out">
Create Post
</router-link>
</div>
</div>
<div class="flex items-center">
<div class="flex">
<div>
<div>Hi, {{ user.name }}</div>
<div class="text-sm text-gray-500">{{ user.email }}</div>
</div>
</div>
<div>
<button @click="logout" type="button" class="inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700 active:bg-gray-900 focus:outline-none focus:border-gray-900 focus:shadow-outline-gray transition ease-in-out duration-150 ml-4"
:class="{ 'opacity-25': processing }"
:disabled="processing">
Log out
</button>
</div>
</div>
</div>
</div>
</nav>
 
// ...
</div>
</template>
 
<script setup>
import { computed } from 'vue';
import { useRoute } from 'vue-router'
import useAuth from '@/composables/auth';
 
const route = useRoute()
const { user } = useAuth()
const { user, processing, logout } = useAuth()
 
const currentPageTitle = computed(() => route.meta.title)
</script>

After refreshing the page, you should see the logout button. After clicking it, you should be logged out successfully and redirected to the login page.

logout button