Laravel PHP Backend Development Guide: Routing, Eloquent and Artisan

Laravel is one of the most popular PHP web frameworks, positioned so developers can focus on their business logic instead of boilerplate. It ships an exceptionally smooth ORM (Eloquent), a command-friendly CLI (Artisan), and a complete middleware and authentication system. Based on the official Laravel documentation, this guide follows four threads: routing, Eloquent, middleware, and Artisan.

Routing: See Every Entry Point at a Glance

Laravel routes live in the routes/ directory: routes/web.php targets the web interface, and routes/api.php targets stateless APIs. Running php artisan install:api installs Sanctum, creates api.php, and automatically applies the /api prefix:

use Illuminate\Support\Facades\Route;

Route::get('/users', function () {
    return 'Hello World';
});

Route::get('/users/{id}', function (string $id) {
    return 'User ' . $id;
})->whereNumber('id');

Route parameters are wrapped in {}, and where helper methods add regex constraints. When several routes must share middleware, prefixes, or subdomains, use route groups. php artisan route:list prints every route whenever you need an overview.

The Eloquent ORM: Models as the Data Entry Point

Every database table maps to an Eloquent model. Running php artisan make:model Flight --migration generates both the model and a migration. Models follow conventions (snake-case plural table names) and offer convenient methods like findOrFail, firstOrCreate, and aggregates:

use App\Models\Flight;

$flight = Flight::findOrFail(1);
$count = Flight::where('active', 1)->count();

Two practices matter most. First, mass assignment protection: before using create(), declare $fillable on the model, otherwise a malicious is_admin field could escalate privileges. Second, soft deletes: add the SoftDeletes trait to mark rows via a deleted_at column instead of physically removing them, and restore with restore() if you change your mind.

Eloquent Relationships: How Models Connect

Eloquent's most comfortable feature is relationship definitions. A user has many orders, and each order belongs to a user — declare relationship methods on the models:

class User extends Authenticatable
{
    public function orders(): HasMany
    {
        return $this->hasMany(Order::class);
    }
}

class Order extends Model
{
    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }
}

Now $user->orders returns that user's order collection directly, and eager loading with with('orders') avoids the classic N+1 query problem. Use plural method names for collections (hasMany) and singular for single relations (belongsTo) — the code reads almost like natural language.

Controllers and request validation

Route callbacks can point at controller methods for clearer separation of concerns:

Route::get('/users', [UserController::class, 'index']);

Inside a controller, Form Requests handle validation cleanly: generate one with php artisan make:request StoreUserRequest, type-hint it in the controller method, and Laravel validates before the method runs, returning 422 with the errors on failure. When several writes must succeed together, wrap them in DB::transaction(fn () => ...) so everything commits or everything rolls back; and with the queue (dispatch), slow work like sending email or calling third-party APIs moves off the request thread instead of blocking the user.

Middleware: Gates Before the Request

Middleware filters requests entering your app — authentication, rate limiting, CORS. Laravel ships web and api middleware groups by default, and the api group includes throttling:

Route::middleware(['auth:sanctum'])->group(function () {
    Route::get('/user', function (Request $request) {
        return $request->user();
    });
});

Middleware runs in the order declared in the kernel array — worth documenting, because whether auth sits before or after rate limiting changes the status code and log shape when a request is rejected. Laravel also provides Gates and Policies for fine-grained authorization (for example, "only the order's creator may edit it"), which is cleaner than stacking if checks in a controller and far easier to test.

Queues: Move Slow Work Out of the Request

Sending email, calling third-party APIs, and generating reports should not block the user's request. Laravel's queue system runs these tasks in the background:

dispatch(function () {
    // background task, e.g. sending a notification or writing a log
})->afterResponse();

For anything serious, generate a dedicated Job class (php artisan make:job SendOrderConfirmation), put the logic in handle(), and push it with dispatch(). Horizon manages the queue processes, retries, and monitoring: failed jobs are retried automatically per your configuration, and the reasons land in the failed_jobs table for later inspection.

Artisan: Command-Line Productivity

Artisan is Laravel's CLI and a major productivity boost:

  • php artisan make:model / make:controller: generate code skeletons;
  • php artisan route:list / route:cache: inspect and cache routes;
  • php artisan migrate: run database migrations;
  • php artisan model:prune: clean up stale records on a schedule.

Combined with scheduling declarations such as Schedule::command('model:prune')->daily(), much routine maintenance is handled by the framework.

Deployment and maintenance

In production, run php artisan config:cache, route:cache, and view:cache to cache configuration, routes, and views for noticeably faster request handling; teams chasing higher throughput use Octane to keep Laravel resident in memory. During deployment, sync the schema with php artisan migrate --force, manage queues through Horizon, and watch requests and exceptions in real time with Pulse or Telescope. Add php artisan test (Pest or PHPUnit) to CI/CD so changing a route or model cannot silently break production behavior. Before shipping, php artisan about gives a quick view of environment and versions, and pairing with Sentry surfaces exceptions the moment they happen.

Troubleshooting quick reference

  • Changed a route but it does not take effect? Check whether you ran route:cache; if route caching is configured, your deploy pipeline must re-cache or clear it, otherwise old routes stay in memory.
  • create() throws a MassAssignmentException? That is Laravel's mass-assignment protection doing its job — check that $fillable is declared instead of disabling the guard.
  • List pages are slow? Inspect the actual SQL with Telescope or DB::enableQueryLog() to confirm indexes are hit and no N+1 queries sneak in from missing with() eager loading.
  • Changed .env but nothing happens? If config is cached, run php artisan config:clear and cache again.

Reference: Laravel docs (Eloquent relationships) https://laravel.com/docs/12.x/eloquent-relationships
Reference: Laravel docs (queues) https://laravel.com/docs/12.x/queues

16IDC perspective

Laravel suits content sites, e-commerce, and admin backends, with a mature official ecosystem (Forge, Nova, Sanctum, and more). For simple form handling, see our PHP Contact Form Handler; for legacy mysql_connect compatibility, see Making PHP 7 Compatible with mysql_connect. For API auth, check API Security with OAuth 2.0 and JWT. More in the Backend Integration category.

Source: https://laravel.com/docs/12.x/routing