<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Faisal Malik</title>
    <description>The latest articles on Forem by Faisal Malik (@faisal_malik_544).</description>
    <link>https://forem.com/faisal_malik_544</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3639107%2Fc5d0b3fa-c841-432d-94cf-a63893f3aff6.webp</url>
      <title>Forem: Faisal Malik</title>
      <link>https://forem.com/faisal_malik_544</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/faisal_malik_544"/>
    <language>en</language>
    <item>
      <title>Roles &amp; Module-Based Permissions in VUE, Laravel &amp; Tailwind</title>
      <dc:creator>Faisal Malik</dc:creator>
      <pubDate>Thu, 15 Jan 2026 19:05:08 +0000</pubDate>
      <link>https://forem.com/faisal_malik_544/roles-module-based-permissions-in-vue-laravel-tailwind-4g63</link>
      <guid>https://forem.com/faisal_malik_544/roles-module-based-permissions-in-vue-laravel-tailwind-4g63</guid>
      <description>&lt;p&gt;I recently built a role-based and module-based CMS using Laravel 12 for the backend and Vue 3 with Tailwind CSS for the frontend. The system supports fine-grained permissions, where users can have multiple roles, and their access is dynamically controlled based on assigned modules and components. Authentication is handled securely with Laravel Sanctum, and the frontend leverages the Composition API for a modern, reactive experience.&lt;/p&gt;

&lt;p&gt;You can explore the full project on GitHub and try it out here: &lt;a href="https://github.com/malickfaisal/cms-permission-vue-laravel" rel="noopener noreferrer"&gt;cms-permission-vue-laravel&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Set up Sanctum for API Authentication&lt;/strong&gt;&lt;br&gt;
We'll use the Spatie Laravel Permission package. It’s widely used and battle-tested.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1.&lt;/strong&gt; Install Laravel Sanctum:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;composer require laravel/sanctum
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2.&lt;/strong&gt; Publish config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3.&lt;/strong&gt; Run migrations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2: Install Spatie Role &amp;amp; Permission&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;composer require spatie/laravel-permission
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
php artisan migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update User model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use Spatie\Permission\Traits\HasRoles;

