Back to blog

Laravel

Building CRUD APIs With Laravel Sanctum Authentication in 2021

End-to-end tutorial creating a token-authenticated CRUD API with Laravel Sanctum, resource transformers, and form request validation.

  • Laravel
  • Sanctum
  • APIs
  • Authentication

SEO Metadata

SEO Title Options

  1. Building CRUD APIs With Laravel Sanctum Authentication in
  2. Building CRUD APIs With Laravel Sanctum: Practical 2026
  3. Laravel Playbook: Building CRUD APIs With Laravel Sanctum

Meta Description Options

  1. Learn Building CRUD APIs With Laravel Sanctum Authentication in 2021 with a practical Laravel framework, expert mistakes, implementation steps, examples, FAQ.
  2. End-to-end tutorial creating a token-authenticated CRUD API with Laravel Sanctum, resource transformers, and form request validation.

URL Slug

building-crud-apis-laravel-sanctum-authentication-2021

Focus Keyword

Building CRUD APIs With Laravel Sanctum Authentication in 2021

Additional LSI Keywords

  • Laravel
  • Sanctum
  • APIs
  • Authentication
  • Building CRUD APIs With Laravel Sanctum Authentication in 2021
  • production checklist
  • implementation guide
  • best practices
  • architecture decisions
  • testing strategy
  • performance impact
  • security review

Table of Contents

Article overview

Building CRUD APIs With Laravel Sanctum Authentication in 2021 is the kind of topic that looks simple until it reaches production. Teams usually discover the real cost late: unclear boundaries, weak defaults, hidden maintenance work, and decisions that seemed harmless when the codebase was small.

The problem gets worse when the article, tutorial, or implementation guide only explains the happy path. This guide closes that gap with a practical framework, a comparison table, common mistakes, and a deep technical section you can use while planning real work.

Keep reading for the non-obvious part: the safest implementation is rarely the most impressive-looking one. It is the one your team can debug, test, document, and evolve without turning every future change into archaeology.

Key Takeaways

  • Building CRUD APIs With Laravel Sanctum Authentication in 2021 should be evaluated as a production decision, not only as a syntax or tooling choice.
  • The best implementation keeps responsibilities visible, with clear ownership, tests, documentation, and rollback paths.
  • Search visibility improves when practical depth, structured answers, and expert examples live on the same page.

[IMAGE: A mobile-first technical article layout showing the main concept, decision table, implementation checklist, and FAQ blocks. Alt: Building CRUD APIs With Laravel Sanctum Authentication in 2021 expert guide for Laravel]

What Building CRUD APIs With Laravel Sanctum Authentication in 2021 means

Building CRUD APIs With Laravel Sanctum Authentication in 2021 means applying laravel knowledge to a concrete engineering decision, then turning that decision into reliable code, documentation, and operational behavior. In practice, it combines the topic's core concepts with trade-off analysis, implementation boundaries, testing strategy, and maintenance discipline.

This is the definition worth optimizing for featured snippets because it avoids hype. It tells the reader what the topic does and what a professional implementation must include.

Why it matters now

The technical web is more crowded than it was a few years ago. Thin tutorials can still get indexed, but they rarely earn trust from senior developers, buyers, AI answer systems, or teams that need production guidance.

For laravel topics, the strongest content now has three layers:

  • a clear answer for fast scanning
  • a practical framework for implementation
  • expert context that explains what breaks later

That same structure helps search engines understand the page. It also helps readers decide whether the advice fits their project.

Implementation framework

Use this framework before adopting the approach described in this article.

  1. Define the user problem and the production risk.
  2. Identify the smallest reliable implementation boundary.
  3. Keep configuration, secrets, and environment-specific behavior outside the article's core logic.
  4. Add tests for the behavior that would hurt if it regressed.
  5. Document the trade-off, not only the final code.
  6. Measure the result with logs, metrics, or user-facing outcomes.
  7. Revisit the decision after real usage exposes edge cases.

The sequence is deliberately conservative. It keeps the work grounded in outcomes instead of novelty.

[IMAGE: A seven-step implementation framework with discovery, boundary design, configuration, tests, documentation, measurement, and iteration. Alt: Building CRUD APIs With Laravel Sanctum Authentication in 2021 implementation framework]

Practical comparison

Decision areaStrong approachWeak approachWhy it matters
ScopeSolve one clear problemMix unrelated concernsFocus improves testing and search intent
ArchitecturePut logic in explicit classes or documented boundariesHide behavior in templates or incidental callbacksFuture changes stay easier to review
Data flowPass prepared data into the view or endpointQuery or compute in presentation codeReduces regressions and performance surprises
TestingCover the risky behavior directlyTest only the happy pathCatches production failures earlier
DocumentationExplain trade-offs and limitsRepeat generic definitionsBuilds E-E-A-T and reader trust
OperationsTrack logs, metrics, and rollback stepsShip without measurementMakes the decision reversible

