{"id":533,"date":"2025-08-31T08:38:50","date_gmt":"2025-08-31T08:38:50","guid":{"rendered":"https:\/\/1v0.net\/blog\/?p=533"},"modified":"2025-08-31T08:38:51","modified_gmt":"2025-08-31T08:38:51","slug":"integrating-laravel-with-vue-3-for-a-modern-frontend","status":"publish","type":"post","link":"https:\/\/1v0.net\/blog\/integrating-laravel-with-vue-3-for-a-modern-frontend\/","title":{"rendered":"Integrating Laravel with Vue 3 for a Modern Frontend"},"content":{"rendered":"\n<p>Laravel 12 integrates seamlessly with Vue 3 using Vite. This stack gives you Laravel\u2019s expressive backend and Vue\u2019s reactive UI, ideal for SPAs, dashboards, and highly interactive pages. In this guide, you\u2019ll install Vue 3 via Vite, understand the folder structure, import and mount components correctly, call them from Blade, fetch data from controllers, react to broadcast events, and build a practical login form UI with validation.<\/p>\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Install Vue 3 in a Laravel 12 Project (Vite Setup)<\/strong><\/h2>\n\n\n\n<p>Laravel 12 uses Vite by default. Add Vue using the official plugin and enable it in your Vite config.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\">npm install vue @vitejs\/plugin-vue<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Bash<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">bash<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This installs Vue 3 and the official Vite Vue plugin.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ vite.config.js<\/span>\n<span class=\"hljs-keyword\">import<\/span> { defineConfig } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'vite'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> laravel <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'laravel-vite-plugin'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> vue <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@vitejs\/plugin-vue'<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> defineConfig({\n    <span class=\"hljs-attr\">plugins<\/span>: &#91;\n        laravel({\n            <span class=\"hljs-attr\">input<\/span>: &#91;<span class=\"hljs-string\">'resources\/js\/app.js'<\/span>],\n            <span class=\"hljs-attr\">refresh<\/span>: <span class=\"hljs-literal\">true<\/span>,\n        }),\n        vue(),\n    ],\n});<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This configuration tells Vite to compile <code>resources\/js\/app.js<\/code>, refresh on Blade changes, and process Vue single-file components.<\/p>\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Understanding Vue Folder Structure in Laravel<\/strong><\/h2>\n\n\n\n<p>Your Vue code lives under <code>resources\/js<\/code>. Here\u2019s the default layout after enabling Vue:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"plaintext\" data-shcb-language-slug=\"plaintext\"><span><code class=\"hljs language-plaintext\">resources\/\n \u2514\u2500\u2500 js\/\n      \u251c\u2500\u2500 app.js          \/\/ Vue entry, mounts the app and registers components\n      \u251c\u2500\u2500 bootstrap.js    \/\/ Axios, CSRF, Echo helpers (optional but useful)\n      \u2514\u2500\u2500 components\/\n           \u2514\u2500\u2500 ExampleComponent.vue\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">plaintext<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">plaintext<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p><code>app.js<\/code> is your entry file. You can mount a single root Vue component, register many components for use inside Blade, or auto-register all components in the folder for convenience.<\/p>\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Working with <code>app.js<\/code>: Three Practical Patterns<\/strong><\/h2>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>1) Mount a Single Root Component (SPA-style)<\/strong><\/h3>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ resources\/js\/app.js<\/span>\n<span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-string\">'.\/bootstrap'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { createApp } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'vue'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> ExampleComponent <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'.\/components\/ExampleComponent.vue'<\/span>;\n\ncreateApp(ExampleComponent).mount(<span class=\"hljs-string\">'#app'<\/span>);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Use this when Vue controls the entire page\/app (typical SPA approach).<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>2) Register Multiple Components (Blade + Vue hybrid)<\/strong><\/h3>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ resources\/js\/app.js<\/span>\n<span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-string\">'.\/bootstrap'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { createApp } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'vue'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> ExampleComponent <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'.\/components\/ExampleComponent.vue'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> PostList <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'.\/components\/PostList.vue'<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> app = createApp({});\napp.component(<span class=\"hljs-string\">'example-component'<\/span>, ExampleComponent);\napp.component(<span class=\"hljs-string\">'post-list'<\/span>, PostList);\napp.mount(<span class=\"hljs-string\">'#app'<\/span>);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This lets you drop Vue widgets into Blade without making the whole page a SPA.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>3) Auto-register All Components (scale easily)<\/strong><\/h3>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ resources\/js\/app.js<\/span>\n<span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-string\">'.\/bootstrap'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { createApp } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'vue'<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> app = createApp({});\n\n<span class=\"hljs-comment\">\/\/ Auto-register .\/components\/*.vue as &lt;ComponentName \/&gt;<\/span>\n<span class=\"hljs-built_in\">Object<\/span>.entries(<span class=\"hljs-keyword\">import<\/span>.meta.glob(<span class=\"hljs-string\">'.\/components\/*.vue'<\/span>, { <span class=\"hljs-attr\">eager<\/span>: <span class=\"hljs-literal\">true<\/span> })).forEach(<span class=\"hljs-function\">(<span class=\"hljs-params\">&#91;path, def]<\/span>) =&gt;<\/span> {\n    <span class=\"hljs-keyword\">const<\/span> name = path.split(<span class=\"hljs-string\">'\/'<\/span>).pop().replace(<span class=\"hljs-string\">'.vue'<\/span>, <span class=\"hljs-string\">''<\/span>);\n    app.component(name, def.default);\n});\n\napp.mount(<span class=\"hljs-string\">'#app'<\/span>);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Now any <code>.vue<\/code> you add under <code>components\/<\/code> becomes available by its filename (e.g., <code>PostList.vue<\/code> \u2192 <code>&lt;PostList&gt;&lt;\/PostList&gt;<\/code>).<\/p>\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Create and Call a Vue Component from Blade<\/strong><\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ resources\/js\/components\/ExampleComponent.vue<\/span>\n&lt;template&gt;\n  <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"p-4\"<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h2<\/span>&gt;<\/span>Hello from Vue 3!<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h2<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span> @<span class=\"hljs-attr\">click<\/span>=<span class=\"hljs-string\">\"count++\"<\/span>&gt;<\/span>Clicked {{ count }} times<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n&lt;<span class=\"hljs-regexp\">\/template&gt;\n\n&lt;script setup&gt;\nimport { ref } from 'vue';\nconst count = ref(0);\n&lt;\/<\/span>script&gt;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>A simple counter using the Composition API. Next, call it from a Blade view and ensure Vite loads <code>app.js<\/code>.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ resources\/views\/welcome.blade.php<\/span>\n&lt;!DOCTYPE html&gt;\n&lt;html lang=<span class=\"hljs-string\">\"en\"<\/span>&gt;\n&lt;head&gt;\n  &lt;meta charset=<span class=\"hljs-string\">\"UTF-8\"<\/span>&gt;\n  &lt;title&gt;Laravel + Vue <span class=\"hljs-number\">3<\/span>&lt;\/title&gt;\n  @vite(<span class=\"hljs-string\">'resources\/js\/app.js'<\/span>)\n&lt;\/head&gt;\n&lt;body&gt;\n  &lt;div id=<span class=\"hljs-string\">\"app\"<\/span>&gt;\n    &lt;example-component&gt;&lt;\/example-component&gt;\n  &lt;\/div&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>With pattern #2 or #3 in <code>app.js<\/code>, the component renders where you place <code>&lt;example-component&gt;<\/code> inside the <code>#app<\/code> container.<\/p>\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Fetch Data from a Controller and Render in Vue<\/strong><\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ app\/Http\/Controllers\/PostController.php<\/span>\n<span class=\"hljs-keyword\">namespace<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Http<\/span>\\<span class=\"hljs-title\">Controllers<\/span>;\n\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Models<\/span>\\<span class=\"hljs-title\">Post<\/span>;\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">PostController<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">Controller<\/span>\n<\/span>{\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">index<\/span><span class=\"hljs-params\">()<\/span>\n    <\/span>{\n        <span class=\"hljs-keyword\">return<\/span> response()-&gt;json(Post::latest()-&gt;get());\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>A simple endpoint that returns posts as JSON for Vue to consume.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ resources\/js\/components\/PostList.vue<\/span>\n&lt;template&gt;\n  <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h2<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"mb-2\"<\/span>&gt;<\/span>Posts<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h2<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ul<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">li<\/span> <span class=\"hljs-attr\">v-for<\/span>=<span class=\"hljs-string\">\"post in posts\"<\/span> <span class=\"hljs-attr\">:key<\/span>=<span class=\"hljs-string\">\"post.id\"<\/span>&gt;<\/span>{{ post.title }}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">li<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">ul<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n&lt;<span class=\"hljs-regexp\">\/template&gt;\n\n&lt;script setup&gt;\nimport { ref, onMounted } from 'vue';\nconst posts = ref(&#91;]);\n\nonMounted(async () =&gt; {\n  const res = await fetch('\/<\/span>posts<span class=\"hljs-string\">');\n  posts.value = await res.json();\n});\n&lt;\/script&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The component fetches data on mount and renders a reactive list. Drop <code>&lt;post-list&gt;<\/code> into Blade to display it.<\/p>\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Realtime UX with Events (Broadcast + Vue)<\/strong><\/h2>\n\n\n\n<p>Broadcast Laravel events and listen in Vue to update the UI instantly (e.g., when a new post is created).<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ app\/Events\/NewPostCreated.php<\/span>\n<span class=\"hljs-keyword\">namespace<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Events<\/span>;\n\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Models<\/span>\\<span class=\"hljs-title\">Post<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Broadcasting<\/span>\\<span class=\"hljs-title\">Channel<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Contracts<\/span>\\<span class=\"hljs-title\">Broadcasting<\/span>\\<span class=\"hljs-title\">ShouldBroadcast<\/span>;\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">NewPostCreated<\/span> <span class=\"hljs-keyword\">implements<\/span> <span class=\"hljs-title\">ShouldBroadcast<\/span>\n<\/span>{\n    <span class=\"hljs-keyword\">public<\/span> $post;\n\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">__construct<\/span><span class=\"hljs-params\">(Post $post)<\/span>\n    <\/span>{\n        <span class=\"hljs-keyword\">$this<\/span>-&gt;post = $post;\n    }\n\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">broadcastOn<\/span><span class=\"hljs-params\">()<\/span>\n    <\/span>{\n        <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> Channel(<span class=\"hljs-string\">'posts'<\/span>);\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This event publishes to the public <code>posts<\/code> channel. Configure Laravel Echo (Pusher or Ably) in <code>bootstrap.js<\/code> to subscribe from Vue.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ resources\/js\/components\/PostList.vue (add listener)<\/span>\n&lt;script setup&gt;\n<span class=\"hljs-keyword\">import<\/span> { ref, onMounted } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'vue'<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> posts = ref(&#91;]);\n\nonMounted(<span class=\"hljs-keyword\">async<\/span> () =&gt; {\n  <span class=\"hljs-keyword\">const<\/span> res = <span class=\"hljs-keyword\">await<\/span> fetch(<span class=\"hljs-string\">'\/posts'<\/span>);\n  posts.value = <span class=\"hljs-keyword\">await<\/span> res.json();\n\n  <span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-built_in\">window<\/span>.Echo) {\n    <span class=\"hljs-built_in\">window<\/span>.Echo.channel(<span class=\"hljs-string\">'posts'<\/span>)\n      .listen(<span class=\"hljs-string\">'NewPostCreated'<\/span>, (e) =&gt; {\n        posts.value.unshift(e.post);\n      });\n  }\n});\n&lt;<span class=\"hljs-regexp\">\/script&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>When <code>NewPostCreated<\/code> is fired on the server, the UI prepends the new post instantly for users connected to the channel.<\/p>\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Build a Vue Login Form UI (Controller + Validation)<\/strong><\/h2>\n\n\n\n<p>Let\u2019s create a practical login UI in Vue that talks to a Laravel controller, validates input, and shows server error messages cleanly.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ resources\/js\/components\/LoginForm.vue<\/span>\n&lt;template&gt;\n  <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">form<\/span> @<span class=\"hljs-attr\">submit.prevent<\/span>=<span class=\"hljs-string\">\"submit\"<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"max-w-sm\"<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h2<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"mb-3\"<\/span>&gt;<\/span>Login<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h2<\/span>&gt;<\/span>\n\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"mb-2\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">label<\/span> <span class=\"hljs-attr\">for<\/span>=<span class=\"hljs-string\">\"email\"<\/span>&gt;<\/span>Email<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">label<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">input<\/span> <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">\"email\"<\/span> <span class=\"hljs-attr\">v-model<\/span>=<span class=\"hljs-string\">\"form.email\"<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"email\"<\/span> <span class=\"hljs-attr\">required<\/span> \/&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span> <span class=\"hljs-attr\">v-if<\/span>=<span class=\"hljs-string\">\"errors.email\"<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"text-danger\"<\/span>&gt;<\/span>{{ errors.email }}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"mb-3\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">label<\/span> <span class=\"hljs-attr\">for<\/span>=<span class=\"hljs-string\">\"password\"<\/span>&gt;<\/span>Password<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">label<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">input<\/span> <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">\"password\"<\/span> <span class=\"hljs-attr\">v-model<\/span>=<span class=\"hljs-string\">\"form.password\"<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"password\"<\/span> <span class=\"hljs-attr\">required<\/span> \/&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span> <span class=\"hljs-attr\">v-if<\/span>=<span class=\"hljs-string\">\"errors.password\"<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"text-danger\"<\/span>&gt;<\/span>{{ errors.password }}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"submit\"<\/span>&gt;<\/span>Sign in<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span> <span class=\"hljs-attr\">v-if<\/span>=<span class=\"hljs-string\">\"errors.global\"<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"text-danger mt-2\"<\/span>&gt;<\/span>{{ errors.global }}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">form<\/span>&gt;<\/span><\/span>\n&lt;<span class=\"hljs-regexp\">\/template&gt;\n\n&lt;script setup&gt;\nimport { reactive } from 'vue';\n\nconst form = reactive({ email: '', password: '' });\nconst errors = reactive({ email: '', password: '', global: '' });\n\nasync function submit() {\n  errors.email = errors.password = errors.global = '';\n\n  const res = await fetch('\/<\/span>login<span class=\"hljs-string\">', {\n    method: '<\/span>POST<span class=\"hljs-string\">',\n    headers: {\n      '<\/span>Content-Type<span class=\"hljs-string\">': '<\/span>application\/json<span class=\"hljs-string\">',\n      '<\/span>X-CSRF-TOKEN<span class=\"hljs-string\">': document.querySelector('<\/span>meta&#91;name=<span class=\"hljs-string\">\"csrf-token\"<\/span>]<span class=\"hljs-string\">').content\n    },\n    body: JSON.stringify(form)\n  });\n\n  if (res.ok) {\n    window.location.href = '<\/span>\/dashboard<span class=\"hljs-string\">';\n    return;\n  }\n\n  if (res.status === 422) {\n    const data = await res.json();\n    errors.email = data.errors?.email?.&#91;0] || '<\/span><span class=\"hljs-string\">';\n    errors.password = data.errors?.password?.&#91;0] || '<\/span><span class=\"hljs-string\">';\n  } else {\n    errors.global = '<\/span>Invalid credentials or server error.<span class=\"hljs-string\">';\n  }\n}\n&lt;\/script&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The component posts JSON to <code>\/login<\/code> with a CSRF token, handles validation (422) errors, and redirects on success.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ resources\/views\/auth\/login.blade.php<\/span>\n&lt;!doctype html&gt;\n&lt;html lang=<span class=\"hljs-string\">\"en\"<\/span>&gt;\n&lt;head&gt;\n  &lt;meta charset=<span class=\"hljs-string\">\"utf-8\"<\/span>&gt;\n  &lt;meta name=<span class=\"hljs-string\">\"csrf-token\"<\/span> content=<span class=\"hljs-string\">\"{{ csrf_token() }}\"<\/span>&gt;\n  &lt;title&gt;Login&lt;\/title&gt;\n  @vite(<span class=\"hljs-string\">'resources\/js\/app.js'<\/span>)\n&lt;\/head&gt;\n&lt;body&gt;\n  &lt;div id=<span class=\"hljs-string\">\"app\"<\/span>&gt;\n    &lt;LoginForm&gt;&lt;\/LoginForm&gt;\n  &lt;\/div&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The Blade view provides the CSRF token meta tag and mounts the <code>&lt;LoginForm&gt;<\/code> component inside <code>#app<\/code>.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ app\/Http\/Controllers\/Auth\/LoginController.php<\/span>\n<span class=\"hljs-keyword\">namespace<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Http<\/span>\\<span class=\"hljs-title\">Controllers<\/span>\\<span class=\"hljs-title\">Auth<\/span>;\n\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Http<\/span>\\<span class=\"hljs-title\">Controllers<\/span>\\<span class=\"hljs-title\">Controller<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Http<\/span>\\<span class=\"hljs-title\">Request<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Support<\/span>\\<span class=\"hljs-title\">Facades<\/span>\\<span class=\"hljs-title\">Auth<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Validation<\/span>\\<span class=\"hljs-title\">ValidationException<\/span>;\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">LoginController<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">Controller<\/span>\n<\/span>{\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">__invoke<\/span><span class=\"hljs-params\">(Request $request)<\/span>\n    <\/span>{\n        $validated = $request-&gt;validate(&#91;\n            <span class=\"hljs-string\">'email'<\/span> =&gt; <span class=\"hljs-string\">'required|email'<\/span>,\n            <span class=\"hljs-string\">'password'<\/span> =&gt; <span class=\"hljs-string\">'required|string|min:6'<\/span>,\n        ]);\n\n        <span class=\"hljs-keyword\">if<\/span> (Auth::attempt($validated, <span class=\"hljs-keyword\">true<\/span>)) {\n            $request-&gt;session()-&gt;regenerate();\n            <span class=\"hljs-keyword\">return<\/span> response()-&gt;json(&#91;<span class=\"hljs-string\">'ok'<\/span> =&gt; <span class=\"hljs-keyword\">true<\/span>]);\n        }\n\n        <span class=\"hljs-keyword\">throw<\/span> ValidationException::withMessages(&#91;\n            <span class=\"hljs-string\">'email'<\/span> =&gt; &#91;<span class=\"hljs-string\">'These credentials do not match our records.'<\/span>],\n        ]);\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The single-action controller validates the request, attempts login, regenerates the session, and returns JSON. On failure it throws a 422 with field errors that Vue shows inline.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-16\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ routes\/web.php<\/span>\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Http<\/span>\\<span class=\"hljs-title\">Controllers<\/span>\\<span class=\"hljs-title\">Auth<\/span>\\<span class=\"hljs-title\">LoginController<\/span>;\n\nRoute::view(<span class=\"hljs-string\">'\/login'<\/span>, <span class=\"hljs-string\">'auth.login'<\/span>)-&gt;name(<span class=\"hljs-string\">'login'<\/span>);\nRoute::post(<span class=\"hljs-string\">'\/login'<\/span>, LoginController::class);\nRoute::get(<span class=\"hljs-string\">'\/dashboard'<\/span>, fn () =&gt; view(<span class=\"hljs-string\">'dashboard'<\/span>))-&gt;middleware(<span class=\"hljs-string\">'auth'<\/span>);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>These routes render the login page, handle the POST login, and protect the dashboard behind <code>auth<\/code> middleware.<\/p>\n\n\n\n<p>Related deep dives: <a href=\"\/blog\/mastering-validation-rules-in-laravel-12\">Mastering Validation Rules in Laravel 12<\/a>, <a href=\"\/blog\/how-to-add-authentication-in-laravel-12-without-fortify\">How to Add Authentication in Laravel 12 (Without Fortify)<\/a>.<\/p>\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Wrapping Up<\/strong><\/h2>\n\n\n\n<p>You integrated Vue 3 into Laravel with Vite, learned the folder structure, registered and mounted components, called them from Blade, fetched data from controllers, reacted to broadcast events, and built a production-ready login form UI with validation. This Laravel + Vue approach pairs a clean backend with a delightful, reactive frontend\u2014perfect for modern apps.<\/p>\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>What\u2019s Next<\/strong><\/h2>\n\n\n\n<p>Level up your stack with these related guides:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"\/blog\/laravel-with-docker-sail-the-right-way\">Laravel with Docker &amp; Sail: The Right Way<\/a><\/li>\n\n\n\n<li><a href=\"\/blog\/using-laravel-telescope-to-debug-performance-issues\">Using Laravel Telescope to Debug Performance Issues<\/a><\/li>\n\n\n\n<li><a href=\"\/blog\/mastering-validation-rules-in-laravel-12\">Mastering Validation Rules in Laravel 12<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Laravel 12 integrates seamlessly with Vue 3 using Vite. This stack gives you Laravel\u2019s expressive backend and Vue\u2019s reactive UI, ideal for SPAs, dashboards, and highly interactive pages. In this guide, you\u2019ll install Vue 3 via Vite, understand the folder structure, import and mount components correctly, call them from Blade, fetch data from controllers, react [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":537,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7],"tags":[12,46,104,105,19,103,106],"class_list":["post-533","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-laravel","tag-authentication","tag-events","tag-frontend-integration","tag-spa","tag-validation","tag-vite","tag-vue"],"_links":{"self":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts\/533","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/comments?post=533"}],"version-history":[{"count":1,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts\/533\/revisions"}],"predecessor-version":[{"id":536,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts\/533\/revisions\/536"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/media\/537"}],"wp:attachment":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/media?parent=533"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/categories?post=533"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/tags?post=533"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}