Mastering Laravel API Development: From Polymorphic Mapping to Sanctum Authorization
Overview: The Anatomy of a Modern Office-Sharing API
Building a robust backend requires more than just making the code work; it requires a commitment to long-term maintainability and security. In this session, we focus on the evolution of
We address critical architectural decisions: shifting from generic polymorphic types to aliased maps, optimizing database queries for geographical distance, and implementing a strict validation layer for office creation. By the end of this walkthrough, the application will handle authenticated requests with
Prerequisites: Essential Toolkit
To follow this tutorial, you should be comfortable with the following technologies and concepts:
- PHP 8.x: Familiarity with modern PHP syntax, including anonymous functions and array destructuring.
- Laravel Framework: Understanding of Routing, Controllers, Eloquent Relationships, and Migrations.
- RESTful Principles: Knowledge of HTTP methods (POST, GET) and status codes (201 Created, 403 Forbidden).
- Testing Fundamentals: Basic experience with PHPUnitor Laravel's testing suite for asserting database states and JSON structures.
Key Libraries & Tools
- Laravel Sanctum: A lightweight authentication system for SPAs and mobile APIs. We use it here to manage token-based authorization and "abilities."
- Eloquent ORM: The database mapper that allows us to interact with our tables using expressive PHP syntax.
- API Resources: A transformation layer that sits between your Eloquent models and the JSON responses returned to your users.
- Artisan Test: A streamlined command-line tool (driven byNuno Maduro) for running test suites with beautiful output.
Refined Relationships with Custom Polymorphic Mapping
By default, App\Models\Office) in the morph_type column of your database. This is a mess for long-term maintenance. If you ever rename your model or move it to a different namespace, your database links break.
We fix this in the AppServiceProvider using Relation::enforceMorphMap. By aliasing App\Models\Office to simply office, we keep the database clean and decouple the data from the physical code structure.
// AppServiceProvider.php
public function boot()
{
Relation::enforceMorphMap([
'office' => \App\Models\Office::class,
'user' => \App\Models\User::class,
]);
}
Enforcing the map is a best practice. If you forget to add a new model to this list, Laravel will throw an exception during development, preventing silent failures in production. This ensures your polymorphic relationships (like images or tags attached to an office) remain consistent and readable.
Advanced API Resource Configuration
Returning a model directly from a controller is risky. It often leaks sensitive columns like email_verified_at or internal timestamps. We utilize
In the OfficeResource, we want to hide internal IDs and timestamps while ensuring that nested relationships—like the office owner or tags—use their own refined resources. This creates a recursive cleaning process for our JSON output.
// OfficeResource.php
public function toArray($request)
{
return array_merge(array_diff_key($this->resource->toArray(), array_flip([
'user_id', 'created_at', 'updated_at', 'deleted_at'
])), [
'user' => UserResource::make($this->user),
'images' => ImageResource::collection($this->images),
'tags' => TagResource::collection($this->tags),
]);
}
Using array_diff_key allows us to exclude specific internal attributes while maintaining the rest of the dynamic model data. This ensures that the user only sees what they need to see, reducing payload size and improving security.
Securing the Create Office Endpoint
Creating an office is a high-privilege action. We protect the route using a combination of verified middleware. This ensures the user is who they say they are and that they have confirmed their email address.
Inside the OfficeController@create, we perform strict validation. One key decision here is the use of the validator helper instead of
// OfficeController.php
public function create(Request $request)
{
$attributes = validator($request->all(), [
'title' => ['required', 'string'],
'description' => ['required', 'string'],
'lat' => ['required', 'numeric'],
'lng' => ['required', 'numeric'],
'address_line1' => ['required', 'string'],
'price_per_day' => ['required', 'integer', 'min:100'],
'tags' => ['array'],
'tags.*' => ['integer', 'exists:tags,id'],
])->validate();
// Authorization check for Sanctum tokens
if ($request->user()->tokenCan('office:create')) {
// proceed with creation
}
$office = $request->user()->offices()->create(Arr::except($attributes, ['tags']));
$office->tags()->sync($attributes['tags'] ?? []);
return OfficeResource::make($office);
}
We use $request->user()->offices()->create() to automatically associate the new office with the authenticated user. This pattern is safer than manually passing a user_id, as it relies on the underlying Eloquent relationship to handle the foreign key assignment.
Syntax Notes: Practical Patterns
- Order By Raw: When sorting by geographical distance, we don't always want to expose the raw distance calculation in the JSON response. By using
orderByRaw, we can sort the results inSQLwithout including the computed distance column in the final object array. - Token Abilities: Sanctum's
tokenCanmethod is essential for granular API control. It allows us to distinguish between a user logged in through a full-access session and an API token that might only have permission to "read" but not "create." - Syncing Relationships: The
sync()method on a Many-to-Many relationship (like tags) is a lifesaver. It automatically handles adding new tags and removing old ones in a single operation, keeping the pivot table perfectly aligned with the request data.
Tips & Gotchas: Lessons from the Field
- Trust the Framework: Don't waste time writing tests that verify if
requiredvalidation works or if theauthmiddleware blocks guests.Laravelis already heavily tested. Focus your tests on your custom business logic, such as whether an office is correctly assigned to the user who created it. - Sanctum Transient Tokens: A common point of confusion is why
tokenCanreturnstrueeven when no token is present. This happens becauseLaravel Sanctumtreats first-party session-authenticated requests as having all abilities. If you need to test specific scope failures, you must explicitly generate a limited token in your test. - Validation Inconsistency: Be careful when asserting JSON paths in tests. Some Laravel test methods expect the path first, others expect the value first. Always check the method signature to avoid "false pass" scenarios where your test isn't actually asserting anything.
- Polymorphic Errors: If you encounter a "No morph map defined" error, it usually means you've strictly enforced the map but missed an entry. Always add your
UserandTokenmodels to the map if you are using packages like Sanctum that rely on polymorphic relations.
