Tanpa berlama-lama. Yuk, langsung kita mulai mengerjakan fitur login pada project yang dibuat menggunakan Vue 3 sebagai frontend dan Laravel 10 sebagai backend.
Lakukan instalasi project
Kalau belum tahu caranya melakukan instalasi, silahkan simak postingan ini:
- Install Laravel: Coming soon
Struktur project
Dalam case ini, folder frontend dan backend berada pada folder yang sama. Ini adalah struktur project yang saya buat (opsional):
🔵 Backend: Laravel 10 (folder backend/)
backend/
├── app/
│ └── Http/
│ ├── Controllers/
│ │ └── Api/
│ │ └── AuthController.php ← Controller login
│ └── Middleware/
│ └── VerifyCsrfToken.php ← Bypass CSRF untuk /api/login
├── config/
│ ├── cors.php ← Atur CORS
│ └── session.php ← Atur same_site=lax
├── routes/
│ └── api.php ← Route untuk /api/login
├── .env ← Konfigurasi Sanctum + Session
🟠Frontend: Vue 3 (folder frontend/)
frontend/
├── src/
│ ├── api/
│ │ └── index.js ← Axios instance setup
│ ├── stores/
│ │ └── auth.js ← Pinia Auth Store
│ ├── views/
│ │ └── admin/
│ │ └── Login.vue ← Halaman Login Admin
│ ├── router/
│ │ └── index.js ← Routing frontend
│ ├── layouts/
│ │ └── AdminLayout.vue ← Layout admin setelah login
├── package.json
Daftar Library yang Harus Diinstall (dengan Penjelasan)
🔵 Backend: Laravel 10
Saat memulai Laravel backend, kita membutuhkan library tambahan:
Laravel Sanctum
Kegunaan:
-
Untuk API token authentication yang simple, ringan, aman.
-
Mengelola login/logout token di API berbasis SPA seperti Vue 3.
Cara install:
composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
🟠Frontend: Vue 3 (Vite)
Saat setup Vue 3 frontend, kita butuh beberapa library:
Axios
Kegunaan:
-
Untuk melakukan HTTP request dari frontend ke backend API.
Cara install:
npm install axios
Vue Router
Kegunaan:
- Untuk mengelola navigasi halaman SPA (Single Page Application).
Cara install:
npm install vue-router@4
Pinia
Kegunaan:
- Untuk State Management, seperti login status (token, user) yang digunakan di banyak halaman.
Cara install:
npm install pinia
Tailwind CSS (opsional)
Kegunaan:
- Untuk mempercepat styling halaman tanpa bikin CSS manual dari awal.
- Misal: form login, tombol, layout admin.
Cara install:
npm install -D tailwindcss@3.4.1 postcss autoprefixer
npx tailwindcss init -p
Recap Instalasi yang Harus Dilakukan
| Platform | Library | Command |
|---|---|---|
| Backend | Laravel Sanctum |
|
| Frontend | Axios | npm install axios |
| Frontend | Pinia | npm install pinia |
| Frontend | Vue Router | npm install vue-router@4 |
| Frontend | Tailwind CSS (styling) |
|
| Frontend | (Optional) Heroicons | npm install @heroicons/vue |
Penulisan Baris Kode
A. Backend (Laravel 10)
directory: backend/app/Http/Controllers/Api/AuthController.php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use App\Models\User;
class AuthController extends Controller
{
public function login(Request $request)
{
$user = User::where('email', $request->email)->first();
if (! $user) {
return response()->json(['message' => 'Email tidak ditemukan'], 404);
}
if (!Hash::check($request->password, $user->password)) {
return response()->json(['message' => 'Password salah'], 403);
}
$token = $user->createToken('api-token')->plainTextToken;
return response()->json([
'message' => 'Login berhasil',
'user' => $user,
'token' => $token,
]);
}
}
directory: backend/routes/api.php
use App\Http\Controllers\Api\AuthController;
Route::post('/login', [AuthController::class, 'login']);
directory: backend/app/Http/Middleware/VerifyCsrfToken.php
protected $except = [
'api/login',
];
directory: backend/.env
SANCTUM_STATEFUL_DOMAINS=localhost:5173
SESSION_DOMAIN=localhost
APP_URL=http://localhost:8000
directory: backend/config/cors.php
'paths' => [
'api/*',
'sanctum/csrf-cookie',
],
'allowed_origins' => [
'http://localhost:5173',
],
'allowed_headers' => ['*'],
'allowed_methods' => ['*'],
'supports_credentials' => true,
directory: backend/config/session.php
'same_site' => 'lax',
B. Frontend (Vue 3)
directory: frontend/src/api/index.js
import axios from 'axios';
const api = axios.create({
baseURL: "http://localhost:8000",
withCredentials: true,
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
});
api.interceptors.request.use((config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
}, (error) => {
return Promise.reject(error);
});
export default api;
directory: frontend/src/stores/auth.js
import { defineStore } from 'pinia';
import api from '@/api';
export const useAuthStore = defineStore('auth', {
state: () => ({
user: null,
token: localStorage.getItem('token') || '',
}),
getters: {
isAuthenticated: (state) => !!state.token,
},
actions: {
async login(credentials) {
try {
await api.get('/sanctum/csrf-cookie');
const response = await api.post('/api/login', credentials);
this.token = response.data.token;
this.user = response.data.user;
localStorage.setItem('token', this.token);
localStorage.setItem('user', JSON.stringify(this.user));
api.defaults.headers.common['Authorization'] = `Bearer ${this.token}`;
return true;
} catch (error) {
throw error.response?.data?.message || 'Login failed';
}
},
logout() {
this.token = '';
this.user = null;
localStorage.removeItem('token');
localStorage.removeItem('user');
delete api.defaults.headers.common['Authorization'];
},
},
});
directory: frontend/src/views/admin/Login.vue
<template>
<div class="flex items-center justify-center bg-gray-100 min-h-screen">
<div class="w-full max-w-md bg-white p-8 rounded-xl shadow-lg">
<h1 class="text-2xl font-semibold text-center mb-6">Login as Admin</h1>
<form @submit.prevent="handleLogin">
<div class="mb-4">
<label class="block text-gray-700 mb-1">Email</label>
<input type="email" v-model="form.email" class="..." placeholder="Enter your email" />
</div>
<div class="mb-6">
<label class="block text-gray-700 mb-1">Password</label>
<input type="password" v-model="form.password" class="..." placeholder="Enter your password" />
</div>
<div class="text-sm text-red-600 mb-4">
<span v-if="errorMessage">{{ errorMessage }}</span>
</div>
<button type="submit" class="..." :disabled="isLoading">
<template v-if="isLoading">
<svg class="animate-spin ..." ...></svg>
Loading...
</template>
<template v-else>
Login
</template>
</button>
</form>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import { useAuthStore } from '@/stores/auth';
const auth = useAuthStore();
const router = useRouter();
const form = ref({
email: '',
password: '',
});
const errorMessage = ref('');
const isLoading = ref(false);
const handleLogin = async () => {
isLoading.value = true;
errorMessage.value = '';
try {
const success = await auth.login(form.value);
if (success) {
router.push('/admin/dashboard');
}
} catch (error) {
errorMessage.value = error;
} finally {
isLoading.value = false;
}
};
</script>
directory: frontend/src/router/index.js
import { createRouter, createWebHistory } from 'vue-router';
import { useAuthStore } from '@/stores/auth';
import PublicLayout from '@/layouts/PublicLayout.vue';
import AdminLayout from '@/layouts/AdminLayout.vue';
import Home from '@/views/public/Home.vue';
import About from '@/views/public/About.vue';
import Dashboard from '@/views/admin/Dashboard.vue';
import Login from '@/views/admin/Login.vue';
const routes = [
{
path: '/',
component: PublicLayout,
children: [
{ path: '', component: Home },
{ path: 'about', component: About },
],
},
{
path: '/admin/login',
name: 'admin-login',
component: Login,
},
{
path: '/admin',
component: AdminLayout,
children: [
{ path: 'dashboard', name: 'admin-dashboard', component: Dashboard },
],
meta: { requiresAuth: true },
},
];
const router = createRouter({
history: createWebHistory(),
routes,
});
router.beforeEach((to, from, next) => {
const auth = useAuthStore();
if (to.meta.requiresAuth && !auth.isAuthenticated) {
next({ name: 'admin-login' });
} else if (to.name === 'admin-login' && auth.isAuthenticated) {
next({ name: 'admin-dashboard' });
} else {
next();
}
});
export default router;
Yuk, langsung cobain yuk!
Semoga berhasil
Komentar
Posting Komentar