This table is intentionally practical. It gives a reviewer something to check before the implementation becomes expensive to change.

Expert workflow

Expert tip: "Treat Building CRUD APIs With Laravel Sanctum Authentication in 2021 as a system boundary. If the next developer cannot find where the decision lives, how it is tested, and when it should be avoided, the implementation is not finished."

A useful workflow is simple:

  • Start with the smallest working example.
  • Add the constraints that exist in your real project.
  • Remove anything that only demonstrates cleverness.
  • Write down the failure modes.
  • Add links to related decisions so future readers can navigate the topic cluster.

That last point matters for both humans and search systems. A single article can answer a question; a cluster proves authority.

Common mistakes

Mistake 1: Copying a pattern without its context

A pattern that works in a small demo can fail in a real application. The missing context is usually data volume, team experience, deployment process, security requirements, or observability.

Before copying the pattern, ask what assumption made it safe in the original example.

Mistake 2: Putting business logic in the wrong layer

This is the fastest way to make future debugging expensive. In Laravel, PHP, and server-rendered websites, presentation should receive prepared data, not discover rules on its own.

Keep decision logic in models, actions, services, policies, requests, jobs, or documented helpers where it can be tested directly.

Mistake 3: Optimizing for novelty instead of maintainability

Newer tools and language features can be valuable. They can also hide simple behavior behind unfamiliar syntax.

Use the option that makes the next production incident easier to understand.

Mistake 4: Publishing without a measurement plan

If the article describes a performance, SEO, security, or architecture improvement, define how success will be checked. Logs, tests, crawl diagnostics, analytics, and user behavior are all stronger than assumptions.

[IMAGE: A common-mistakes board with context loss, wrong layer, novelty bias, and missing measurement highlighted. Alt: Building CRUD APIs With Laravel Sanctum Authentication in 2021 common mistakes]

Image placeholders

  • [IMAGE: A concept diagram for Building CRUD APIs With Laravel Sanctum Authentication in 2021 with input, decision boundary, implementation, tests, and production feedback. Alt: Building CRUD APIs With Laravel Sanctum Authentication in 2021 concept diagram]
  • [IMAGE: A mobile screenshot-style checklist for Building CRUD APIs With Laravel Sanctum Authentication in 2021. Alt: Building CRUD APIs With Laravel Sanctum Authentication in 2021 mobile checklist]
  • [IMAGE: A comparison table visualization for strong versus weak implementation choices. Alt: Building CRUD APIs With Laravel Sanctum Authentication in 2021 comparison table]

Video placeholder

[VIDEO: Insert a 5-8 minute YouTube walkthrough that demonstrates the main decision, the implementation boundary, the test strategy, and the production caveats for Building CRUD APIs With Laravel Sanctum Authentication in 2021.]

Internal linking opportunities

Original Technical Deep Dive

What we are building

This guide builds a small CRUD API for projects:

  • POST /api/login returns a Sanctum personal access token.
  • GET /api/user returns the authenticated user.
  • GET /api/projects lists the user's projects.
  • POST /api/projects creates a project.
  • GET /api/projects/{project} shows one project.
  • PUT /api/projects/{project} updates it.
  • DELETE /api/projects/{project} deletes it.
  • POST /api/logout revokes the current token.

The point is not to create another demo controller that accepts unvalidated arrays. The API will use:

  • Laravel Sanctum for bearer-token authentication.
  • Eloquent ownership through user_id.
  • Form request classes for validation.
  • API resources for stable JSON output.
  • Policies so users cannot read or mutate another user's records.

In 2021, this was a practical Laravel 8 setup for mobile apps, external clients, and simple token-authenticated APIs.

Choose the right Sanctum mode

Sanctum supports two different authentication styles:

  • API tokens for third-party clients, mobile apps, CLIs, and simple machine-to-API access.
  • Cookie-based SPA authentication for first-party JavaScript applications.

This article uses API tokens.

For a first-party Vue, React, or Next.js app on your own domain, do not blindly copy the token flow. Sanctum's SPA mode uses Laravel's session cookies and CSRF protection instead of manually storing bearer tokens in browser storage.

For mobile apps and external API consumers, bearer tokens are a good fit.

Install Sanctum

Install the package:

composer require laravel/sanctum

Publish Sanctum's config and migrations:

