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)