Blog / Serve a Laravel project on Web, Desktop and Mobile with Tauri

Image used for article Serve a Laravel project on Web, Desktop and Mobile with Tauri

Serve a Laravel project on Web, Desktop and Mobile with Tauri




TL;DR: How to display a Laravel project simultaneously on the web, your operating system, and your mobile device using Tauri.




A sample Laravel project can be found on this Github Repository. Find out more on CapsulesX or Bluesky.




Introduction : NativePHP, a framework that enables building native applications with PHP, needs no further introduction. Developed by Marcel Pociot and Simon Hamp, this framework currently relies on Electron. However, a new approach based on Tauri is being implemented.




This article explains how to locally serve the same project on three different platforms, using hot module replacement to view modifications in real-time. The web is served using the command php artisan serve:web. The desktop is served using the command php artisan serve:desktop. The mobile is served using the command php artisan serve:mobile.




In the interest of the reader and to keep this article concise, certain information will be directly integrated into the commands to avoid unnecessary back-and-forth.




The base command, php artisan serve:web, can be summarized as follows:



app/Console/Commands/ServeWeb.php


<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Process;

use function Laravel\Prompts\intro;
use function Laravel\Prompts\note;


class ServeWeb extends Command
{
    protected $signature = 'serve:web';

    public function handle()
    {
        intro( 'Running Web Environment' );

        $this->initViteServer();
        $this->initPHPServer();
    }

    private function initViteServer() : void
    {
        note( "Starting Vite Development Server" );

        Process::start( "npm run dev:vite:web" );
    }

    private function initPHPServer() : void
    {
        note( "Starting PHP Server" );

        Process::forever()->tty()->run( "php artisan serve --port=50000" );
    }
}




Several notable features include:




1. A Vite development server is started with the command npm run dev:vite:web instead of the standard npm run dev command. The php artisan serve command is used with port 50000.


2. In the context of this article, it is essential to separate the different Vite servers. These servers will be launched simultaneously and must independently notify their respective platform when they detect a change. Unlike the PHP server, which keeps port 50000. The different ports are chosen arbitrarily.




Next, it is necessary to configure the package.json file to include the command npm run dev:vite:web.



package.json


{
    "private": true,
    "type": "module",
    "scripts": {
        "build": "vite build",
        "dev": "vite",
        "dev:vite:web" : "vite --config vite.web.config.js"
    },
    "devDependencies": {
        "autoprefixer": "^10.4.20",
        "axios": "^1.7.4",
        "concurrently": "^9.0.1",
        "laravel-vite-plugin": "^1.0",
        "postcss": "^8.4.47",
        "tailwindcss": "^3.4.13",
        "vite": "^6.0"
    }
}




And create the Vite configuration file specific to the web:serve command.



vite.web.config.js


import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';


export default defineConfig( {
    plugins : [
        laravel( {
            input : [ 'resources/css/app.css', 'resources/js/app.js' ],
            refresh : true,
        } ),
    ],
    server : {
        port : 50001
    }
} );




Ports 50000 and 50001 are respectively allocated to the PHP server and the Vite server for the web:serve command.



Here is the result obtained when running the php artisan web:serve command:



> php artisan web:serve

Running Web Environment

Starting Vite Development Server

Starting PHP Server

