VueJS Articles

Solve Vue.js Component Overload with Parent-Child Patterns

0 👍
👎 0
 Laravel
 VueJS

Has your Vue component become a tangled mess? Too much template markup, too many responsibilities, and decreasing maintainability.  

Well the solution is to use component composition with parent-child relationships that Vue provides.

 

In this tutorial, you'll learn to:

 

1. Refactor effectively breaking up monolithic components into focused children

2. Pass data gracefully using props to send data from parent to child  

3. Handle child events by capturing custom events emitted by child components

4. Maintain clean data flow by establishing predictable communication between components

 

See a real-world example where a parent component delegates UI to a specialized Toolbar child, creating cleaner code and better separation of concerns.

 

Parent Component:

 

 <template>

    <div class="parent-component">

        <h2>Evaluate Product</h2>

      

        <Toolbar :message="message" @evaluate-product="evaluate" />

        <div class="parent-data">

            <p>Total Likes: {{ total.likes }}</p>

            <p>Total Dislikes: {{ total.dislikes }}</p>

            <p v-if="action">Last Action: {{ action }}</p>

        </div>

    </div>

 </template>

 

 <script>

 import Toolbar from './Toolbar.vue';

 

 export default {

  name: 'EvaluateComponent',

  components: {

     Toolbar

  },

  data(){

     return {

        total: {

            likes: 0,

            dislikes: 0

         },

         message: '',

         action: null,

         messageTimeout: null

     }

  },

  methods:{

     evaluate(task) {

         // Clear previous timeout

         if (this.messageTimeout) {

             clearTimeout(this.messageTimeout);

         }

        

         switch(task) {

               case 'like':

                   this.total.likes++;

                   this.message = "Like incremented successfully!";

                   this.action = 'like';

                   break;

               case 'dislike':

                   this.total.dislikes++;

                   this.message = "Dislike incremented successfully!";

                   this.action = 'dislike';

                   break;

         }

        

         // Auto-clear message after 3 seconds

         this.messageTimeout = setTimeout(() => {

             this.message = '';

         }, 3000);

     }

  },

   beforeDestroy() {

     // Clean up timeout when component is destroyed

     if (this.messageTimeout) {

         clearTimeout(this.messageTimeout);

     }

  }

 }

</script>

 



Child Component (Toolbar tag in the parent):

 

<template>

   <div class="child-component">

       <div class="message" v-if="messageSet">{{ message }}</div>

       <button @click="performEvaluate('like')">Like</button>

       <button @click="performEvaluate('dislike')">Dislike</button>

   </div>

</template>

 

<script>

export default {

   props: {

       message: {

           type: String,

           default: ''

       }

   },

   data() {

       return {

           // You can add data properties here if needed

       }

   },

   emits: ['evaluate-product'],

   computed: {

       messageSet() {

           return this.message.length > 0;

       }

   },

   methods: {

       performEvaluate(evaluation) { 

           this.$emit('evaluate-product', evaluation);

       }

   }

}

</script>

 

 

Format Date and Time in JavaScript with Date Object

0 👍
👎 0
 VueJS
 Javascript

This tutorial The JavaScript Date library is a built-in object that represents a single moment in time. It provides methods for creating, parsing, formatting, and manipulating dates and times. Dates are stored as the number of milliseconds since the Unix epoch (January 1, 1970, 00:00:00 UTC).

 

In this tutorial I will cover how to format date only, time only, and both date and time using these 3 methods from built-in object Date. A method to format a date. A method to format a time. And a method to format both date and time.  


const
date = new Date('2025-10-23 11:06:48');

date.toLocaleDateString('en-US');

date.toLocaleTimeString('en-US');

date.toLocaleString('en-US');

Also, I have included 2 handy references at the end of this tutorial.  A reference for options and values available to define the format.  Also, a reference that lists all available methods on the Javascript built-in Date object. 

 

Date toLocaleDateString()


The
toLocaleDateString() method formats the date only.  Below the value of the date will be initiated as datetime (yyyy-mm-dd hh:mm:ss):

const date = new Date('2025-10-23 11:06:48');

Even though the Date object is initiated with a date and time string, only the date part of the string will be extracted. The default date.toLocaleDateString('en-US') returns the date formatted as mm/dd/yyyy.  Also use other formatting options available. The options are referenced below. 

 

 const date = new Date('2025-10-23 11:06:48');

 

 // Date only

 date.toLocaleDateString('en-US');

 // "10/23/2025"

 

 date.toLocaleDateString('en-US', {

   year: 'numeric',

   month: 'long',

   day: 'numeric'

 });

 // "October 23, 2025"

 



