skip to content

In a Laravel 13 app, what does `composer run dev` start, and why use it instead of running `php artisan serve` alone?

level: juniorimportance: must knowfreq 48%

answer

  1. a Composer script calling one Artisan command
  2. php artisan dev, added in 13.16
  3. server, queue, logs, vite
  4. queue:listen --tries=1 --timeout=0
  5. DevCommands::except() in a service provider

basics

~20 s

composer run dev calls php artisan dev, which by default runs the PHP development server, a queue listener, Pail log tailing and the Vite dev server together. artisan serve alone only answers HTTP, so queued jobs and Vite assets are left without a process.

solid answer

~40 s

The skeleton's `dev` script disables Composer's process timeout and runs `php artisan dev` (added in Laravel 13.16). That command starts four named processes: `server` (`php artisan serve`), `queue` (`php artisan queue:listen --tries=1 --timeout=0`), `logs` (`php artisan pail --timeout=0`, only when Pail is installed and `pcntl` is available) and `vite` (`npm run dev` through your package manager, when `package.json` exists). On macOS and Linux it runs them in the `@laravel/multiplex` tabbed interface and restarts a crashed process; on Windows it falls back to `concurrently`. `php artisan serve` alone gives you HTTP only: with the skeleton's `QUEUE_CONNECTION=database`, a queued reminder email just sits in the `jobs` table, and pages using `@vite` need either the dev server or a build. You can trim or extend the list with `DevCommands` in a service provider.

code

php · 18 lines
php
<?php

namespace App\Providers;

use Illuminate\Foundation\DevCommands;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        // Herd already serves vet-clinic.test, so skip the PHP dev server
        DevCommands::except('server');

        // Also run the scheduler locally for appointment reminders
        DevCommands::artisan('schedule:work', 'scheduler');
    }
}

go deeper

for a junior

Know that composer run dev starts the web server, a queue listener, log tailing and Vite in one go, and that artisan serve alone only serves pages.

for a middle

Explain the chain from the Composer script to php artisan dev, the four default processes and the conditions under which logs and vite are registered.

for a senior

Shape the team's local loop with DevCommands (drop the server under Herd, add the scheduler) and keep dev-only settings such as one try and no timeout out of production workers.

for a principal

Decide what the standard local loop contains across services, so every developer runs the same background processes the app depends on.

## The script and the command A freshly created Laravel 13 app has a `dev` entry in the `scripts` section of `composer.json`: - `Composer\Config::disableProcessTimeout` lifts Composer's default 300-second limit on scripts, so a long-running dev session is not killed. - `@php artisan dev` then runs the **`dev` Artisan command**, added in Laravel 13.16. So `composer run dev` is simply a convenient name; the work is done by `php artisan dev`, and you can run that directly. Earlier skeletons wired the same processes together inside the Composer script with the `concurrently` npm package. ## The four default processes `php artisan dev` asks the `Illuminate\Foundation\DevCommands` registry for its commands. The framework registers these defaults: | Name | Command | Registered when | |---|---|---| | `server` | `php artisan serve` | always | | `queue` | `php artisan queue:listen --tries=1 --timeout=0` | always | | `logs` | `php artisan pail --timeout=0` | Pail's provider is loaded and `pcntl_fork` exists | | `vite` | `npm run dev` (or pnpm, Yarn, Bun) | a `package.json` exists | On macOS and Linux the processes run under the **`@laravel/multiplex`** package, which gives each one a labelled, colour-coded tab, restarts a process that crashes (unless `--no-restart`), and prints the collected output when you quit; the docs say it needs Node 22.13 or later. On Windows the command falls back to `concurrently` without tabs. Flags such as `--stream`, `--tabs` and `--inline` change the display. ## Why `php artisan serve` alone is not enough `php artisan serve` starts PHP's built-in web server and nothing else. In a veterinary-clinic booking app that is enough to render pages, but: 1. **Queued work never runs.** The skeleton's default queue connection is the database, so an appointment-reminder job dispatched from a controller waits for a worker process. The `queue` process is that worker for local development. 2. **Front-end assets are missing.** Blade pages that use `@vite` need the Vite dev server running (or a production build in `public/build`). 3. **Logs stay in a file.** The `logs` process streams entries to the terminal as they are written. Starting all of them by hand means four terminals; `composer run dev` is one command that the whole team runs the same way. ## How the multiplexed session behaves A few details make the combined session easier to live with than four terminals: - Each process gets a **name** (`server`, `queue`, `logs`, `vite`) and a colour, so a stack trace in the queue tab is not confused with a Vite rebuild message. - If one process crashes, for example the queue listener hitting a fatal error, it is **restarted automatically**; pass `--no-restart` when you want it to stay down so you can read the error. - `--timestamps` prefixes every line with the time, and `--inline` prints plain output, which is also the default when the terminal is not a TTY, such as inside an editor's task runner. - Quitting ends the session and writes the collected output back to your terminal, so nothing printed during the session is lost. For the clinic app, a developer who books a test appointment sees the request in `server`, the reminder job in `queue`, the log line in `logs` and any front-end rebuild in `vite`, all in one window. ## Customizing the process list Register changes in the `boot()` method of `AppServiceProvider`: - `DevCommands::register('command', 'name')` adds any shell command; `DevCommands::artisan()` prefixes `php artisan` for you. - `DevCommands::except('server')` drops a default, useful when Herd or Valet already serves the site. - `DevCommands::only(...)` keeps only the named processes, and `DevCommands::order([...])` sets their order. - `php artisan dev:list` (13.17) shows what will run and where each command was registered. ## Common misreadings - It is not a production process manager; every process it starts is a development tool. - Its queue process runs each job with one try and no timeout, which suits local debugging, not a production worker. - `composer run dev` does not build assets for deployment; that is `npm run build`.

  • How do you check which processes php artisan dev will start, and where each one was registered?
    Run `php artisan dev:list`, added in Laravel 13.17. It lists the registered dev processes after `only()`, `except()` and ordering are applied, and the registry records the source file and line of each registration, so a process added by a package's service provider can be traced. `DevCommands::withoutVendorCommands()` drops processes registered from `vendor/`.
  • Which processes can be missing from php artisan dev, and why?
    `logs` is registered only when Pail's service provider is loaded and the `pcntl_fork` function exists, so Windows, which has no `pcntl`, gets no log tab. `vite` is registered only when `package.json` exists. `server` and `queue` are always registered unless you remove them with `DevCommands::except()` or `withoutDefaultCommands()`.

saying these in an interview costs you the question

  • composer run dev builds production assets for deployment
  • php artisan serve also runs the queue listener and Vite
  • The dev queue process retries each failed job three times
  • You change the dev processes by editing vendor files
  • composer run dev and php artisan dev start different processes