A step-by-step guide to creating a scalable, maintainable Laravel API starter—using real code, real repos, and real-world patterns.
Why This Guide?
- Consistent API responses (no more frontend confusion)
- Reusable traits (DRY, testable code)
- Extendable base controllers (easy to scale)
- Built-in authentication (Sanctum)
- Automated scaffolding (Artisan commands)
- Repo links for every step
1️⃣ Project Setup: Start Clean
a) Create a new Laravel project:
composer create-project laravel/laravel my-api
cd my-api
b) Add Sanctum for API auth:
composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate
c) Set up your API routes:
Edit routes/api.php
and use the auth:sanctum
middleware for protected endpoints:
Route::middleware('auth:sanctum')->get('/user', fn(Request $request) => $request->user());
2️⃣ Traits: The Secret to DRY, Consistent APIs
a) HandlesApiResponse
Standardize every response—success or error.
// app/Concerns/HandlesApiResponse.php
trait HandlesApiResponse
{
public function respondSuccess($data = null, string $message = '', int $status = 200)
{
return response()->json([
'success' => true,
'data' => $data,
'message' => $message,
'errors' => null
], $status);
}
public function respondError(string $message, int $code = 422, $errors = null)
{
return response()->json([
'success' => false,
'data' => null,
'message' => $message,
'errors' => $errors
], $code);
}
}
b) HandlesValidation
Centralize validation logic.
// app/Concerns/HandlesValidation.php
trait HandlesValidation
{
public function validateRequest(Request $request, array $rules)
{
return $request->validate($rules);
}
}
Usage Example:
class UserController extends BaseApiController
{
public function store(Request $request)
{
$data = $this->validateRequest($request, [
'email' => 'required|email|unique:users'
]);
return $this->respondSuccess(User::create($data), 'User created', 201);
}
}
3️⃣ Base Controllers: Your API's Foundation
a) BaseApiController
Centralize traits and error handling.
class BaseApiController extends Controller
{
use HandlesApiResponse, HandlesValidation;
public function handleRequest(callable $action, string $successMessage)
{
try {
$result = $action();
return $this->respondSuccess($result, $successMessage);
} catch (Throwable $e) {
return $this->respondError($e->getMessage());
}
}
}
b) ProtectedApiController
Auto-protect endpoints with Sanctum.
class ProtectedApiController extends BaseApiController
{
public function __construct()
{
$this->middleware('auth:sanctum');
}
}
Usage Example:
ProfileController Example
class ProfileController extends ProtectedApiController
{
public function index()
{
return $this->respondSuccess(auth()->user(), 'Profile loaded');
}
}
4️⃣ Automate with Artisan: No More Boilerplate
Create a custom command to scaffold controllers, requests, and resources:
php artisan make:api-resource Post
This generates:
PostController
PostResource
-
StorePostRequest
/UpdatePostRequest
PostPolicy
5️⃣ Response Standards: Make Frontend Happy
Success:
{
"success": true,
"data": { "id": 1, "name": "John" },
"message": "User loaded",
"errors": null
}
Error:
{
"success": false,
"data": null,
"message": "Validation failed",
"errors": { "email": ["Invalid email"] }
}
6️⃣ Your Next Steps
- Clone the boilerplate or this variant.
- Run
php artisan serve
. - Build your first endpoint in minutes.
- Try the challenge: Add a
DELETE /posts/{id}
endpoint with ownership checks (solution here).
Why This Works
- No more spaghetti code: Traits and base controllers keep things DRY and testable.
- Frontend-friendly: Predictable, consistent responses.
- Easy to extend: Add features without rewriting core logic.
- Battle-tested: Used in real projects (see repo stars & issues).
References & Further Reading
- codewithmikee/laravel-backend-starter-template
- codewithmikee/laravel-api-boilerplate
- Laravel Docs
- Sanctum Docs
Ready to build? Fork, clone, and make it yours!
Questions? Open an issue on the repo or reach out to the community.
Top comments (0)