Date toLocaleTimeString()


Target and format the time portion of the string using
toLocaleTimeString().  Now time is extracted from the initial value set below…  

 const date = new Date('2025-10-23 11:06:48');


The
toLocaleTimeString() method can be used with or without options. The default time is in 12 hour format (hh:mm:ss AM/PM).  Options are listed below:

 

 // Time only

 date.toLocaleTimeString('en-US');

 // "11:06:48 AM"

 

 date.toLocaleTimeString('en-US', {

    hour: '2-digit',

    minute: '2-digit',

    hour12: true

 });

 // "11:06 AM"

 




Date toLocaleString()

To format both the date and time use toLocaleString().  This method also accepts options to provide formatting instructions.  Refer to the list below.  

 

 // Combined date and time

 date.toLocaleString('en-US');

 // "10/23/2025, 11:06:48 AM"

 

 date.toLocaleString('en-US').replace(/,/g, '');

 // "10/23/2025 11:06:48 AM"

 


Below are all the possible format options that can be passed to
toLocaleString() for the en-US locale.

Option Reference Table toLocaleString() 

Parameter

Possible Values

Description

Example Output

DATE COMPONENTS

     

year

'numeric'

Full year

2025

 

'2-digit'

Two-digit year

25

month

'numeric'

Month number

10

 

'2-digit'

Two-digit month

10

 

'long'

Full month name

October

 

'short'

Abbreviated month

Oct

 

'narrow'

Single character

O

day

'numeric'

Day of month

23

 

'2-digit'

Two-digit day

23

weekday

'long'

Full weekday name

Thursday

 

'short'

Abbreviated weekday

Thu

 

'narrow'

Single character

T

TIME COMPONENTS

     

hour

'numeric'

Hour

11

 

'2-digit'

Two-digit hour

11

minute

'numeric'

Minute

6

 

'2-digit'

Two-digit minute

06

second

'numeric'

Second

48

 

'2-digit'

Two-digit second

48

fractionalSecondDigits

1, 2, 3

Milliseconds digits

48.123

TIME SETTINGS

     

hour12

true

12-hour clock

11:06 AM

 

false

24-hour clock

11:06

timeZone

'America/New_York'

Specific timezone

Adjusts time

 

'UTC'

UTC timezone

 
 

'Europe/London'

Other timezones

 

timeZoneName

'short'

Abbreviated timezone

EDT

 

'long'

Full timezone name

Eastern Daylight Time

 

'shortOffset'

Short offset

GMT-4

 

'longOffset'

Long offset

GMT-04:00

 

'shortGeneric'

Generic short

ET

 

'longGeneric'

Generic long

Eastern Time

OTHER OPTIONS

     

era

'long'

Full era name

Anno Domini

 

'short'

Abbreviated era

AD

 

'narrow'

Single character

A

dayPeriod

'narrow'

AM/PM format

AM

 

'short'

AM/PM format

AM

 

'long'

AM/PM format

AM

calendar

'gregory'

Gregorian calendar

Default

numberingSystem

'latn'

Latin digits

0123456789

PRESET STYLES

     

dateStyle

'full'

Complete date

Thursday, October 23, 2025

 

'long'

Long format

October 23, 2025

 

'medium'

Medium format

Oct 23, 2025

 

'short'

Short format

10/23/2025

timeStyle

'full'

Complete time

11:06:48 AM Eastern Daylight Time

 

'long'

Long format

11:06:48 AM EDT

 

'medium'

Medium format

11:06:48 AM

 

'short'

Short format

11:06 AM






JavaScript Date Library Function Reference

Constructor Methods

Method

Description

Example

new Date()

Current date/time

new Date()

new Date(milliseconds)

From milliseconds since epoch

new Date(1634980000000)

new Date(dateString)

From ISO string

new Date("2025-10-23")

new Date(year, month, day, hours, minutes, seconds, ms)

From components

new Date(2025, 9, 23)

 

Static Methods

Method

Description

Returns

Date.now()

Current timestamp in milliseconds

1634980000000

Date.parse()

Parse date string to milliseconds

1634980000000

Date.UTC()

UTC timestamp from components

1634980000000

 

 

Getter Methods: Local Time Getters

Method

Description

Range

getFullYear()

4-digit year

1900+

getMonth()

Month