class User extends Authenticatable
{
    use HasRoles;
    ....
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3: Add Modules &amp;amp; Components&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;1.&lt;/strong&gt; Module Migration&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan make:migration create_modules_table 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update Migration database/migrations/xxxx_xx_xx_xxxx_create_modules_table.php&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration {
    public function up(): void
    {
        Schema::create('modules', function (Blueprint $table) {
            $table-&amp;gt;id();
            $table-&amp;gt;string('name');
            $table-&amp;gt;string('slug'); 
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('modules');
    }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan make:migration create_modules_components_table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2.&lt;/strong&gt; Components Migration&lt;br&gt;
Update Migration database/migrations/xxxx_xx_xx_xxxx_create_modules_components_table.php&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration {
    public function up(): void
    {
        Schema::create('modules_components', function (Blueprint $table) {
            $table-&amp;gt;id();

            // Foreign key to modules table (optional)
            $table-&amp;gt;unsignedBigInteger('module_id');

            $table-&amp;gt;string('name');
            $table-&amp;gt;string('slug'); // Not unique

            $table-&amp;gt;timestamps();

            // Optional foreign key constraint
            $table-&amp;gt;foreign('module_id')
                  -&amp;gt;references('id')
                  -&amp;gt;on('modules')
                  -&amp;gt;onDelete('cascade');
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('modules_components');
    }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3.&lt;/strong&gt; Alter Permission Migration&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan make:migration create_permission_alter_table 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update Migration database/migrations/xxxx_xx_xx_xxxx_create_permission_alter_table.php&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
&amp;lt;?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::table('permissions', function (Blueprint $table) {
            // Example: Add new columns
            $table-&amp;gt;unsignedBigInteger('module_id')-&amp;gt;after('id');
            $table-&amp;gt;unsignedBigInteger('component_id')-&amp;gt;after('module_id');
            $table-&amp;gt;string('label')-&amp;gt;after('component_id');

        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::table('permissions', function (Blueprint $table) {
            // Drop the columns added in up()
            $table-&amp;gt;dropColumn('module_id');
            $table-&amp;gt;dropColumn('component_id');
            $table-&amp;gt;dropColumn('label');
        });
    }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4.&lt;/strong&gt; Create Models for Module, Components, Permissions&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan make:model Module
php artisan make:model ModuleComponent
php artisan make:model ModuleHasPermission
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5.&lt;/strong&gt; Create Seeder&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan make:seeder DatabaseSeeder
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy &amp;amp; paste the below function in database/seeders/DatabaseSeeder.php&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php

namespace Database\Seeders;

use App\Models\User;
use App\Models\Modules\Module;
use App\Models\Modules\ModuleComponent;
use App\Models\Modules\Permission;
use App\Models\Modules\ModuleHasPermission;
use Spatie\Permission\Models\Role;
use Illuminate\Support\Facades\Hash;
use Illuminate\Database\Seeder;

class DatabaseNewSeeder extends Seeder
{
    public function run(): void
    {
        // Create default user
        $user = User::factory()-&amp;gt;create([
            'name' =&amp;gt; 'Test Admin',
            'email' =&amp;gt; 'admin@example.com',
            'password' =&amp;gt; Hash::make('12345678'),
        ]);

        // ------------------
        // Create roles
        // ------------------
        $adminRole = Role::firstOrCreate(['name' =&amp;gt; 'admin']);
        $userRole  = Role::firstOrCreate(['name' =&amp;gt; 'user']);

        // Assign admin role to test user
        $user-&amp;gt;syncRoles([$adminRole-&amp;gt;name]);

        // ------------------
        // Modules + Components + Permissions
        // ------------------
        $modules = [
            'Users' =&amp;gt; ['Details', 'Permissions'],
            'Tasks' =&amp;gt; ['List'],
            'Settings' =&amp;gt; ['Details']
        ];

        foreach ($modules as $moduleName =&amp;gt; $components) {
            $module = Module::create([
                'name' =&amp;gt; $moduleName,
                'slug' =&amp;gt; strtolower($moduleName),
            ]);

            foreach ($components as $componentName) {
                $component = ModuleComponent::create([
                    'module_id' =&amp;gt; $module-&amp;gt;id,
                    'name' =&amp;gt; $componentName,
                    'slug' =&amp;gt; strtolower($componentName),
                ]);

                // Create permissions for each component
                $crudLabels = ['View', 'Add', 'Edit', 'Delete'];
                foreach ($crudLabels as $label) {
                    $permissionName = strtolower($moduleName) . '.' . strtolower($componentName) . '.' . strtolower($label);
                    $permission = Permission::create([
                        'module_id' =&amp;gt; $module-&amp;gt;id,
                        'component_id' =&amp;gt; $component-&amp;gt;id,
                        'label' =&amp;gt; $label,
                        'name' =&amp;gt; $permissionName,
                        'guard_name' =&amp;gt; 'web',
                    ]);

                    ModuleHasPermission::create([
                        'permission_id' =&amp;gt; $permission-&amp;gt;id,
                        'model_id' =&amp;gt; $user-&amp;gt;id,
                        'model_type' =&amp;gt; User::class,
                    ]);
                }
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Run&lt;/strong&gt; the seeder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan db:seed --class=DatabaseSeeder
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 5: Create Auth API&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;1.&lt;/strong&gt; Create AuthController&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan make:controller AuthController
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php

namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class AuthController extends Controller
{
    public function login(Request $request)
    {
        $request-&amp;gt;validate([
            'email'=&amp;gt;'required|email',
            'password'=&amp;gt;'required'
        ]);

        $user = User::where('email', $request-&amp;gt;email)-&amp;gt;first();

        if (!$user || !Hash::check($request-&amp;gt;password, $user-&amp;gt;password)) {
            throw ValidationException::withMessages([
                'email' =&amp;gt; ['Invalid credentials.']
            ]);
        }

        $token = $user-&amp;gt;createToken('api-token')-&amp;gt;plainTextToken;

        return response()-&amp;gt;json([
            'user'=&amp;gt;$user,
            'token'=&amp;gt;$token
        ]);
    }
    public function show_permissions()
    {
        $auth = Auth::user(); 
        $admin['detail'] = $auth;
        $admin['roles'] = $auth-&amp;gt;getRoleNames();
        $admin['permissions'] = $auth-&amp;gt;getPermissionNames();
        return response()-&amp;gt;json($admin, '201');
    }
    public function logout(Request $request)
    {
        $request-&amp;gt;user()-&amp;gt;currentAccessToken()-&amp;gt;delete();
        return response()-&amp;gt;json(['message'=&amp;gt;'Logged out']);
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2.&lt;/strong&gt; Create UserController&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan make:controller UserController
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php

namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
use Illuminate\Http\Request;

class UserController extends Controller
{
   public function index()
    {  
        $users = User::get();
        return response()-&amp;gt;json($users, 200);
    }

    public function store(Request $request)
    {
        $request-&amp;gt;validate([
            'role'=&amp;gt;'required',
            'name'=&amp;gt;'required',
            'email'=&amp;gt;'required|email|unique:users',
            'password'=&amp;gt;'required|min:6'
        ]);

        $user = User::create([
            'name'=&amp;gt;$request-&amp;gt;name,
            'email'=&amp;gt;$request-&amp;gt;email,
            'password'=&amp;gt;Hash::make($request-&amp;gt;password),
        ]);

        $user-&amp;gt;assignRole($request-&amp;gt;role); 

        return response()-&amp;gt;json($user, 201);
    }

    public function update(int $id, Request $request)
    {

        $user = User::findOrFail($id);

        $request-&amp;gt;validate([
            'role' =&amp;gt; 'required|string', 
            'name'  =&amp;gt; 'required|string|max:255',
            'email' =&amp;gt; [
                'required',
                'email',
                Rule::unique('users')-&amp;gt;ignore($user-&amp;gt;id),
            ],
        ]);

        $user-&amp;gt;update([
            'name'  =&amp;gt; $request-&amp;gt;name,
            'email' =&amp;gt; $request-&amp;gt;email,
        ]);

        $user-&amp;gt;syncRoles([$request-&amp;gt;role]);

        return response()-&amp;gt;json($user, 201);
    }

    public function show(int $id)
    {
        $user = User::findOrFail($id); 
        $user-&amp;gt;role = $user-&amp;gt;getRoleNames();
        return response()-&amp;gt;json($user, 201);
    }

    public function destroy(int $id)
    {
        $user = User::findOrFail($id);
        $delete =  $user-&amp;gt;delete();

        return response()-&amp;gt;json($delete, 201);
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3.&lt;/strong&gt; Create ModuleController&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan make:controller ModuleController
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Modules\Module;
use App\Models\Modules\ModuleComponent;
use App\Models\Modules\Permission;
use App\Models\Modules\ModuleHasPermission;

class ModuleController extends Controller
{

    public function index()
    {
        $modules = Module::get();
        foreach($modules as $key =&amp;gt; $row){
            $data['module_id'] = $row-&amp;gt;id;
            $data['module_name'] = $row-&amp;gt;label;
            $permissions = Permission::where('module_id', $row-&amp;gt;id)-&amp;gt;get();
            $data['module_permissions'] = $permissions;
        }             
        return response()-&amp;gt;json($module, 201);
    }      
    public function permission(int $user)
    {
        $actions = array('View', 'Add', 'Edit', 'Delete');
        $modules = Module::get();
        foreach($modules as $key =&amp;gt; $row){
            $data[$key]['module_id'] = $row-&amp;gt;id;
            $data[$key]['module_name'] = $row-&amp;gt;name;

            $components = ModuleComponent::where('module_id', $row-&amp;gt;id)-&amp;gt;get();
            $component = array();
            foreach($components as $i =&amp;gt; $comp){
                $component[$i]['component_label'] = $comp-&amp;gt;name;
                $action_arry = [];
                foreach($actions as $j =&amp;gt; $action){
                    $permission = Permission::where('module_id', $row-&amp;gt;id)-&amp;gt;where('component_id', $comp-&amp;gt;id)-&amp;gt;where('label', $action)-&amp;gt;first();
                    $per = array();
                    if(isset($permission-&amp;gt;id)){
                        $per['per_id'] = $permission-&amp;gt;id;
                        $per['per_label'] = $permission-&amp;gt;label;
                        $per['per_name'] = $permission-&amp;gt;name;
                        $admin_per = ModuleHasPermission::where('model_id', $user)-&amp;gt;where('permission_id', $permission-&amp;gt;id)-&amp;gt;first();
                        if(isset($admin_per-&amp;gt;model_id)){
                            $per['check'] = 1;
                        }else{
                            $per['check'] = 0;
                        }
                    }
                    $action_arry[$j] = $per;
                }
                $component[$i]['component_actions'] = $action_arry;
            }
            $data[$key]['module_permissions'] = $component;
        }             
        return response()-&amp;gt;json($data, 201);
    }  

    public function store(int $user, Request $request)
    {

        $modules = $request-&amp;gt;all();
         foreach($modules as $key =&amp;gt; $row){
            foreach($row['module_permissions'] as $i =&amp;gt; $comp){
                foreach($comp['component_actions'] as $ind =&amp;gt; $val){
                    if(isset($val['check']) &amp;amp;&amp;amp; $val['check'] == 1){
                        $admin_per = ModuleHasPermission::where('model_id', $user)-&amp;gt;where('permission_id', $val['per_id'])-&amp;gt;first();
                        if(empty($admin_per)){
                            $per_create = ModuleHasPermission::create([
                                'model_id' =&amp;gt; $user,
                                'permission_id' =&amp;gt; $val['per_id'],
                                'model_type' =&amp;gt; 'App\Models\User',
                            ]);
                        }
                    }
                    if(isset($val['check']) &amp;amp;&amp;amp; $val['check'] == 0){
                        $admin_per = ModuleHasPermission::where('model_id', $user)-&amp;gt;where('permission_id', $val['per_id'])-&amp;gt;first();
                        if(isset($admin_per-&amp;gt;model_id)){
                            $per_delete = ModuleHasPermission::where('model_id', $user)-&amp;gt;where('permission_id', $val['per_id'])-&amp;gt;delete();
                        }
                    }
                }
            }
        } 


        return response()-&amp;gt;json($modules, 201);
    }  
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4.&lt;/strong&gt; Add routes routes/api.php:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\AuthController;
use App\Http\Controllers\UserController;
use App\Http\Controllers\ModuleController;

Route::post('/register', [AuthController::class, 'register']);
Route::post('/login', [AuthController::class, 'login']);

Route::middleware('auth:sanctum')-&amp;gt;group(function () {
    Route::post('/logout', [AuthController::class, 'logout']);
    Route::get("/users-permissions", [AuthController::class, "show_permissions"]);

    // Users
    Route::apiResource("/users", UserController::class);
    //Permissions
    Route::get("/users/{user}/permissions", [ModuleController::class, "permission"]);
    Route::post("/users/{user}/permissions", [ModuleController::class, "store"]);
});

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;strong&gt;Step 6: Set up Vue 3 Frontend&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1.&lt;/strong&gt; Create a Vue 3 project (in the same folder or another folder):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm create vite@latest frontend
cd frontend
npm install
npm install axios pinia vue-router
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2.&lt;/strong&gt; Axios API Global Config&lt;br&gt;
src/stores/api.js&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import axios from 'axios';

axios.defaults.baseURL = 'http://127.0.0.1:8000/api';
axios.defaults.headers.common['Accept'] = 'application/json';

const token = localStorage.getItem('token');
if (token) {
  axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
}

export default axios;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3.&lt;/strong&gt; Set up Pinia Store&lt;/p&gt;

&lt;p&gt;src/stores/auth.js&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { defineStore } from 'pinia';
import axios from 'axios';
import api from './api';

const api_url = 'http://127.0.0.1:8000';

export const useAuthStore = defineStore('auth', {
  state: () =&amp;gt; ({
    user: null,
    token: localStorage.getItem('token') || null,
  }),

  getters: {
    isLoggedIn: (state) =&amp;gt; !!state.token,
  },

  actions: {
    async login(credentials) {
        axios.get(api_url+'/sanctum/csrf-cookie')
        const res = await axios.post(api_url+'/api/login', credentials);

        this.user = res.data.user;
        this.token = res.data.token;

        localStorage.setItem('token', this.token);
        axios.defaults.headers.common['Authorization'] = `Bearer ${this.token}`;
    },
    async logout() {
      await api.post('/logout');

      this.user = null;
      this.token = null;

      localStorage.removeItem('token');
      delete axios.defaults.headers.common['Authorization'];
    },
  },
});

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;src/stores/permission.js&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
import { defineStore } from 'pinia'
import api from './api';

export const usePermissionStore = defineStore('permission', {
  state: () =&amp;gt; ({
    user: null,
    roles: [],
    permissions: [],
    loaded: false,
  }),
  actions: {
    async fetchUser() {
      await api.get(`/users-permissions`)
      .then((resp) =&amp;gt; {            
        this.user = resp.data.detail
        this.roles = resp.data.roles
        this.permissions = resp.data.permissions
        this.loaded = true
      })
    },
    can(check_permission) {
      return this.permissions.includes(check_permission)
    },
    canRole(check_role) {
      return this.roles.includes(check_role)
    }
  }
})

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4.&lt;/strong&gt; Vue Router with Auth Guard&lt;/p&gt;

&lt;p&gt;src/router/index.js&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { createRouter, createWebHistory } from 'vue-router';
import { useAuthStore } from '../stores/auth';
import { usePermissionStore } from '../stores/permission'
import Login from '../views/Login.vue';
import Dashboard from '../views/Dashboard.vue';
import Task from '../views/Task.vue';

const routes = [
  { path: '/login', component: Login },
  {
    path: '/dashboard',
    component: Dashboard,
    meta: { 
      requiresAuth: true,
    },
  },
  {
    path: '/tasks',
    component: Task,
    meta: { 
      requiresAuth: true,
      permission: ['tasks.list.view']
    },
  },
  { path: '/', redirect: '/dashboard' },
];

const router = createRouter({
  history: createWebHistory(),
  routes,
});

router.beforeEach(async (to, from, next) =&amp;gt; {
  const auth = useAuthStore();
  const adminStore = usePermissionStore()

  // Load user/permissions if needed
  if (!adminStore.loaded &amp;amp;&amp;amp; auth.isLoggedIn) {
    await adminStore.fetchUser()
  }

  if (to.matched.some(record =&amp;gt; record.meta.requiresAuth)) {
    if (!auth.isLoggedIn) {
      next({
        path: '/login',
        query: { redirect: to.fullPath }
      });
    } else {
      // Check permission and role if defined
      const requiredRoles = to.meta.role
      const requiredPermissions = to.meta.permission
      if (requiredRoles &amp;amp;&amp;amp; !requiredRoles.some(r =&amp;gt; adminStore.canRole(r))) {
        next({ path: '/unauthorized' })
      } 
      else if (requiredPermissions &amp;amp;&amp;amp; !requiredPermissions.some(p =&amp;gt; adminStore.can(p))) {
        next({ path: '/unauthorized' })
      } else {
        next()
      }
    }
  } else if (to.matched.some(record =&amp;gt; record.meta.guestOnly)) {
    if (auth.isLoggedIn) {
      next({ path: '/' }) // Redirect logged-in user away from guest-only pages
    } else {
      next()
    }
  } else {
    next()
  }
})

export default router;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5.&lt;/strong&gt; Change main.js&lt;/p&gt;

&lt;p&gt;src/main.js&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { createApp } from 'vue'
import { createPinia } from 'pinia';
import router from './router';
import './style.css'
import App from './App.vue'

createApp(App)
  .use(createPinia())
  .use(router)
  .mount('#app');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Login Screen&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;src/views/Login.vue&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
&amp;lt;template&amp;gt;

  &amp;lt;div class="min-h-screen flex items-center justify-center"&amp;gt;
    &amp;lt;div class="w-full max-w-md bg-white p-8 rounded-xl shadow-lg"&amp;gt;
      &amp;lt;h2 class="text-2xl font-bold text-center mb-6 text-gray-800 "&amp;gt;
        Login to your account
      &amp;lt;/h2&amp;gt;

      &amp;lt;form @submit.prevent="submit" class="space-y-4"&amp;gt;
        &amp;lt;div&amp;gt;
          &amp;lt;label class="block text-sm font-medium text-gray-600"&amp;gt;Email&amp;lt;/label&amp;gt;
          &amp;lt;input
            v-model="email"
            type="email"
            class=" text-black bg-gray-100 mt-1 w-full px-4 py-2 border-gray-300 border-1 rounded-lg focus:ring focus:ring-indigo-200"
            required
          /&amp;gt;
        &amp;lt;/div&amp;gt;

        &amp;lt;div&amp;gt;
          &amp;lt;label class="block text-sm font-medium text-gray-600"&amp;gt;Password&amp;lt;/label&amp;gt;
          &amp;lt;input
            v-model="password"
            type="password"
            class="text-black bg-gray-100 mt-1 w-full px-4 py-2  border-gray-300 border-1 rounded-lg focus:ring focus:ring-indigo-200"
            required
          /&amp;gt;
        &amp;lt;/div&amp;gt;

        &amp;lt;button
          type="submit"
          class="w-full bg-indigo-600 text-white py-2 rounded-lg hover:bg-indigo-700 transition mt-2"
        &amp;gt;
          Login
        &amp;lt;/button&amp;gt;

        &amp;lt;p v-if="error" class="text-red-500 text-sm text-center"&amp;gt;
          {{ error }}
        &amp;lt;/p&amp;gt;
      &amp;lt;/form&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script setup&amp;gt;
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import { useAuthStore } from '@/stores/auth';
import { usePermissionStore } from '@/stores/permission'

const email = ref('');
const password = ref('');
const error = ref('');
const router = useRouter();
const auth = useAuthStore();
const permission = usePermissionStore();

const submit = async () =&amp;gt; {
  try {
    await auth.login({ email: email.value, password: password.value });
    permission.fetchUser();
    router.push('/dashboard');
  } catch {
    error.value = 'Invalid credentials';
  }
};
&amp;lt;/script&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;7.&lt;/strong&gt; Dashboard Screen&lt;br&gt;
src/Dashboard.vue&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;h1&amp;gt;Dashboard&amp;lt;/h1&amp;gt;

    &amp;lt;h1 class="text-3xl font-bold mb-4"&amp;gt;Welcome, {{ permissionStore.user?.name }}&amp;lt;/h1&amp;gt;

    &amp;lt;p class="text-gray-600 mb-6" v-if="permissionStore.user?.roles[0]?.name"&amp;gt;
      Roles: &amp;lt;span class="font-medium text-indigo-600 capitalize"&amp;gt;{{ permissionStore.user?.roles[0]?.name }}&amp;lt;/span&amp;gt;
    &amp;lt;/p&amp;gt;

    &amp;lt;div class="bg-white p-8 rounded-xl shadow mb-5" v-if="permissionStore.canRole('admin')"&amp;gt;
      &amp;lt;h1 class="text-3xl font-bold mb-6 text-indigo-700"&amp;gt;Admin Panel&amp;lt;/h1&amp;gt;
      &amp;lt;p&amp;gt;This is admin section.&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div class="bg-white p-8 rounded-xl shadow mb-5" v-if="permissionStore.can('settings.details.view')"&amp;gt;
      &amp;lt;h1 class="text-3xl font-bold mb-6 text-indigo-700"&amp;gt;Setting Panel&amp;lt;/h1&amp;gt;
      &amp;lt;p&amp;gt;This is settings section.&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div class="bg-white p-8 rounded-xl shadow mb-5" v-if="permissionStore.can('tasks.list.view')"&amp;gt;
      &amp;lt;h1 class="text-3xl font-bold mb-6 text-indigo-700"&amp;gt;Task Panel&amp;lt;/h1&amp;gt;
      &amp;lt;router-link to="/tasks" class="bg-indigo-600 text-white px-4 py-2 rounded hover:bg-indigo-700" v-if="permissionStore.can('tasks.list.view')" &amp;gt;
        View Tasks
      &amp;lt;/router-link&amp;gt;
    &amp;lt;/div&amp;gt;

    &amp;lt;button class='bg-indigo-600 text-white px-4 py-2 rounded hover:bg-indigo-700' @click="logout"&amp;gt;Logout&amp;lt;/button&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script setup&amp;gt;
import { useAuthStore } from '@/stores/auth';
import { usePermissionStore } from '@/stores/permission'
import { useRouter } from 'vue-router';

const auth = useAuthStore();
const permissionStore = usePermissionStore();
const router = useRouter();

const logout = async () =&amp;gt; {
  await auth.logout();
  router.push('/login');
};
&amp;lt;/script&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;8.&lt;/strong&gt; Task Screen&lt;br&gt;
src/Task.vue&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;div class="flex justify-between items-center mb-6"&amp;gt;
      &amp;lt;h1 class="text-2xl font-bold"&amp;gt;Tasks&amp;lt;/h1&amp;gt;
      &amp;lt;button type="button" v-if="permissionStore.can('tasks.list.add')" class="bg-indigo-600 text-white px-4 py-2 rounded hover:bg-indigo-700" &amp;gt;+ Add Task&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;

    &amp;lt;div class="bg-white shadow rounded-lg overflow-hidden"&amp;gt;
      &amp;lt;table class="w-full"&amp;gt;
        &amp;lt;thead class="bg-gray-800 text-white"&amp;gt;
          &amp;lt;tr&amp;gt;
            &amp;lt;th class="px-4 py-3 text-left"&amp;gt;Title&amp;lt;/th&amp;gt;
            &amp;lt;th class="px-4 py-3 text-left"&amp;gt;Action Date&amp;lt;/th&amp;gt;
            &amp;lt;th class="px-4 py-3 text-left"&amp;gt;Due Date&amp;lt;/th&amp;gt;
            &amp;lt;th class="px-4 py-3 text-left"&amp;gt;Status&amp;lt;/th&amp;gt;
            &amp;lt;th class="px-4 py-3 text-left"&amp;gt;Actions&amp;lt;/th&amp;gt;
          &amp;lt;/tr&amp;gt;
        &amp;lt;/thead&amp;gt;

        &amp;lt;tbody&amp;gt;
          &amp;lt;tr
            v-for="task in tasks"
            :key="task.id"
            class="border-t border-t-gray-300 hover:bg-gray-50"
          &amp;gt;
            &amp;lt;td class="px-4 py-3"&amp;gt;{{ task.title }}&amp;lt;/td&amp;gt;
            &amp;lt;td class="px-4 py-3"&amp;gt;{{ task.action_date }}&amp;lt;/td&amp;gt;
            &amp;lt;td class="px-4 py-3"&amp;gt;{{ task.due_date }}&amp;lt;/td&amp;gt;
            &amp;lt;td class="px-4 py-3"&amp;gt;{{ task.status }}&amp;lt;/td&amp;gt;
            &amp;lt;td class="px-4 py-3 space-x-2"&amp;gt;
             &amp;lt;button type="button" v-if="permissionStore.can('tasks.list.edit')" class="text-indigo-600 hover:underline" &amp;gt;Edit&amp;lt;/button&amp;gt;
                &amp;lt;button type="button" @click="deleteTask" v-if="permissionStore.can('tasks.list.delete')" class="text-indigo-600 hover:underline" &amp;gt;Delete&amp;lt;/button&amp;gt;
            &amp;lt;/td&amp;gt;
          &amp;lt;/tr&amp;gt;
        &amp;lt;/tbody&amp;gt;
      &amp;lt;/table&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script setup&amp;gt;
const tasks = [
  {
    id: 1,
    title: 'Design login page UI',
    action_date: '05-01-2026',
    due_date: '07-01-2026',
    status: 'Pending',
  },
  {
    id: 2,
    title: 'Implement user authentication',
    action_date: '06-01-2026',
    due_date: '10-01-2026',
    status: 'In Progress',
  },
];



const deleteTask = async (id) =&amp;gt; {
  if (!confirm('Delete this task?')) return;
};

&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 7: Test Everything&lt;/strong&gt;&lt;br&gt;
Run Laravel API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run Vue 3 frontend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Github:&lt;/strong&gt;&lt;br&gt;
Check out the full CMS Permission System built with Laravel 12, Vue 3, and Tailwind CSS on GitHub: &lt;br&gt;
&lt;a href="https://github.com/malickfaisal/cms-permission-vue-laravel" rel="noopener noreferrer"&gt;https://github.com/malickfaisal/cms-permission-vue-laravel&lt;/a&gt;&lt;br&gt;
Explore the code, try it out, and see role-based &amp;amp; module-based permissions in action!&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>vue</category>
      <category>tailwindcss</category>
      <category>programming</category>
    </item>
    <item>
      <title>Build an AI-Powered Document Insights Tool with Django (Python), and React</title>
      <dc:creator>Faisal Malik</dc:creator>
      <pubDate>Thu, 08 Jan 2026 18:37:44 +0000</pubDate>
      <link>https://forem.com/faisal_malik_544/build-an-ai-powered-document-insights-tool-with-django-python-and-react-5bf7</link>
      <guid>https://forem.com/faisal_malik_544/build-an-ai-powered-document-insights-tool-with-django-python-and-react-5bf7</guid>
      <description>&lt;p&gt;Managing contracts, agreements, or any business documents can be time-consuming and prone to errors. What if you could automatically extract key information like parties, important dates, and clauses from PDFs, all powered by AI?&lt;/p&gt;

&lt;p&gt;In this tutorial, I’ll show you how to build a document insights tool using Django (Python) for the backend, React for the frontend, and OpenAI GPT API for AI-powered document analysis.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tech Stack&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Backend: Django + Django REST Framework&lt;/li&gt;
&lt;li&gt;Frontend: React + Axios&lt;/li&gt;
&lt;li&gt;Database: SQLite&lt;/li&gt;
&lt;li&gt;AI: OpenAI&lt;/li&gt;
&lt;li&gt;PDF Parsing: PyPDF2&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;1. Backend &lt;br&gt;
Step 1: Install Dependencies&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install django djangorestframework PyPDF2 openai
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2: Django Model for Documents&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In api/models.py:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
from django.db import models

class Document(models.Model):
    file = models.FileField(upload_to='documents/')
    uploaded_at = models.DateTimeField(auto_now_add=True)
    extracted_text = models.TextField(blank=True, null=True)
    insights = models.JSONField(blank=True, null=True)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run migrations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python manage.py makemigrations
python manage.py migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3: Serializer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In api/serializers.py:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
from rest_framework import serializers
from .models import Document

class DocumentSerializer(serializers.ModelSerializer):
    class Meta:
        model = Document
        fields = ['id', 'file', 'uploaded_at', 'extracted_text', 'insights']

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4: Django API View Using OpenAI GPT&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In api/views.py:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import Document
from .serializers import DocumentSerializer
from PyPDF2 import PdfReader
from openai import OpenAI

# Initialize OpenAI client
client = OpenAI(api_key="YOUR_OPENAI_API_KEY")

class UploadDocument(APIView):
    def post(self, request):
        file = request.FILES['file']
        doc = Document.objects.create(file=file)

        # Extract text from PDF
        reader = PdfReader(file)
        text = ""
        for page in reader.pages:
            text += page.extract_text() or ""
        doc.extracted_text = text

        # Prepare AI prompt
        prompt = f"""
        You are an AI assistant that extracts structured information from documents.
        For the following document text, provide:
        1. Parties involved
        2. Important dates
        3. Key clauses
        4. Any missing or inconsistent information
        Return as JSON.

        Document:
        {text}
        """

        # Call OpenAI API
        response = client.chat.completions.create(
            model="gpt-5-nano",  # use GPT-3.5 or GPT-4 if you have access
            messages=[{"role": "user", "content": prompt}]
        )

        # Save AI insights
        doc.insights = response.choices[0].message.content
        doc.save()

        serializer = DocumentSerializer(doc)
        return Response(serializer.data)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 5: URLs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In api/urls.py:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
from django.urls import path
from .views import UploadDocument

urlpatterns = [
    path('upload-document/', UploadDocument.as_view(), name='upload-document'),
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Include in project/urls.py:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from django.urls import path, include

urlpatterns = [
    path('api/', include('api.urls')),
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 6: React Frontend&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Install Axios:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install axios
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a new file: src/pages/DocumentInsights.jsx&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
import React, { useState } from 'react';
import axios from 'axios';

export default function DocumentInsights() {
  const [file, setFile] = useState(null);
  const [insights, setInsights] = useState(null);

  const handleUpload = async () =&amp;gt; {
    const formData = new FormData();
    formData.append("file", file);

    try {
      const { data } = await axios.post(
        "http://127.0.0.1:8000/api/upload-document/",
        formData
      );
      setInsights(JSON.parse(data.insights));
    } catch (error) {
      console.error(error);
    }
  };

  return (
    &amp;lt;div className="min-h-screen flex items-center justify-center"&amp;gt;
      &amp;lt;div className=" bg-gray-100 rounded-lg shadow-md p-6 w-full max-w-lg"&amp;gt;
        &amp;lt;h1 className="text-2xl font-semibold mb-4 text-center"&amp;gt;
          AI Document Insights
        &amp;lt;/h1&amp;gt;
        &amp;lt;div&amp;gt;
          &amp;lt;input  className="bg-white border rounded mb-4 p-4" type="file" onChange={(e) =&amp;gt; setFile(e.target.files[0])} /&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div className='block'&amp;gt;
          &amp;lt;button className='px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600' onClick={handleUpload}&amp;gt;Upload&amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
        {insights &amp;amp;&amp;amp; (
          &amp;lt;div&amp;gt;
            &amp;lt;h2&amp;gt;Parties:&amp;lt;/h2&amp;gt; {insights.parties.join(", ")}
            &amp;lt;h2&amp;gt;Dates:&amp;lt;/h2&amp;gt; {insights.dates.join(", ")}
            &amp;lt;h2&amp;gt;Key Clauses:&amp;lt;/h2&amp;gt; {insights.key_clauses.join(", ")}
            &amp;lt;h2&amp;gt;Missing Info:&amp;lt;/h2&amp;gt; {insights.missing_info.join(", ")}
          &amp;lt;/div&amp;gt;
        )}
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;src/App.js&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import DocumentInsights from "./pages/DocumentInsights";
import './App.css'

function App() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;DocumentInsights /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default App;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure your backend allows CORS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install django-cors-headers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In settings.py:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INSTALLED_APPS = [
    ....,
    'corsheaders',
]

MIDDLEWARE = [
    ...,
    'corsheaders.middleware.CorsMiddleware',
]

CORS_ALLOW_ALL_ORIGINS = True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 7: Test It&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Run&lt;/strong&gt; Django:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python manage.py runserver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Run&lt;/strong&gt; React:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Open&lt;/strong&gt; your browser, upload a PDF, and see AI-powered structured insights.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>react</category>
      <category>python</category>
    </item>
    <item>
      <title>Build an E-Signature with React, TailwindCSS, and Django Python</title>
      <dc:creator>Faisal Malik</dc:creator>
      <pubDate>Mon, 05 Jan 2026 16:05:27 +0000</pubDate>
      <link>https://forem.com/faisal_malik_544/build-an-e-signature-with-react-tailwindcss-and-django-5g08</link>
      <guid>https://forem.com/faisal_malik_544/build-an-e-signature-with-react-tailwindcss-and-django-5g08</guid>
      <description>&lt;p&gt;Learn how to create a fully functional e-signature application using React for the frontend, TailwindCSS for styling, and Django REST Framework to save signatures as PNG images. Perfect for contracts, forms, or any web application needing digital signatures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Digital signatures are increasingly common in web apps for signing forms, agreements, or contracts online. In this tutorial, we will build an e-signature app where users can draw their signature on a canvas and save it on the server as an image file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We’ll use:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React for the frontend&lt;/li&gt;
&lt;li&gt;TailwindCSS for styling&lt;/li&gt;
&lt;li&gt;react-signature-canvas for drawing the signature&lt;/li&gt;
&lt;li&gt;Django as a backend to receive and save the signature&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the end, you’ll have a full-stack solution ready for integration into any project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1️⃣ Frontend Setup: React + TailwindCSS&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Step 1: Install dependencies&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;First, make sure you have a React app. If not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install react-signature-canvas
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Optional but recommended)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install trim-canvas
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2: Create SignaturePad Component&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Create a new file: src/pages/SignaturePad.jsx&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useRef } from "react";
import SignatureCanvas from "react-signature-canvas";
export default function SignaturePage() {
  const sigRef = useRef(null);
  const clearSignature = () =&amp;gt; {
    if (!sigRef.current) return;
    sigRef.current.clear();
  };
  const saveSignature = () =&amp;gt; {
    if (!sigRef.current || sigRef.current.isEmpty()) {
      alert("Please sign first");
      return;
    }
    const canvas = sigRef.current.getCanvas();
    const dataUrl = canvas.toDataURL("image/png");
    console.log("Signature Base64:", dataUrl);
    // Needs to do: send to backend
  };
  return (

    &amp;lt;div className="min-h-screen flex items-center justify-center"&amp;gt;
      &amp;lt;div className=" bg-gray-100 rounded-lg shadow-md p-6 w-full max-w-lg"&amp;gt;
        &amp;lt;h1 className="text-2xl font-semibold mb-4 text-center"&amp;gt;
          E-Signature
        &amp;lt;/h1&amp;gt;
        &amp;lt;div className="bg-white border rounded mb-4"&amp;gt;
          &amp;lt;SignatureCanvas
            ref={sigRef}
            penColor="black"
            canvasProps={{
              width: 500,
              height: 200,
              className: "w-full h-auto"
            }}
          /&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div className="flex justify-between"&amp;gt;
          &amp;lt;button
            onClick={clearSignature}
            className="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600"
          &amp;gt;
            Clear
          &amp;lt;/button&amp;gt;
          &amp;lt;button
            onClick={saveSignature}
            className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
          &amp;gt;
            Save
          &amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2️⃣ Backend Setup: Django REST Framework&lt;br&gt;
Step 1: Install Django &amp;amp; DRF&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install django djangorestframework
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Create a Django project and app:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;django-admin startproject e-sign
cd e-sign
python manage.py startapp api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add rest_framework and your app to INSTALLED_APPS in &lt;strong&gt;settings.py&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INSTALLED_APPS = [
    ....,
    'rest_framework',
    'api',
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2: Create API Endpoint&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Create a view to save the signature:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# api/views.py
from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
import os
import base64
from django.conf import settings

class SaveSignature(APIView):
    def post(self, request):
        data_url = request.data.get("signature")
        if not data_url:
            return Response({"error": "No signature provided"}, status=status.HTTP_400_BAD_REQUEST)

        try:
            header, encoded = data_url.split(",", 1)
            binary_data = base64.b64decode(encoded)
        except Exception:
            return Response({"error": "Invalid signature format"}, status=status.HTTP_400_BAD_REQUEST)

        save_path = os.path.join(settings.BASE_DIR, "signatures")
        os.makedirs(save_path, exist_ok=True)
        # Save with unique filename
        import uuid
        file_name = os.path.join(save_path, f"{uuid.uuid4()}.png")

        with open(file_name, "wb") as f:
            f.write(binary_data)

        return Response({"message": f"Signature saved at {file_name}"}, status=status.HTTP_201_CREATED)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Explanation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;data_url.split(",", 1) separates the Base64 header from the actual data.&lt;/li&gt;
&lt;li&gt;base64.b64decode converts the Base64 string back to binary data.&lt;/li&gt;
&lt;li&gt;We save the signature in a signatures folder using a UUID filename to avoid collisions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Setup URLs&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# api/urls.py
from django.urls import path
from .views import SaveSignature

urlpatterns = [
    path("save-signature/", SaveSignature.as_view(), name="save-signature"),
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4: Include the app URLs in the project urls.py&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# e-sign/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path("admin/", admin.site.urls),
    path("api/", include("api.urls")),
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3️⃣ Connecting React to Django&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Your saveSignature function in React sends a POST request with the signature in Base64 format. Django receives it, decodes it, and saves it as a PNG.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const saveSignature = async () =&amp;gt; {
    if (!sigRef.current || sigRef.current.isEmpty()) {
      alert("Please sign first");
      return;
    }

    const canvas = sigRef.current.getCanvas();
    const dataUrl = canvas.toDataURL("image/png");

    try {
      const res = await fetch("http://127.0.0.1:8000/api/save-signature/", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ signature: dataUrl }),
      });

      const result = await res.json();
      console.log(result);
      alert(result.message);
    } catch (err) {
      console.error(err);
      alert("Failed to save signature");
    }
  };
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, running both the frontend (npm run dev) and backend (python manage.py runserver) will allow users to draw, clear, and save signatures.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In this tutorial, you learned how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a signature pad using react-signature-canvas&lt;/li&gt;
&lt;li&gt;Build a Django REST API to save signatures as PNG images&lt;/li&gt;
&lt;li&gt;Connect frontend and backend for a full-stack e-signature app&lt;/li&gt;
&lt;li&gt;This setup can be used for contracts, forms, and any web app requiring digital signatures.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>react</category>
      <category>django</category>
      <category>tailwindcss</category>
    </item>
    <item>
      <title>How to Add/Update Subscribers with Tags to Mailchimp Using PHP/Laravel</title>
      <dc:creator>Faisal Malik</dc:creator>
      <pubDate>Tue, 16 Dec 2025 16:18:54 +0000</pubDate>
      <link>https://forem.com/faisal_malik_544/how-to-addupdate-subscribers-with-tags-to-mailchimp-using-php-g18</link>
      <guid>https://forem.com/faisal_malik_544/how-to-addupdate-subscribers-with-tags-to-mailchimp-using-php-g18</guid>
      <description>&lt;p&gt;If you’re building a web app and need to add users to a Mailchimp audience programmatically, you can do it easily using PHP/Laravel and the Mailchimp API.&lt;br&gt;
In this post, I’ll show you how to add or update a subscriber, apply tags, and store merge fields like &lt;strong&gt;first name&lt;/strong&gt; and &lt;strong&gt;last name&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Set Up Your Mailchimp API Key&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can generate your API key from the Mailchimp dashboard:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to &lt;strong&gt;Account → Extras → API Keys&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Create a new key and copy it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;blockquote&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$apiKey = 'YOUR_API_KEY';
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Get Your Audience (List) ID&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To find your audience ID:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Navigate to Audience → Audience settings&lt;/li&gt;
&lt;li&gt;Copy the Audience ID&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;blockquote&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$listId = 'YOUR_LIST_ID';
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Identify Your Server Prefix&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Your Mailchimp server prefix is visible in the Mailchimp dashboard URL.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://us18.admin.mailchimp.com/" rel="noopener noreferrer"&gt;https://us18.admin.mailchimp.com/&lt;/a&gt;...&lt;br&gt;
So your server prefix would be:&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$server = 'us18';
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Step 4: Prepare the Subscriber Data&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Next, collect user data (usually from a form request) and define any tags you want to apply.&lt;/p&gt;

&lt;blockquote&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$email = $request-&amp;gt;email;
$tags = ['Tag 1']; // You can add multiple tags
$data = [
    'email_address' =&amp;gt; $email,
    'merge_fields' =&amp;gt; [
        'FNAME' =&amp;gt; $request-&amp;gt;fname,
        'LNAME' =&amp;gt; $request-&amp;gt;lname,
    ],
    'status_if_new' =&amp;gt; 'subscribed',
    'tags' =&amp;gt; $tags
];
$jsonData = json_encode($data);
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Step 5: Build the Mailchimp API URL&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Mailchimp identifies list members using an &lt;strong&gt;MD5 hash of the lowercase email address&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$url = 'https://' . $server . '.api.mailchimp.com/3.0/lists/' 
     . $listId . '/members/' . md5(strtolower($email));
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Step 6: Send the PUT Request Using cURL&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We’ll use a PUT request so the subscriber is created if new or updated if they already exist.&lt;/p&gt;

&lt;blockquote&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ch = curl_init($url);
curl_setopt($ch, CURLOPT_USERPWD, 'user:' . $apiKey);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonData);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode == 200) {
    echo "Subscriber added or updated successfully!";
} else {
    echo "Error: " . $response;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Step 7: One Complete PHP Example&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Below is the full working PHP example that adds or updates a subscriber in Mailchimp, applies tags, and saves merge fields like first and last name.&lt;/p&gt;

&lt;blockquote&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php
// Mailchimp configuration
$apiKey = 'YOUR_API_KEY';          // Mailchimp API key
$listId = 'YOUR_LIST_ID';          // Audience ID
$server = 'us18';                  // Server prefix (e.g. us18)

// Subscriber data (example from form request)
$email = $request-&amp;gt;email;
$fname = $request-&amp;gt;fname;
$lname = $request-&amp;gt;lname;

// Tags
$tags = ['Tag 1'];

// Prepare data
$data = [
    'email_address' =&amp;gt; $email,
    'merge_fields' =&amp;gt; [
        'FNAME' =&amp;gt; $fname,
        'LNAME' =&amp;gt; $lname,
    ],
    'status_if_new' =&amp;gt; 'subscribed',
    'tags' =&amp;gt; $tags
];

// Convert data to JSON
$jsonData = json_encode($data);

// Mailchimp API endpoint
$url = 'https://' . $server . '.api.mailchimp.com/3.0/lists/' 
     . $listId . '/members/' . md5(strtolower($email));

// Initialize cURL
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_USERPWD, 'user:' . $apiKey);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonData);

// Execute request
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

// Handle response
if ($httpCode === 200) {
    echo "Subscriber added or updated successfully!";
} else {
    echo "Mailchimp API Error: " . $response;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Notes&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PUT ensures the subscriber is created if they don’t exist, or updated if they do.&lt;/li&gt;
&lt;li&gt;Tags help segment users for campaigns and automations.&lt;/li&gt;
&lt;li&gt;merge_fields allow you to store additional user data like first and last names.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With just a few lines of PHP, you can integrate Mailchimp subscriptions into your web app, manage audience tags, and keep subscriber data organized automatically. This approach works perfectly for partners, customers, and any segmented email campaigns.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>api</category>
      <category>development</category>
    </item>
  </channel>
</rss>
