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.