0-11

getDate()

Day of month

1-31

getDay()

Day of week

0-6

getHours()

Hours

0-23

getMinutes()

Minutes

0-59

getSeconds()

Seconds

0-59

getMilliseconds()

Milliseconds

0-999

getTime()

Milliseconds since epoch

0+

getTimezoneOffset()

Timezone offset in minutes

-720 to 720

 

 

UTC Getters

Method

Description

Range

getUTCFullYear()

UTC 4-digit year

1900+

getUTCMonth()

UTC month

0-11

getUTCDate()

UTC day of month

1-31

getUTCDay()

UTC day of week

0-6

getUTCHours()

UTC hours

0-23

getUTCMinutes()

UTC minutes

0-59

getUTCSeconds()

UTC seconds

0-59

getUTCMilliseconds()

UTC milliseconds

0-999

Setter Methods

Local Time Setters

Method

Description

Parameters

setFullYear()

Set year

year[, month, day]

setMonth()

Set month

month[, day]

setDate()

Set day of month

day

setHours()

Set hours

hours[, min, sec, ms]

setMinutes()

Set minutes

minutes[, sec, ms]

setSeconds()

Set seconds

seconds[, ms]

setMilliseconds()

Set milliseconds

ms

setTime()

Set time via milliseconds

milliseconds

UTC Setters

Method

Description

Parameters

setUTCFullYear()

Set UTC year

year[, month, day]

setUTCMonth()

Set UTC month

month[, day]

setUTCDate()

Set UTC day of month

day

setUTCHours()

Set UTC hours

hours[, min, sec, ms]

setUTCMinutes()

Set UTC minutes

minutes[, sec, ms]

setUTCSeconds()

Set UTC seconds

seconds[, ms]

setUTCMilliseconds()

Set UTC milliseconds

ms

Conversion Methods

Method

Description

Example Output

toString()

Full date string

"Thu Oct 23 2025 11:06:48 GMT-0400 (EDT)"

toDateString()

Date portion only

"Thu Oct 23 2025"

toTimeString()

Time portion only

"11:06:48 GMT-0400 (EDT)"

toISOString()

ISO 8601 format

"2025-10-23T15:06:48.000Z"

toUTCString()

UTC string

"Thu, 23 Oct 2025 15:06:48 GMT"

toGMTString()

GMT string (deprecated)

"Thu, 23 Oct 2025 15:06:48 GMT"

toJSON()

JSON string

"2025-10-23T15:06:48.000Z"

toLocaleString()

Locale date/time

"10/23/2025, 11:06:48 AM"

toLocaleDateString()

Locale date only

"10/23/2025"

toLocaleTimeString()

Locale time only

"11:06:48 AM"

Value Methods

Method

Description

Returns

valueOf()

Primitive value

Milliseconds (same as getTime())

 

Laravel Webpack.mix Asset Compilation AND Performance Optimization

0 👍
👎 0
 Laravel
 VueJS
 Javascript
 Blade

Getting Started with webpack.mix


This is an essential must know for Laravel developers.  This tutorial will go through the basics of webpack.mix and preparing live CSS stylesheets and Javascript includes.


Locate
webpack.mix.js and the /resources and /public directories.  I will define a few ways to specify the files from resources compiled to the live public directory.  Check out these 2 examples…  they should cover necessary usage for 99% of projects.

 

Example: Combine multiple files into a single file.

In this example I’ll define a simple way to combine multiple custom javascript files into a single file.  Let’s take 4 custom stylesheets and 4 javascript files.  We will combine all specified stylesheets from /resources/css into a single stylesheet named /public/css/custom-all.css.  Also let’s combine the specified javascript files from /resources/js into a single javascript file named /public/js/custom-all.js  


All files that can be edited directly are located in the resources directory.  All compiled files will be placed in the public directory where the code can be accessed live.

 

 mix.js('resources/js/app.js', 'public/js')

    .vue()

    .sass('resources/sass/app.scss', 'public/css');

 

 // Combine all custom JS into one file

 mix.scripts([

    'resources/js/custom/main.js',

    'resources/js/custom/helpers.js',

    'resources/js/custom/components/*.js'

 ], 'public/js/custom-all.js');

 

 // Combine all custom CSS into one file

 mix.styles([

    'resources/css/custom/main.css',

    'resources/css/custom/components/buttons.css',

    'resources/css/custom/pages/*.css'

 ], 'public/css/custom-all.css');

 

  

