Skip to main content

Building a RESTful API with Sanctum

20/28
Chapter 5 Authentication and Authorization

Building a RESTful API with Sanctum

26 min read Lesson 20 / 28

RESTful API Development with Laravel Sanctum

Laravel Sanctum provides token-based authentication for SPAs, mobile apps, and third-party integrations.

API Route Structure

// routes/api.php
Route::prefix('v1')->name('api.v1.')->group(function () {
    // Public
    Route::get('/posts', [PostController::class, 'index']);
    Route::get('/posts/{post:slug}', [PostController::class, 'show']);

    // Auth endpoints
    Route::post('/auth/login', [AuthController::class, 'login']);
    Route::post('/auth/register', [AuthController::class, 'register']);

    // Protected
    Route::middleware('auth:sanctum')->group(function () {
        Route::get('/auth/me', [AuthController::class, 'me']);
        Route::post('/auth/logout', [AuthController::class, 'logout']);
        Route::apiResource('/posts', PostController::class)->except(['index', 'show']);
    });
});

Token Authentication

public function login(Request $request): JsonResponse
{
    $request->validate([
        'email' => ['required', 'email'],
        'password' => ['required'],
    ]);

    if (! Auth::attempt($request->only('email', 'password'))) {
        return response()->json(['message' => 'Invalid credentials.'], 401);
    }

    $token = $request->user()->createToken('api-token')->plainTextToken;

    return response()->json([
        'user' => new UserResource($request->user()),
        'token' => $token,
    ]);
}

API Controller Pattern

class PostController extends Controller
{
    public function index(Request $request): AnonymousResourceCollection
    {
        $posts = Post::with(['author', 'category'])
            ->published()
            ->when($request->category, fn ($q, $cat) =>
                $q->whereHas('category', fn ($q) => $q->where('slug', $cat))
            )
            ->latest()
            ->paginate(15);

        return PostResource::collection($posts);
    }

    public function store(StorePostRequest $request): JsonResponse
    {
        $post = $request->user()->posts()->create($request->validated());

        return response()->json(
            new PostResource($post),
            201
        );
    }
}

Consistent Error Responses

// In bootstrap/app.php
->withExceptions(function (Exceptions $exceptions) {
    $exceptions->render(function (ModelNotFoundException $e, Request $request) {
        if ($request->expectsJson()) {
            return response()->json(['message' => 'Resource not found.'], 404);
        }
    });

    $exceptions->render(function (ValidationException $e, Request $request) {
        if ($request->expectsJson()) {
            return response()->json([
                'message' => 'Validation failed.',
                'errors' => $e->errors(),
            ], 422);
        }
    });
})

A well-structured API with consistent responses makes integration effortless for frontend teams and third-party consumers.