# Step-by-Step Guide: Build a Custom CRM from Scratch with Laravel + Vue.js (Full Code)

> 1. What is a Custom CRM and Why Build One?2. Why Laravel + Vue.js?3. Tools & Tech Stack Overview4. Project Structure & Setup4.1. 📥 Install Laravel4.2. ⚙️ Configure .env5. Backend: Laravel API Development5.1. Create Cus…

- **Published:** 2025-07-09
- **Updated:** 2026-07-27
- **Category:** Web Development
- **URL:** https://kavcomexpert.com/blog/step-by-step-guide-build-a-custom-crm

## Key takeaways

- What is a Custom CRM and Why Build One?2.
- Conclusion11.1 📢 Want to Build a CRM for Your Team?
- A CRM (Customer Relationship Management) tool helps businesses manage their interactions with existing and potential customers.

## 1. What is a Custom CRM and Why Build One?

A **[CRM (Customer Relationship Management)](https://kavcomexpert.com/wordpress-development/)** tool helps businesses manage their interactions with existing and potential customers. Popular options like HubSpot, Salesforce, and Zoho CRM are great—but they’re often **overkill**, expensive, or inflexible.

**Custom CRMs** give businesses:

- Full control of features
- Seamless integration with existing tools
- Cost-effective long-term scalability
- Ownership of sensitive customer data

## 2. Why Laravel + Vue.js?

- **Laravel** is a powerful PHP framework offering built-in routing, ORM (Eloquent), authentication, and more.
- **Vue.js** is a lightweight frontend framework ideal for building reactive, component-driven UIs.
- They’re a perfect match for **[Single Page Applications (SPAs)](https://kavcomexpert.com/ios-app-development/)** and API-driven development.

## 3. Tools & Tech Stack Overview



## 4. Project Structure & Setup

### 📥 Install Laravel

```
svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14">g fill="none" fill-rule="evenodd" transform="translate(1 1)">circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5">/circle>circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5">/circle>circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5">/circle>/g>/svg>pre class="code-block-pro-copy-button-pre" aria-hidden="true">textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>composer create-project laravel/laravel crm-app
cd crm-app
php artisan serve/textarea>/pre>svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4">/path>path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2">/path>/svg>pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0">composer create-project laravel/laravel crm-app
cd crm-app
php artisan serve/pre>
```

### ⚙️ Configure `.env`

```
svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14">g fill="none" fill-rule="evenodd" transform="translate(1 1)">circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5">/circle>circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5">/circle>circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5">/circle>/g>/svg>pre class="code-block-pro-copy-button-pre" aria-hidden="true">textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>DB_DATABASE=crm
DB_USERNAME=root
DB_PASSWORD=/textarea>/pre>svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4">/path>path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2">/path>/svg>pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0">DB_DATABASE=crm
DB_USERNAME=root
DB_PASSWORD=/pre>
```

```
Create the crm database in MySQL.
```

## 5. Backend: Laravel API Development

### 🛠️ a) Create Customer Model & Migration

```
svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14">g fill="none" fill-rule="evenodd" transform="translate(1 1)">circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5">/circle>circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5">/circle>circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5">/circle>/g>/svg>pre class="code-block-pro-copy-button-pre" aria-hidden="true">textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>php artisan make:model Customer -m/textarea>/pre>svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4">/path>path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2">/path>/svg>pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0">php artisan make:model Customer -m/pre>
```

Now edit the migration:

```
svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14">g fill="none" fill-rule="evenodd" transform="translate(1 1)">circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5">/circle>circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5">/circle>circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5">/circle>/g>/svg>pre class="code-block-pro-copy-button-pre" aria-hidden="true">textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>public function up()
{
    Schema::create('customers', function (Blueprint $table) {
        $table->id();
        $table->string('name');
        $table->string('email')->unique();
        $table->string('phone')->nullable();
        $table->text('notes')->nullable();
        $table->timestamps();
    });
}/textarea>/pre>svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4">/path>path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2">/path>/svg>pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0">public function up()
{
    Schema::create('customers', function (Blueprint $table) {
        $table->id();
        $table->string('name');
        $table->string('email')->unique();
        $table->string('phone')->nullable();
        $table->text('notes')->nullable();
        $table->timestamps();
    });
}/pre>
```

Run the migration:

```
svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14">g fill="none" fill-rule="evenodd" transform="translate(1 1)">circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5">/circle>circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5">/circle>circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5">/circle>/g>/svg>pre class="code-block-pro-copy-button-pre" aria-hidden="true">textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>php artisan migrate/textarea>/pre>svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4">/path>path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2">/path>/svg>pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0">php artisan migrate/pre>
```

### 📦 b) Customer Controller

```
svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14">g fill="none" fill-rule="evenodd" transform="translate(1 1)">circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5">/circle>circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5">/circle>circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5">/circle>/g>/svg>pre class="code-block-pro-copy-button-pre" aria-hidden="true">textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>php artisan make:controller API/CustomerController --api/textarea>/pre>svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4">/path>path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2">/path>/svg>pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0">php artisan make:controller API/CustomerController --api/pre>
```

### Controller Logic

```
svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14">g fill="none" fill-rule="evenodd" transform="translate(1 1)">circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5">/circle>circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5">/circle>circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5">/circle>/g>/svg>pre class="code-block-pro-copy-button-pre" aria-hidden="true">textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>namespace App\Http\Controllers\API;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Customer;

class CustomerController extends Controller
{
    public function index() {
        return response()->json(Customer::all(), 200);
    }

    public function store(Request $request) {
        $validated = $request->validate([
            'name' => 'required|string',
            'email' => 'required|email|unique:customers,email',
        ]);

        $customer = Customer::create($validated + $request->only(['phone', 'notes']));
        return response()->json($customer, 201);
    }

    public function show($id) {
        return response()->json(Customer::findOrFail($id));
    }

    public function update(Request $request, $id) {
        $customer = Customer::findOrFail($id);
        $customer->update($request->all());
        return response()->json($customer);
    }

    public function destroy($id) {
        Customer::destroy($id);
        return response()->json(['message' => 'Customer deleted']);
    }
}/textarea>/pre>svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4">/path>path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2">/path>/svg>pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0">namespace App\Http\Controllers\API;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Customer;

class CustomerController extends Controller
{
    public function index() {
        return response()->json(Customer::all(), 200);
    }

    public function store(Request $request) {
        $validated = $request->validate([
            'name' => 'required|string',
            'email' => 'required|email|unique:customers,email',
        ]);

        $customer = Customer::create($validated + $request->only(['phone', 'notes']));
        return response()->json($customer, 201);
    }

    public function show($id) {
        return response()->json(Customer::findOrFail($id));
    }

    public function update(Request $request, $id) {
        $customer = Customer::findOrFail($id);
        $customer->update($request->all());
        return response()->json($customer);
    }

    public function destroy($id) {
        Customer::destroy($id);
        return response()->json(['message' => 'Customer deleted']);
    }
}/pre>
```

### Update the Model

```
svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14">g fill="none" fill-rule="evenodd" transform="translate(1 1)">circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5">/circle>circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5">/circle>circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5">/circle>/g>/svg>pre class="code-block-pro-copy-button-pre" aria-hidden="true">textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>protected $fillable = ['name', 'email', 'phone', 'notes'];/textarea>/pre>svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4">/path>path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2">/path>/svg>pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0">protected $fillable = ['name', 'email', 'phone', 'notes'];/pre>
```

### 🌐 c) API Routes

Update `routes/api.php`:

```
svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14">g fill="none" fill-rule="evenodd" transform="translate(1 1)">circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5">/circle>circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5">/circle>circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5">/circle>/g>/svg>pre class="code-block-pro-copy-button-pre" aria-hidden="true">textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>use App\Http\Controllers\API\CustomerController;

Route::apiResource('customers', CustomerController::class);/textarea>/pre>svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4">/path>path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2">/path>/svg>pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0">use App\Http\Controllers\API\CustomerController;

Route::apiResource('customers', CustomerController::class);/pre>
```

## 🎨 6. Frontend: Vue 3 + Vite Setup

### 🧰 a) Install Vue 3

```
svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14">g fill="none" fill-rule="evenodd" transform="translate(1 1)">circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5">/circle>circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5">/circle>circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5">/circle>/g>/svg>pre class="code-block-pro-copy-button-pre" aria-hidden="true">textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>npm install
npm install vue@next vue-router@4 axios
npm run dev/textarea>/pre>svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4">/path>path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2">/path>/svg>pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0">npm install
npm install vue@next vue-router@4 axios
npm run dev/pre>
```

### `resources/js/app.js`

```
svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14">g fill="none" fill-rule="evenodd" transform="translate(1 1)">circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5">/circle>circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5">/circle>circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5">/circle>/g>/svg>pre class="code-block-pro-copy-button-pre" aria-hidden="true">textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>import { createApp } from 'vue';
import App from './App.vue';

createApp(App).mount('#app');/textarea>/pre>svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4">/path>path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2">/path>/svg>pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0">import { createApp } from 'vue';
import App from './App.vue';

createApp(App).mount('#app');/pre>
```

### 🔲 b) Basic Component: `CustomerList.vue`

```
svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14">g fill="none" fill-rule="evenodd" transform="translate(1 1)">circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5">/circle>circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5">/circle>circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5">/circle>/g>/svg>pre class="code-block-pro-copy-button-pre" aria-hidden="true">textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly><template>
  <div>
    <h2 class="text-xl font-bold mb-4">Customer List</h2>
    <ul>
      <li v-for="customer in customers" :key="customer.id" class="mb-2">
        {{ customer.name }} – {{ customer.email }}
        <button @click="deleteCustomer(customer.id)" class="text-red-600">Delete</button>
      </li>
    </ul>
  </div>
</template>

<script>
import axios from 'axios'

export default {
  data() {
    return {
      customers: []
    }
  },
  mounted() {
    this.getCustomers()
  },
  methods: {
    getCustomers() {
      axios.get('/api/customers').then(res => {
        this.customers = res.data
      })
    },
    deleteCustomer(id) {
      axios.delete(`/api/customers/${id}`).then(() => this.getCustomers())
    }
  }
}
</script>/textarea>/pre>svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4">/path>path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2">/path>/svg>pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><template>
  <div>
    <h2 class="text-xl font-bold mb-4">Customer List</h2>
    <ul>
      <li v-for="customer in customers" :key="customer.id" class="mb-2">
        {{ customer.name }} – {{ customer.email }}
        <button @click="deleteCustomer(customer.id)" class="text-red-600">Delete</button>
      </li>
    </ul>
  </div>
</template>

<script>
import axios from 'axios'

export default {
  data() {
    return {
      customers: []
    }
  },
  mounted() {
    this.getCustomers()
  },
  methods: {
    getCustomers() {
      axios.get('/api/customers').then(res => {
        this.customers = res.data
      })
    },
    deleteCustomer(id) {
      axios.delete(`/api/customers/${id}`).then(() => this.getCustomers())
    }
  }
}
</script>/pre>
```

### 🧩 `App.vue`

```
svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14">g fill="none" fill-rule="evenodd" transform="translate(1 1)">circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5">/circle>circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5">/circle>circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5">/circle>/g>/svg>pre class="code-block-pro-copy-button-pre" aria-hidden="true">textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly><template>
  <div class="max-w-4xl mx-auto p-6">
    <h1 class="text-3xl font-bold mb-6">Custom CRM</h1>
    <CustomerList />
  </div>
</template>

<script>
import CustomerList from './components/CustomerList.vue'

export default {
  components: { CustomerList }
}
</script>
/textarea>/pre>svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4">/path>path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2">/path>/svg>pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><template>
  <div class="max-w-4xl mx-auto p-6">
    <h1 class="text-3xl font-bold mb-6">Custom CRM</h1>
    <CustomerList />
  </div>
</template>

<script>
import CustomerList from './components/CustomerList.vue'

export default {
  components: { CustomerList }
}
</script>
/pre>
```

---

## 🔐 7. Authentication with Laravel Sanctum

Install Laravel Sanctum:

```
svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14">g fill="none" fill-rule="evenodd" transform="translate(1 1)">circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5">/circle>circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5">/circle>circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5">/circle>/g>/svg>pre class="code-block-pro-copy-button-pre" aria-hidden="true">textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate/textarea>/pre>svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4">/path>path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2">/path>/svg>pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0">composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate/pre>
```

In `api.php`:

```
svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14">g fill="none" fill-rule="evenodd" transform="translate(1 1)">circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5">/circle>circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5">/circle>circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5">/circle>/g>/svg>pre class="code-block-pro-copy-button-pre" aria-hidden="true">textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>Route::middleware('auth:sanctum')->group(function () {
    Route::apiResource('customers', CustomerController::class);
});/textarea>/pre>svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4">/path>path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2">/path>/svg>pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0">Route::middleware('auth:sanctum')->group(function () {
    Route::apiResource('customers', CustomerController::class);
});/pre>
```

## 🎨 8. UI Enhancements with Tailwind CSS

Install Tailwind:

```
svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14">g fill="none" fill-rule="evenodd" transform="translate(1 1)">circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5">/circle>circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5">/circle>circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5">/circle>/g>/svg>pre class="code-block-pro-copy-button-pre" aria-hidden="true">textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>npm install -D tailwindcss
npx tailwindcss init/textarea>/pre>svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4">/path>path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2">/path>/svg>pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0">npm install -D tailwindcss
npx tailwindcss init/pre>
```

Add Tailwind to `resources/css/app.css`:

```
svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14">g fill="none" fill-rule="evenodd" transform="translate(1 1)">circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5">/circle>circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5">/circle>circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5">/circle>/g>/svg>pre class="code-block-pro-copy-button-pre" aria-hidden="true">textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>@tailwind base;
@tailwind components;
@tailwind utilities;/textarea>/pre>svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4">/path>path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2">/path>/svg>pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0">@tailwind base;
@tailwind components;
@tailwind utilities;/pre>
```

Update Vite config and re-run:

```
svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14">g fill="none" fill-rule="evenodd" transform="translate(1 1)">circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5">/circle>circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5">/circle>circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5">/circle>/g>/svg>pre class="code-block-pro-copy-button-pre" aria-hidden="true">textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>npm run dev/textarea>/pre>svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4">/path>path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2">/path>/svg>pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0">npm run dev/pre>
```

Your CRM now looks clean, responsive, and modern.

---

## ✅ 9. Testing the Custom CRM

Use **Postman** or Vue UI:

- **Create** a customer → POST `/api/customers`
- **Read** all customers → GET `/api/customers`
- **Update** customer → PUT `/api/customers/{id}`
- **Delete** customer → DELETE `/api/customers/{id}`

---

## 🚀 10. Future Improvements

To scale this CRM further:

- Add **search, filters, pagination**
- Export customer data to CSV/PDF
- Add **[email/SMS integrations](https://linkedIn.com/company/kavcomexpert)**
- Add user roles: admin, staff, sales
- Build dashboards with charts (Chart.js)

---

## 🧠 11. Conclusion

By building a CRM from scratch using **Laravel + Vue.js**, you gain **full control, flexibility, and scalability**. This is ideal for teams who want software tailored to their operations—no extra features, no subscriptions, no compromises.

Whether you're a dev building your own tool or an agency like **[Kavcom Expert](https://kavcomexpert.com/)** creating solutions for clients—this stack delivers.

---

### 📢 Want to Build a CRM for Your Team?

At **[Kavcom Expert](https://kavcomexpert.com/contact/)**, we build custom CRMs, dashboards, and SaaS products for businesses in the US and globally. Contact us for tailored development.