Let’s see how to use things in a blade template:

 

 <!DOCTYPE html>

 <html lang="en">

 <head>

    <meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>Your Laravel Application</title>

   

    <!-- Compiled CSS -->

    <link href="{{ mix('css/app.css') }}" rel="stylesheet">

    <link href="{{ mix('css/custom-all.css') }}" rel="stylesheet">

 </head>

 <body>

    <div id="app">

        <!-- Your application content -->

    </div>

 

    <!-- Compiled JavaScript -->

    <script src="{{ mix('js/app.js') }}"></script>

    <script src="{{ mix('js/custom-all.js') }}"></script>

 </body>

 </html>

 



Example: Prepare several files.


This example is very similar to the example above.  The only difference is in this example we are generating individual files in the
public directory for each file specified from the resources directory. 

 

 // Webpack-compiled JS and CSS

 mix.js('resources/js/app.js', 'public/js')

   .vue({ version: 2 })

   .sass('resources/sass/app.scss', 'public/css') // Or .css() if not using Sass

   .css('resources/css/app.css', 'public/css');

 

 // Custom JS files (separate from Webpack bundle)

 mix.js('resources/js/custom/main.js', 'public/js/custom.js')

   .js('resources/js/custom/helpers.js', 'public/js/helpers.js');

 

 // Custom CSS files (separate from Webpack bundle)

 mix.css('resources/css/custom/main.css', 'public/css/custom.css')

   .css('resources/css/custom/components/buttons.css', 'public/css/components/buttons.css');

 


Notice in
webpack.mix.js the definitions for mix.js and mix.css methods.  Each of the javascript files and stylesheets from the resources directory has a corresponding file in the public directory.

 

Now let’s look at how to use this in a blade template:

 

 <!DOCTYPE html>

 <html lang="en">

 <head>

    <meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>Your Laravel Application</title>

   

    <!-- Compiled CSS -->

    <link href="{{ mix('css/app.css') }}" rel="stylesheet">

    <link href="{{ mix('css/custom.css') }}" rel="stylesheet">

    <link href="{{ mix('css/components/buttons.css') }}" rel="stylesheet">

 </head>

 <body>

    <div id="app">

        <!-- Your application content -->

    </div>

 

    <!-- Compiled JavaScript -->

    <script src="{{ mix('js/app.js') }}"></script>

    <script src="{{ mix('js/custom.js') }}"></script>

    <script src="{{ mix('js/helpers.js') }}"></script>

 </body>

 </html>

 

 

To generate the public CSS and JS files:

 

 npm install

 npm run dev

 

Scoped Slot in Vue 2 (Options API)

0 👍
👎 0
 Laravel
 VueJS
 Javascript

With scoped slots, the child component can pass data to the slot content in the parent. That way, the parent can decide how to render data coming from the child.

 

1. Child Component (List.vue)

 

 <template>

   <ul>

     <!-- we expose each item to the slot -->

     <li v-for="(item, index) in items" :key="index">

       <!-- default slot gets props -->

       <slot :item="item" :index="index">

         <!-- fallback content if parent doesn't use scoped slot -->

         {{ index }} - {{ item }}

       </slot>

     </li>

   </ul>

 </template>

 

 <script>

  export default {

    name: "List",

    props: {

      items: {

        type: Array,

        required: true

      }

    }

  }

 </script>

 

 

2. Parent Component (App.vue)

 

 <template>

   <div>

     <h1>Scoped Slots Example</h1>

 

     <!-- Using scoped slot -->

     <List :items="categories">

       <template v-slot:default="slotProps">

         <!-- slotProps.item and slotProps.index come from child -->

         <strong>{{ slotProps.index + 1 }}.</strong> {{ slotProps.item.toUpperCase() }}

       </template>

     </List>

 

     <!-- Without scoped slot (fallback from List.vue will be used) -->

     <List :items="tags" />

   </div>

 </template>

 

 <script>

 import List from "./List.vue";

 export default {

    name: "App",

    components: { List },

    data() {

       return {

         categories: ["News", "Sports", "Entertainment"],

         tags: ["vue", "laravel", "slots"]

       };

    }

 }

 </script>

 

 

Using Slots in Vue 2 (Options API)

0 👍
👎 0
 Laravel
 VueJS
 NodeJS
 Javascript

Slots in Vue let you create reusable components where the parent controls part of the content. 

 