php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"

Run migrations:

php artisan migrate

Sanctum adds a personal_access_tokens table. Tokens are stored hashed in the database. The plain text token is only available when the token is created, so return it once and treat it like a password.

Add HasApiTokens to User

Update app/Models/User.php:

<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens;
    use Notifiable;
}

If your Laravel 8 app already has HasFactory, keep it:

use HasApiTokens, HasFactory, Notifiable;

Without HasApiTokens, the user model cannot call createToken(), access the tokens relationship, or revoke the current token cleanly.

Create the Project model

Generate the model and migration:

php artisan make:model Project -m

Create the table:

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

class CreateProjectsTable extends Migration
{
    public function up(): void
    {
        Schema::create('projects', function (Blueprint $table): void {
            $table->id();
            $table->foreignId('user_id')->constrained()->cascadeOnDelete();
            $table->string('name');
            $table->string('status')->default('draft');
            $table->date('deadline')->nullable();
            $table->text('description')->nullable();
            $table->timestamps();

            $table->index(['user_id', 'status']);
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('projects');
    }
}

Run it:

php artisan migrate

Define the model:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Project extends Model
{
    protected $fillable = [
        'name',
        'status',
        'deadline',
        'description',
    ];

    protected $casts = [
        'deadline' => 'date',
    ];

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

Do not put user_id in $fillable for this API. The authenticated user should own the created project. The client should not decide ownership.

Create login and logout endpoints

Generate a controller:

php artisan make:controller Api/AuthController

Implement token issuance:

<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;

class AuthController extends Controller
{
    public function login(Request $request): JsonResponse
    {
        $credentials = $request->validate([
            'email' => ['required', 'email'],
            'password' => ['required', 'string'],
            'device_name' => ['required', 'string', 'max:255'],
        ]);

        $user = User::where('email', $credentials['email'])->first();

        if (! $user || ! Hash::check($credentials['password'], $user->password)) {
            throw ValidationException::withMessages([
                'email' => ['The provided credentials are incorrect.'],
            ]);
        }

        $token = $user->createToken(
            $credentials['device_name'],
            ['projects:read', 'projects:write']
        );

        return response()->json([
            'token' => $token->plainTextToken,
            'token_type' => 'Bearer',
        ]);
    }

    public function logout(Request $request): JsonResponse
    {
        $request->user()->currentAccessToken()->delete();

        return response()->json([
            'message' => 'Token revoked.',
        ]);
    }
}

This endpoint returns the token once. The client sends it later as:

Authorization: Bearer 1|plain-text-token-value

[IMAGE: Supporting visual 1 for Building CRUD APIs With Laravel Sanctum Authentication in 2021, showing Building CRUD APIs With Laravel Sanctum Authentication in 2021 decisions, examples, and Laravel, Sanctum, APIs. Alt: Building CRUD APIs With Laravel Sanctum Authentication in 2021 building-crud-apis-laravel-sanctum-authentication-2021 visual 1]

[IMAGE: Supporting visual 1 for Building CRUD APIs With Laravel Sanctum Authentication in 2021, showing Building CRUD APIs With Laravel Sanctum Authentication in 2021 decisions, examples, and Laravel, Sanctum, APIs. Alt: Building CRUD APIs With Laravel Sanctum Authentication in 2021 building-crud-apis-laravel-sanctum-authentication-2021 visual 1]

Do not log this header. Do not store it in frontend local storage for a first-party browser app. For browser SPAs, use Sanctum's cookie mode.

Add routes

In routes/api.php:

use App\Http\Controllers\Api\AuthController;
use App\Http\Controllers\Api\ProjectController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::post('/login', [AuthController::class, 'login'])
    ->middleware('throttle:login');

Route::middleware('auth:sanctum')->group(function (): void {
    Route::get('/user', static fn (Request $request) => $request->user());
    Route::post('/logout', [AuthController::class, 'logout']);

    Route::apiResource('projects', ProjectController::class);
});

Route::apiResource() creates the API actions and skips HTML form routes such as create and edit.

The auth:sanctum middleware protects the routes. Requests without a valid bearer token receive an authentication error before they reach the controller.

Add form requests

Generate request classes:

php artisan make:request StoreProjectRequest
php artisan make:request UpdateProjectRequest

Store request:

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class StoreProjectRequest extends FormRequest
{
    public function authorize(): bool
    {
        $user = $this->user();

        return $user !== null && $user->tokenCan('projects:write');
    }

    public function rules(): array
    {
        return [
            'name' => ['required', 'string', 'max:120'],
            'status' => ['sometimes', 'in:draft,active,archived'],
            'deadline' => ['nullable', 'date'],
            'description' => ['nullable', 'string', 'max:5000'],
        ];
    }
}

Update request:

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class UpdateProjectRequest extends FormRequest
{
    public function authorize(): bool
    {
        $project = $this->route('project');
        $user = $this->user();

        return $project !== null
            && $user !== null
            && $user->can('update', $project)
            && $user->tokenCan('projects:write');
    }

    public function rules(): array
    {
        return [
            'name' => ['sometimes', 'required', 'string', 'max:120'],
            'status' => ['sometimes', 'in:draft,active,archived'],
            'deadline' => ['nullable', 'date'],
            'description' => ['nullable', 'string', 'max:5000'],
        ];
    }
}

When validation fails for an API request, Laravel returns a JSON validation response. That keeps the controller focused on application behavior.

Add the API resource

Generate the resource:

php artisan make:resource ProjectResource

Define the response shape:

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class ProjectResource extends JsonResource
{
    public function toArray($request): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'status' => $this->status,
            'deadline' => optional($this->deadline)->toDateString(),
            'description' => $this->description,
            'created_at' => $this->created_at->toISOString(),
            'updated_at' => $this->updated_at->toISOString(),
        ];
    }
}

API resources stop your database schema from leaking directly into your API contract. You can rename fields, format dates, hide internal columns, and add links later without rewriting every controller.

Add authorization policy

Generate a policy:

php artisan make:policy ProjectPolicy --model=Project

Implement ownership checks:

<?php

namespace App\Policies;

use App\Models\Project;
use App\Models\User;

class ProjectPolicy
{
    public function view(User $user, Project $project): bool
    {
        return $project->user_id === $user->id
            && $user->tokenCan('projects:read');
    }