INFO  Server running on [<http://127.0.0.1:50000>].

Press Ctrl+C to stop the server

2024-07-16 01:52:18 / ............................................... ~ 0.16ms
2024-07-16 01:52:18 /favicon.ico .................................. ~ 506.93ms







For now, nothing new.




The next step: create a native application with Tauri. To do this, it is necessary to set up the Tauri infrastructure. But what is Tauri?



Tauri is a framework for building tiny, fast binaries for all major desktop and mobile platforms. It is designed to provide a lighter alternative to Electron. Since version 2.0, Tauri offers mobile solutions.




Installing Tauri is simple:



npm install --save-dev @tauri-apps/cli@2.2.2



Next, you need to add the binary to the scripts in the package.json file.



"scripts" : {
    ...
    "tauri" : "tauri"
    ...



Then, initialize Tauri using the command npm run tauri init:



> npm run tauri init

tauri
tauri init

✔ What is your app name? · Tauri App
✔ What should the window title be? · Tauri
✔ Where are your web assets (HTML/CSS/JS) located, relative to the "<current dir>/src-tauri/tauri.conf.json" file that will be created? · ../public
✔ What is the url of your dev server? · <http://127.0.0.1:50000>
✔ What is your frontend dev command?
✔ What is your frontend build command?
  • The answers provided are tailored to the criteria of this article.




The src-tauri folder then appears. It is composed of several folders and files:



> src-tauri 
   > capabilities
   > icons
   > src
     .gitignore
     build.rs
     Cargo.tml
     tauri.conf.json




Once Tauri is installed, the generation of a native application can begin. To do this, a new command must be created: ServeDesktop.



app/Console/Commands/ServeDesktop.php


<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Process;
use Illuminate\Support\Facades\File;

use function Laravel\Prompts\intro;
use function Laravel\Prompts\note;


class ServeDesktop extends Command
{
    protected $signature = 'serve:desktop';

    public function handle()
    {
        intro( 'Running Desktop Environment' );

        $this->initViteServer();
        $this->initPHPServer();
        $this->initTauriServer();
    }

    private function initTauriServer() : void
    {
        note( 'Starting Desktop App' );

        if( ! File::exists( base_path( 'src-tauri/target' ) ) )
        {
            Process::path( 'src-tauri' )->forever()->tty()->run( "cargo build" );
        }

        Process::forever()->tty()->run( "npm run dev:tauri:desktop -- --port=50003" );
    }

    private function initViteServer() : void
    {
        note( "Starting Vite Development Server" );

        Process::start( "npm run dev:vite:desktop" );
    }

    private function initPHPServer() : void
    {
        note( "Starting PHP Server" );

        Process::tty()->start( "php artisan serve --port=50000" );
    }
}




1. Three processes will be launched simultaneously.

2. The ports used for the desktop command are 50000, 50002, and 50003.

3. The initTauriServer method checks if the src-tauri/target folder exists. If it doesn't, it indicates that the dependencies related to Tauri have not been installed.

4. Process::tty() allows displaying information in the terminal.

5. Process::forever()->run() and Process::start() execute a command without a timeout. start runs in the background, while run remains the main process.




The PHP server port remains 50000. There is no need to launch multiple PHP servers: If the server is already running, an error message will appear: Failed to listen on 127.0.0.1:50000 (reason: Address already in use). Don't worry, this simply means the server is still active.




Now it's time for the Vite configuration for the desktop version.



vite.desktop.config.js


import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';


export default defineConfig( {
    plugins : [
        laravel( {
            input : [ 'resources/css/app.css', 'resources/js/app.js' ],
            refresh : true,
        } ),
    ],
    clearScreen : false,
    server : {
        port : 50002
    }
} );




1. The ports used for the ServeDesktop command are: 50000 for PHP, 50002 for Vite, and 50003 for Tauri.

2. clearScreen: false prevents Vite from clearing the screen before displaying new data.




The commands npm run dev:vite:desktop and npm run dev:tauri:desktop appear in the package.json file. The dev:tauri:desktop command simply represents the tauri dev command.



package.json


...
"scripts": {
    "dev:vite:web": "vite --config vite.web.config.js",
    "dev:vite:desktop": "vite --config vite.desktop.config.js",
    "dev:tauri:desktop": "tauri dev"
},
...




The result:



> php artisan desktop:serve

Running Desktop Environment

Starting Desktop App

  Updating crates.io index
  Locking 471 packages to latest compatible versions
  ...
  Compiling tauri-macros v2.0.4
  Finished `dev` profile [unoptimized + debuginfo] target(s) in 36.49s
  
Starting Vite Development Server

Starting PHP Server

> dev:tauri:desktop
> tauri dev --port=50003

INFO  Server running on [<http://127.0.0.1:50000>].
...

Info Watching /article/src-tauri for changes...
Compiling libc v0.2.169
Compiling core-foundation-sys v0.8.7
Compiling objc-sys v0.3.5
...
Compiling tauri-macros v2.0.4
Compiling app v0.1.0 (/article/src-tauri)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 23.44s
Running `target/debug/app`

2024-07-16 02:12:51 / ............................................... ~ 502.58ms







The final step of this article concerns the mobile application. A new command is introduced: ServeMobile.



app/Console/Commands/ServeMobile.php


<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Process;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\File;

use function Laravel\Prompts\intro;
use function Laravel\Prompts\note;
use function Laravel\Prompts\warning;


class ServeMobile extends Command
{
    protected $signature = 'serve:mobile {--android} {--ios}';

    public function handle()
    {
        if( ! $this->option( 'ios' ) && ! $this->option( 'android' ) ) return warning( "A device option is needed : 'mobile:serve --android' or 'mobile:serve --ios'" );

        intro( 'Running Mobile Environment' );

        $this->initViteServer();
        $this->initPHPServer();
        $this->initTauriServer();
    }

    private function initTauriServer() : void
    {
        $device = $this->option( 'ios' ) ? 'ios' : 'android';

        note( Str::headline( "Starting Mobile {$device} App" ) );

        if( ! File::exists( base_path( 'src-tauri/target' ) ) )
        {
            Process::path( 'src-tauri' )->forever()->tty()->run( "cargo build" );
        }

        if( ! File::exists( base_path( "src-tauri/gen/{$device}" ) ) )
        {
            Process::forever()->tty()->run( "npm run tauri {$device} init" );
        }

        Process::forever()->tty()->run( "npm run dev:tauri:mobile:{$device} -- --port=50005" );
    }

    private function initViteServer() : void
    {
        note( "Starting Vite Development Server" );

        Process::start( "npm run dev:vite:mobile" );
    }

    private function initPHPServer() : void
    {
        note( "Starting PHP Server" );

        Process::tty()->start( "php artisan serve --port=50000" );
    }
}




1. A parameter is now required: the choice between `--android` or `--ios`

2. A warning is displayed if the parameter is not specified.

3. As with the management of missing Tauri dependencies, the same applies to the folders specific to the mobile applications ios and android, via the command npm run tauri {$device} init.

4. Ports 50000, 50004, and 50005 are used respectively for the PHP server, the Vite server, and the Tauri server.




A new Vite configuration file must be created at the root of the project.



vite.mobile.config.js


import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';


export default defineConfig( {
    plugins : [
        laravel( {
            input : [ 'resources/css/app.css', 'resources/js/app.js' ],
            refresh : true,
        } ),
    ],
    clearScreen : false,
    server : {
        host : '0.0.0.0',
        port : 50004,
        strictPort : true,
        hmr : {
            protocol : 'ws',
            host : "192.168.0.8",
            port : 50005,
        },
    }
} );




1. It includes ports 50004 and 50005.

2. The host must be specified to allow Vite to route the essential files for the proper functioning of the web page to the Android emulator in this specific case. A useful command to identify this host is: ipconfig getifaddr en0.




The various scripts are then added to the package.json file.



package.json


...
"scripts": {
    "dev:vite:web": "vite --config vite.web.config.js",
    "dev:vite:desktop": "vite --config vite.desktop.config.js",
    "dev:vite:mobile" : "vite --config vite.mobile.config.js",
    "tauri": "tauri",
    "dev:tauri:desktop": "tauri dev",
    "dev:tauri:mobile:android" : "tauri android dev",
    "dev:tauri:mobile:ios" : "tauri ios dev"
},
...




And here is the result when the command is executed:



> php artisan mobile:serve --android

Running Mobile Environment

Starting Mobile Android App

> tauri
> tauri android init

Generating Android Studio project...
    Info "/article/src-tauri" relative to "/article/src-tauri/gen/android/app" is "../../../"
    victory: Project generated successfully!
    Make cool apps! 🌻 🐕 🎉
 
 
Starting Vite Development Server

Starting PHP Server

> dev:tauri:mobile:android
> tauri android dev --port=50005

   INFO  Server running on [<http://127.0.0.1>.:50000].

   Press Ctrl+C to stop the server

Detected Android emulators:
  [0] INFO    | Storing crashdata in: /tmp/android/emu-crash-34.1.20.db, detection is enabled for process: 36048
  [1] Medium_Phone_API_33
  Enter an index for a emulator above.
Emulator: 1
...

...
Compiling rustls-webpki v0.102.3
Compiling tokio-rustls v0.26.0s]
Compiling hyper-rustls v0.27.1s]
Compiling reqwest v0.12.12 [29s]
  Finished `dev` profile [unoptimized + debuginfo] target(s) in 14.54s
  Info symlinking lib "/article/src-tauri/target/aarch64-linux-android/debug/libapp_lib.so" in jniLibs dir "/Users/mho/Work/Projects/Development/Web/Personal/serve/src-tauri/gen/android/app/src/main/jniLibs/arm64-v8a"
  Info "/article/src-tauri/target/aarch64-linux-android/debug/libapp_lib.so" requires shared lib "liblog.so"
  Info "/article/src-tauri/target/aarch64-linux-android/debug/libapp_lib.so" requires shared lib "libandroid.so"
  Info "/article/src-tauri/target/aarch64-linux-android/debug/libapp_lib.so" requires shared lib "libdl.so"
  Info "/article/src-tauri/target/aarch64-linux-android/debug/libapp_lib.so" requires shared lib "libm.so"
  Info "/article/src-tauri/target/aarch64-linux-android/debug/libapp_lib.so" requires shared lib "libc.so"
Performing Streamed Install
Success
Starting: Intent { cmp=com.tauri.dev/.MainActivity }
  
  
2024-07-16 02:34:48 / ................................................ ~ 0.12ms
2024-07-16 02:34:49 /favicon.ico ..................................... ~ 0.06ms




An emulator choice will be offered, here Emulator: 1, which corresponds to the virtual device Medium_Phone_API_33 created from Android Studio. This link explains how to create an Android virtual device.







And when the three commands are executed simultaneously :








Glad this helped.

v1.5.3

X IconBluesky IconGithub Icon