1. Create a reusable component (Card.vue)

 

 <template>

   <div class="card">

     <div class="card-header">

       <!-- Named slot for header -->

       <slot name="header">Default Header</slot>

     </div>

 

     <div class="card-body">

       <!-- Default slot -->

       <slot>Default content goes here...</slot>

     </div>

 

     <div class="card-footer">

       <!-- Named slot for footer -->

       <slot name="footer">Default Footer</slot>

     </div>

   </div>

 </template>

 

 <script>

 export default {

   name: "Card"

 }

 </script>

 

 

2. Use the component in a parent (App.vue or a Blade-Vue view)

 

 <template>

   <div>

     <h1>Vue 2 Slots Example</h1>

 

     <Card>

       <template v-slot:header>

         Custom Header: Article Info

       </template>

 

       This is the article body content coming from the parent.

 

       <template v-slot:footer>

         <button @click="sayHello">Click Me</button>

       </template>

     </Card>

 

     <!-- Another card with defaults -->

     <Card />

   </div>

 </template>

 <script>

 import Card from "./Card.vue";

 export default {

   name: "App",

   components: { Card },

   methods: {

     sayHello() {

       alert("Hello from slot footer button!");

     }

   }

 }

 </script>

 

 

Upload and Resize an Image with Laravel Vue and ImageMagick

0 👍
👎 0
 Laravel
 VueJS
 PHP
 ImageMagick
 Bootstrap

In this example I use the Laravel and Vue frameworks to implement a SPA tool to upload images and display a list of images that have been uploaded.  The application also demonstrates image resizing with the amazing PHP ImageMagick library.  So there are a few prerequisits to get started however I won't cover that in this tutorial.  I've added some links to get you through the prerequisits:

  1. Install imagemagick and the pecl imagemagick package. Configure the extension in php.
  2. Install and set-up Laravel to use the Vue 2 Options API with Webpack compiler.
  3. Create required Controllers, Blade Views, routes(web.php, api.php, routes.js) and Vue components

Assuming you have an initial setup you will need to generate an API controller to handle requests made from the Vue components.  You can generate the controller with php artisan:

php artisan make:controller Api/ImageController

Next, we need to specify a directory to store these images.  For this tutorial we will upload our images to /public/images.  So notice in the ImageController the method public_path('images/'.$imageName) is used to specify this location.

 

/app/Http/Controllers/Api/ImageController.php

We need two methods to handle the functionality for this application.  An index method for GET requests to retrieve a list of all images.  And an upload method to handle a POST request to upload an image.

 

<?php

namespace App\Http\Controllers;

 

use Illuminate\Http\Request;

use Illuminate\Support\Facades\File;

use Imagick;

 

class ImageController extends Controller

{

   /**

    * index

    */

   public function index()

   {

       $fileNames = [];

       $imagesPath = public_path('images/');

       $files = File::files($imagesPath);

       foreach ($files as $key=>$file) {

           $fileNames[] = [

               'file'=>$file->getFilename(),

               'path'=>$file->getPathname(),

               'size'=>$file->getSize(),

               'ext'=>$file->getExtension()

           ];

       }

       return response()->json($fileNames);

   }

 

 

   /**

    * upload

    */

   public function upload(Request $request)

   {

       $request->validate([

           'image' => 'required|image|mimes:jpeg,png,jpg,gif,svg|max:2048',

       ]);

 

       // Create a new Imagick object

       $imagick = new Imagick();

 

       // Read the uploaded image

       $imagick->readImage($request->file('image')->getPathname());

 

       // Resample the image (change the resolution)

       $imagick->resizeImage(100, 100, Imagick::FILTER_LANCZOS, 1);

 

       // Save the image

       $imageName = time() . '.' . $request->file('image')->getClientOriginalExtension();

       $imagick->writeImage(public_path('images/' . $imageName));

 

       // Clear the Imagick object

       $imagick->clear();

       $imagick->destroy();

 

       $fileNames = [];

       $imagesPath = public_path('images/');

       $files = File::files($imagesPath);

       foreach ($files as $file) {

           $fileNames[] = [

               'file'=>$file->getFilename(),

               'path'=>$file->getPathname(),

               'bytes'=>$file->getSize(),

               'ext'=>$file->getExtension()

           ];

       }

      

       return response()->json($fileNames);

   }
}

 

/resources/js/Image/Upload.vue

The Upload.vue component will display an image upload form and a script to interact with the form actions.  Once the user selects an image, they will be able to preview the image before upload.  Notice the API request made with Axios POST to /api/images.  These routes are specified in /routes/api.php