    public function update(User $user, Project $project): bool
    {
        return $project->user_id === $user->id
            && $user->tokenCan('projects:write');
    }

    public function delete(User $user, Project $project): bool
    {
        return $project->user_id === $user->id
            && $user->tokenCan('projects:write');
    }
}

If policy auto-discovery is not available in your app structure, register it in AuthServiceProvider:

protected $policies = [
    Project::class => ProjectPolicy::class,
];

Authentication answers "who is calling?" Authorization answers "what can this caller do?" Sanctum tokens do not replace policies.

Build the CRUD controller

Generate the API controller:

php artisan make:controller Api/ProjectController --api

Implement it:

<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Http\Requests\StoreProjectRequest;
use App\Http\Requests\UpdateProjectRequest;
use App\Http\Resources\ProjectResource;
use App\Models\Project;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;

class ProjectController extends Controller
{
    public function index(): AnonymousResourceCollection
    {
        $projects = auth()->user()
            ->projects()
            ->latest()
            ->paginate(15);

        return ProjectResource::collection($projects);
    }

    public function store(StoreProjectRequest $request): ProjectResource
    {
        $project = $request->user()
            ->projects()
            ->create($request->validated());

        return new ProjectResource($project);
    }

    public function show(Project $project): ProjectResource
    {
        $this->authorize('view', $project);

        return new ProjectResource($project);
    }

    public function update(UpdateProjectRequest $request, Project $project): ProjectResource
    {
        $project->update($request->validated());

        return new ProjectResource($project->refresh());
    }

    public function destroy(Project $project): JsonResponse
    {
        $this->authorize('delete', $project);

        $project->delete();

        return response()->json(null, 204);
    }
}

Add the relationship to User:

use Illuminate\Database\Eloquent\Relations\HasMany;

public function projects(): HasMany
{
    return $this->hasMany(Project::class);
}

The controller never accepts user_id from the request. It scopes listing and creation through the authenticated user, then uses policies for single-record access.

Test with curl

Login:

curl -X POST https://example.test/api/login \
  -H "Accept: application/json" \
  -d "email=taylor@example.com" \
  -d "password=password" \
  -d "device_name=macbook"

Create a project:

curl -X POST https://example.test/api/projects \
  -H "Accept: application/json" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -d "name=Website rebuild" \
  -d "status=active"

List projects:

curl https://example.test/api/projects \
  -H "Accept: application/json" \
  -H "Authorization: Bearer YOUR_TOKEN"

Update a project:

curl -X PUT https://example.test/api/projects/1 \
  -H "Accept: application/json" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -d "status=archived"

Delete a project:

curl -X DELETE https://example.test/api/projects/1 \
  -H "Accept: application/json" \
  -H "Authorization: Bearer YOUR_TOKEN"

Revoke the current token:

curl -X POST https://example.test/api/logout \
  -H "Accept: application/json" \
  -H "Authorization: Bearer YOUR_TOKEN"

Always send Accept: application/json from API clients. It helps Laravel return JSON errors instead of browser-oriented responses.

Token abilities are not enough

This is tempting:

Route::apiResource('projects', ProjectController::class)
    ->middleware(['auth:sanctum', 'ability:projects:write']);

That is too broad. It would require write access even for read actions unless you split routes by action.

Use abilities for coarse capabilities:

  • projects:read
  • projects:write
  • billing:read
  • billing:write

Use policies for record-level access:

  • This project belongs to this user.
  • This user belongs to this team.
  • This account is active.
  • This role can archive records.

[IMAGE: Supporting visual 2 for Building CRUD APIs With Laravel Sanctum Authentication in 2021, showing Building CRUD APIs With Laravel Sanctum Authentication in 2021 decisions, examples, and Laravel, Sanctum, APIs. Alt: Building CRUD APIs With Laravel Sanctum Authentication in 2021 building-crud-apis-laravel-sanctum-authentication-2021 visual 2]

The safest pattern is both:

return $project->user_id === $user->id
    && $user->tokenCan('projects:write');

Abilities define what the token can do. Policies decide whether the user can do it to this resource.

Return consistent error shapes

For a token-authenticated API, clients should expect:

  • 401 Unauthorized when no valid token was sent.
  • 403 Forbidden when the token or user is not allowed to perform the action.
  • 422 Unprocessable Entity for validation errors.
  • 404 Not Found when a route-bound model is missing.
  • 204 No Content after a successful delete.

[IMAGE: Supporting visual 2 for Building CRUD APIs With Laravel Sanctum Authentication in 2021, showing Building CRUD APIs With Laravel Sanctum Authentication in 2021 decisions, examples, and Laravel, Sanctum, APIs. Alt: Building CRUD APIs With Laravel Sanctum Authentication in 2021 building-crud-apis-laravel-sanctum-authentication-2021 visual 2]

Do not return 200 with { "success": false } for everything. HTTP status codes are part of the API contract.

Laravel already handles most of this if you use middleware, form requests, policies, and resource responses instead of manual controller conditionals everywhere.

Production checklist

Before shipping a Sanctum CRUD API:

  • Use HTTPS everywhere.
  • Hash passwords with Laravel's hasher.
  • Rate limit login and token-issuing routes.
  • Return the plain text token only once.
  • Revoke tokens on logout and account compromise.
  • Keep bearer tokens out of logs.
  • Do not store browser SPA tokens in local storage when cookie auth is the correct mode.
  • Validate every write request with form requests.
  • Scope queries by the authenticated user or tenant.
  • Use policies for record-level authorization.
  • Transform output with API resources.
  • Paginate list endpoints.
  • Add feature tests for 401, 403, 422, 201, 200, and 204 paths.

Sanctum makes token authentication small. It does not remove the need for validation, ownership checks, rate limiting, and a stable JSON contract.

FAQ

What is Building CRUD APIs With Laravel Sanctum Authentication in 2021?

Building CRUD APIs With Laravel Sanctum Authentication in 2021 is a practical laravel topic that should be evaluated through implementation scope, production risk, testing, documentation, and long-term maintainability.

When should a team use Building CRUD APIs With Laravel Sanctum Authentication in 2021?

Use Building CRUD APIs With Laravel Sanctum Authentication in 2021 when it solves a real project constraint, improves clarity, or reduces operational risk. Avoid it when it only adds novelty or hides behavior from future maintainers.

What is the biggest risk with Building CRUD APIs With Laravel Sanctum Authentication in 2021?

The biggest risk is copying a pattern without its context. Production systems need clear boundaries, rollback options, tests, and observability before a technique becomes dependable.

How do you test Building CRUD APIs With Laravel Sanctum Authentication in 2021?

Test the smallest unit that owns the behavior, then add integration coverage for the path users or systems actually rely on. Include failure cases, configuration differences, and regression checks.

How does Building CRUD APIs With Laravel Sanctum Authentication in 2021 affect SEO and AI search visibility?

It improves visibility when the article gives a direct answer, expert context, structured headings, internal links, trustworthy references, and FAQ content that matches the visible page.

Conclusion

Building CRUD APIs With Laravel Sanctum Authentication in 2021 is worth doing when the implementation improves clarity, reliability, or delivery speed. It is not worth doing when it hides ownership, increases operational risk, or makes the system harder to explain.

Use the framework above as a review checklist. Then connect this topic to the rest of the project documentation so readers can move from concept to implementation without losing context.

Top