orebarranco / laravel-api-starter-kit
Production-ready API-only starter kit for Laravel 13 with Sanctum auth, JSON:API responses, DTOs, Actions, and 100% test coverage.
Package info
github.com/orebarranco/laravel-api-starter-kit
Type:project
pkg:composer/orebarranco/laravel-api-starter-kit
Requires
- php: ^8.4
- laravel/framework: ^13.2
- laravel/sanctum: ^4.3.1
- laravel/tinker: ^3.0
- spatie/laravel-query-builder: ^7.1
Requires (Dev)
- driftingly/rector-laravel: ^2.2
- fakerphp/faker: ^1.24.1
- larastan/larastan: ^3.9.3
- laravel/boost: ^2.4.1
- laravel/pail: ^1.2.6
- laravel/pint: ^1.29
- laravel/sail: ^1.55
- mockery/mockery: ^1.6.12
- nunomaduro/collision: ^8.9.1
- pestphp/pest: ^4.4.3
- pestphp/pest-plugin-laravel: ^4.1
- pestphp/pest-plugin-type-coverage: ^4.0.3
- rector/rector: ^2.3.9
This package is auto-updated.
Last update: 2026-04-21 13:42:22 UTC
README
A production-ready, API-only starter built with Laravel 13 and PHP 8.4. Designed for scalable backends, mobile apps, SPAs, SaaS platforms, and microservices.
No frontend scaffolding. No Blade. Pure headless API.
Core Philosophy
- Thin controllers — business logic lives in Actions
- Typed DTOs hydrated from Form Requests via
toDto() - Strict typing throughout (
declare(strict_types=1),finalclasses) - JSON:API compliant responses
- Versioned APIs from day one
Features
- Token authentication via Laravel Sanctum
- Email verification with signed URLs
- Password reset via email
- Rate limiting (per IP and per user)
- API versioning (URI-based)
- Custom
readonlyDTOs (no external packages) - JSON:API resource objects via
JsonApiResource - Centralized exception handling
- Pest with 100% coverage enforced
- Static analysis via Larastan
- Automated refactoring with Rector
- Code formatting via Laravel Pint
Requirements
- PHP 8.4+
- Composer 2.x
- MySQL / PostgreSQL / SQLite
Quick Start
Via the Laravel installer (recommended):
laravel new myapp --using=orebarranco/laravel-api-starter-kit
Via Composer:
composer create-project orebarranco/laravel-api-starter-kit myapp
Via Git:
git clone https://github.com/orebarranco/laravel-api-starter-kit.git myapp
cd myapp
composer setup
composer test
Authentication
Protected routes require:
Authorization: Bearer {token}
| Endpoint | Method | Auth |
|---|---|---|
/api/v1/auth/register |
POST | — |
/api/v1/auth/login |
POST | — |
/api/v1/auth/logout |
POST | Bearer |
/api/v1/auth/me |
GET | Bearer |
/api/v1/auth/forgot-password |
POST | — |
/api/v1/auth/reset-password |
POST | — |
/api/v1/auth/email/verify/{id}/{hash} |
GET | Signed URL |
/api/v1/auth/email/resend |
POST | Bearer |
API Versioning
URI-based versioning. Each version is fully isolated:
app/Http/Controllers/Api/V1/
app/Http/Requests/Api/V1/
routes/api/v1.php
Response Format
All responses use Content-Type: application/vnd.api+json.
Success
{
"data": {
"id": "01kn38s0cv0edq25et3vyrxd7s",
"type": "users",
"attributes": { "name": "Carlos Méndez", "email": "carlos@example.com" }
},
"meta": { "request_id": "...", "version": "v1", "timestamp": "..." }
}
Error
{
"errors": [{
"status": "422",
"code": "VALIDATION_ERROR",
"title": "The given data was invalid.",
"detail": "The email field is required.",
"source": { "pointer": "/data/attributes/email" }
}],
"meta": { "request_id": "...", "version": "v1", "timestamp": "..." }
}
Project Structure
app/
├── Actions/ # Single-purpose use cases
├── DTOs/ # Immutable readonly DTOs
├── Exceptions/ # Typed exceptions + centralized handler
├── Http/
│ ├── Controllers/Api/ # Versioned, single-action controllers
│ ├── Middleware/ # ForceJsonResponse, EnsureEmailIsVerified
│ ├── Requests/Api/ # Validation + toDto()
│ └── Resources/Api/ # JSON:API resources
├── Models/
├── Providers/ # AppServiceProvider (rate limiting, email verification, password reset)
└── Traits/ # ApiResponse
routes/
├── api.php # Version grouping
└── api/v1.php
Action Pattern
Controllers delegate to single-purpose Action classes:
// Controller public function __invoke(RegisterRequest $request, RegisterUserAction $action): JsonResponse { $result = $action->execute($request->toDto()); return $this->success(new UserResource($result['user']), Response::HTTP_CREATED, [ 'token' => $result['token'], ]); } // Action public function execute(RegisterUserDTO $data): array { $user = User::query()->create([...]); event(new Registered($user)); return ['user' => $user, 'token' => $user->createToken('auth_token')->plainTextToken]; }
Rate Limiting
| Limiter | Routes | Limit |
|---|---|---|
auth |
register, login, forgot-password, reset-password, email verify | 5 req/min per IP |
api |
all authenticated endpoints | 120 req/min per user · 60 req/min per IP |
Email Verification
Sent automatically on registration via the Registered event.
GET /auth/email/verify/{id}/{hash} — no auth required (signed URL)
POST /auth/email/resend — requires Bearer token
Password Reset
POST /auth/forgot-password — sends reset link to email (no auth required)
POST /auth/reset-password — resets password and invalidates all tokens (no auth required)
The reset link points to FRONTEND_URL/reset-password?token=...&email=.... Configure FRONTEND_URL in your .env.
Middleware
force.json— enforcesAccept: application/vnd.api+jsonapi.version— setsX-API-Versionresponse headerverified— requires verified email →EMAIL_NOT_VERIFIED(403)
Testing
composer test # lint + static analysis + coverage composer test:unit # unit tests only
Powered by Pest 4 with 100% coverage enforced. Feature and unit tests for all controllers, actions, middleware, and exception handling.
Code Quality
composer lint # Rector + Pint
- PHPStan level max via Larastan
- Rector for automated refactoring
- Laravel Pint for code style
License
MIT License