<template>

   <div class="container">

       <div class="row justify-content-center">

           <div class="col-md-8">

               Upload Image Form

                   <div class="row m-1 g-1">

                       <div class="form-group col-md-12">

                           <input class="form-control" :class="[{'text-danger': 'image' in errors && errors.image.length > 0}]" type="file" @change="onFileChange" name="image" id="image"  accept="image/*" >

                       </div>

                       <div v-if="'image' in errors" class="text-danger">                

                           <span v-for="(error, index) in errors['image']" :key="'type-'+index">

                               {{error}}

                           </span>

                       </div>

                   </div>

                   <div v-if="preview" class="mt-3">

                       <img :src="preview" alt="preview" width="100" height="100" />

                   </div>

                   <div class="row m-1 g-1">

                       <div class="form-group col-12">

                           <button type="button" @click.prevent="cancelUpload" class="btn btn-sm btn-fw btn-danger float-end mx-2">

                               Cancel

                           </button>

                           <button class="btn btn-sm btn-fw btn-primary float-end" type="submit" @click.prevent="uploadImage">Upload Image</button>

                       </div>

                   </div>

           </div>

       </div>

   </div>

</template>

<script>
export default {

 

   data() {

       return {

           image: null,

           preview: null,

           user: {},

           errors:{

               image: []

           },

       };

   },

 

   methods: {

       cancelUpload(){

           let redirectTo = this.$route.query.redirect || { name: 'admin-images' };

           this.$router.push(redirectTo);

       },

       async uploadImage(){

           if(this.image)

           {

               let fd = new FormData();

               fd.append('image', this.image);

               try {

                   let resp = (await axios.post('/api/images', fd)).data.data;

                   let redirectTo = this.$route.query.redirect || { name: 'admin-images' };

                   this.$router.push(redirectTo);

               } catch(error) {

                   this.errors = error;

               }

           }

           else {

               this.errors.image.push(`An image file is required for upload.`);

           }

          

       },

       onFileChange(e) {

           let file = e.target.files[0];

           if (file) {

               this.image = file;

               this.preview = URL.createObjectURL(file);

           }

       }

   },

   created() {

       console.log('Image/Upload COMPONENT CREATED.');       

   }
}
</script>

 

/resources/admin/js/Image/Index.vue

The Index.vue component will display a list of images that currently exist in the /public/images directory.  Also notice the API GET request made to /api/images.  

<template>

   <div class="container">

       <div class="row justify-content-center">

           <div class="col-md-10">

               <router-link

                   class="btn btn-primary mb-2 float-right"

                   :to="{ name: 'image-upload', query: {redirect: $route.fullPath} }">

                Image Upload

               </router-link>

 

               <div class="mb-4 row" v-for="(image, index) in images" :key="index">

                <img

                       :src="`/images/${image.file}`"

                       class="img-thumbnail "

                       width="100"

                     />

               </div>

           </div>

       </div>

   </div>

</template>

<script>

export default {

   data() {

       return {

           images: [],

           user: {}

       };

   },

   methods: {

   },

   async created() {

       let response = await axios.get("/api/images");

       this.images = response.data.length ? response.data : [];

   }

};

</script>

 

/resources/js/routes.js

In order to see the Image/Index.vue and Image/Upload.vue components we need to specify a route for each in routes.js.  Because the path /images and /image/upload routes are handled via Vue we need to use a <router-link> inside of another Vue component order to navigate to /images and /image/upload.  

<router-link class="nav-link active" :to="{name: 'image-index'}">Images</router-link>
<router-link class="nav-link active" :to="{name: 'image-upload'}">Upload</router-link>

Then configure the routes in /resources/js/routes.js like below:

 

 import ImageIndex from "./Image/Index";

 import ImageUpload from "./Image/Upload";

 

 const routes = [

       // add these 2 new routes

{

    path: "/images",

    component: ImageIndex,

    name: "image-index",

},

{

    path: "/image/upload",

    component: ImageUpload,

    name: "image-upload",

}

 ];

 

 

/routes/api.php

Finally we create API routes to upload and retrieve images.  Notice both routes both go to /images and either Route::get or Route::post will be used to identify them properly in a request.

 

// GET request to ‘/api/images’

Route::get('/images', [App\Http\Controllers\Api\ImageController::class, 'index']);

 

// POST Request to ‘/api/images’
Route::post('/images', [App\Http\Controllers\Api\ImageController::class, 'upload']);