Published 1/3/2024 · 4 min read
We’ll use Vite to scaffold our Vue 3 project. Vite provides lightning-fast hot module replacement and optimized builds.
Create the Project
npm create vue@latest laravel-vue
When prompted, select:
- ✅ TypeScript (recommended, but optional)
- ✅ Vue Router
- ✅ Pinia
- ✅ ESLint
- ✅ Prettier
cd laravel-vue
npm install
Install Additional Dependencies
We need Axios for API requests:
npm install axios
Configure Environment Variables
Create a .env file in your Vue project root:
VITE_API_URL=http://localhost
Vite exposes environment variables prefixed with VITE_ to your application.
Configure Axios
Create src/services/api.ts:
import axios from "axios";
const api = axios.create({
baseURL: import.meta.env.VITE_API_URL,
withCredentials: true, // Required for Sanctum cookies
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
});
export default api;
The withCredentials: true setting is critical - it allows cookies to be sent with cross-origin requests, which Sanctum needs for session-based authentication.
Configure Pinia Store
Pinia is the official state management solution for Vue 3, replacing Vuex. Create src/stores/auth.ts:
import { defineStore } from "pinia";
import { ref, computed } from "vue";
import api from "@/services/api";
interface User {
id: number;
name: string;
email: string;
email_verified_at: string | null;
is_admin?: boolean;
}
export const useAuthStore = defineStore("auth", () => {
const user = ref<User | null>(null);
const isLoading = ref(false);
const error = ref<string | null>(null);
const isAuthenticated = computed(() => !!user.value);
const isAdmin = computed(() => user.value?.is_admin ?? false);
const isVerified = computed(() => !!user.value?.email_verified_at);
async function fetchUser() {
try {
isLoading.value = true;
const response = await api.get("/api/user");
user.value = response.data;
} catch (e) {
user.value = null;
} finally {
isLoading.value = false;
}
}
async function login(credentials: { email: string; password: string }) {
error.value = null;
try {
// Get CSRF cookie first
await api.get("/sanctum/csrf-cookie");
// Then login
await api.post("/login", credentials);
await fetchUser();
} catch (e: any) {
error.value = e.response?.data?.message || "Login failed";
throw e;
}
}
async function logout() {
await api.post("/logout");
user.value = null;
}
async function register(data: {
name: string;
email: string;
password: string;
password_confirmation: string;
}) {
await api.get("/sanctum/csrf-cookie");
await api.post("/register", data);
await fetchUser();
}
return {
user,
isLoading,
error,
isAuthenticated,
isAdmin,
isVerified,
fetchUser,
login,
logout,
register,
};
});
Configure Vue Router
Update src/router/index.ts:
import { createRouter, createWebHistory } from "vue-router";
import { useAuthStore } from "@/stores/auth";
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: "/",
name: "home",
component: () => import("@/views/HomeView.vue"),
},
{
path: "/login",
name: "login",
component: () => import("@/views/LoginView.vue"),
meta: { guest: true },
},
{
path: "/register",
name: "register",
component: () => import("@/views/RegisterView.vue"),
meta: { guest: true },
},
{
path: "/dashboard",
name: "dashboard",
component: () => import("@/views/DashboardView.vue"),
meta: { requiresAuth: true },
},
],
});
router.beforeEach(async (to) => {
const auth = useAuthStore();
// Try to fetch user if not loaded
if (!auth.user && !auth.isLoading) {
await auth.fetchUser();
}
// Redirect authenticated users away from guest pages
if (to.meta.guest && auth.isAuthenticated) {
return { name: "dashboard" };
}
// Redirect unauthenticated users to login
if (to.meta.requiresAuth && !auth.isAuthenticated) {
return { name: "login", query: { redirect: to.fullPath } };
}
});
export default router;
Start Development Server
npm run dev
Your Vue app will be available at http://localhost:5173.
Project Structure
Your project should now look like this:
src/
├── assets/
├── components/
├── router/
│ └── index.ts
├── services/
│ └── api.ts
├── stores/
│ └── auth.ts
├── views/
│ ├── HomeView.vue
│ ├── LoginView.vue
│ ├── RegisterView.vue
│ └── DashboardView.vue
├── App.vue
└── main.ts
Next up: Testing your API endpoints with Insomnia or Postman.
Related Articles
- Form Handling: Moving from Vue to Svelte
A practical guide to translating Vue form patterns to Svelte, covering two-way binding, validation, async submission, and what actually works better in each framework.
- Building a Modal: Vue vs Svelte
A side-by-side comparison of building a modal component in Vue 3 and Svelte 5, exploring the differences in reactivity, props, and component patterns.
- Using Getters & Setters Vuex
A short article on using the getter and setter pattern to update data held in a Vuex store.