Files
laravel-api-kit/README.md
Jean-Marc Strauven 5900990527 feat: Initial Laravel API-only starter kit
- Laravel 12 with Sanctum authentication
- API versioning with grazulex/laravel-apiroute
- spatie/laravel-query-builder for filtering/sorting
- spatie/laravel-data for DTOs
- dedoc/scramble for auto API documentation
- Pest PHP testing framework
- Docker development environment
- Standardized JSON API responses
- Rate limiting and CORS configuration
- Comprehensive README documentation
2025-12-25 06:33:21 +01:00

630 lines
16 KiB
Markdown

# Laravel API Kit
A production-ready, API-only Laravel 12 starter kit following the 2024-2025 REST API ecosystem best practices. No frontend dependencies - purely headless API for mobile apps, SPAs, or microservices.
[![PHP Version](https://img.shields.io/badge/PHP-8.3%2B-blue)](https://php.net)
[![Laravel Version](https://img.shields.io/badge/Laravel-12.x-red)](https://laravel.com)
[![License](https://img.shields.io/badge/License-MIT-green)](LICENSE)
## Features
- **API-Only** - No Blade, Vite, or frontend assets
- **Token Authentication** - Laravel Sanctum for mobile/SPA auth
- **API Versioning** - URI-based versioning with deprecation support via [grazulex/laravel-apiroute](https://github.com/Grazulex/laravel-apiroute)
- **Query Building** - Filtering, sorting, includes via [spatie/laravel-query-builder](https://github.com/spatie/laravel-query-builder)
- **Data Objects** - Type-safe DTOs via [spatie/laravel-data](https://github.com/spatie/laravel-data)
- **Auto Documentation** - Zero-annotation OpenAPI 3.1 via [dedoc/scramble](https://github.com/dedoc/scramble)
- **Modern Testing** - Pest PHP with Laravel HTTP testing
- **Rate Limiting** - Configurable per-route rate limiters
- **Standardized Responses** - Consistent JSON response format
## Requirements
- Docker & Docker Compose
- Or: PHP 8.3+, Composer 2.x
## Quick Start
### With Docker (Recommended)
```bash
# Clone the repository
git clone https://github.com/grazulex/laravel-api-kit.git
cd laravel-api-kit
# Copy environment file
cp .env.example .env
# Build and start containers
docker compose build
docker compose up -d
# Install dependencies
docker compose run --rm app composer install
# Generate application key
docker compose run --rm app php artisan key:generate
# Run migrations
docker compose run --rm app php artisan migrate
# Run tests to verify installation
docker compose run --rm app ./vendor/bin/pest
```
### Without Docker
```bash
# Clone and install
git clone https://github.com/grazulex/laravel-api-kit.git
cd laravel-api-kit
composer install
# Configure
cp .env.example .env
php artisan key:generate
# Database (SQLite by default)
touch database/database.sqlite
php artisan migrate
# Verify
./vendor/bin/pest
```
## API Documentation
Once running, access the auto-generated documentation:
- **Swagger UI**: [http://localhost:8080/docs/api](http://localhost:8080/docs/api)
- **OpenAPI JSON**: [http://localhost:8080/docs/api.json](http://localhost:8080/docs/api.json)
## Authentication
This kit uses **Laravel Sanctum** with token-based authentication (ideal for mobile apps and third-party API consumers).
### Register a New User
```bash
curl -X POST http://localhost:8080/api/v1/register \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"name": "John Doe",
"email": "john@example.com",
"password": "password123",
"password_confirmation": "password123"
}'
```
**Response:**
```json
{
"success": true,
"message": "User registered successfully",
"data": {
"user": {
"id": 1,
"name": "John Doe",
"email": "john@example.com",
"created_at": "2025-01-15T10:30:00+00:00"
},
"token": "1|abc123..."
}
}
```
### Login
```bash
curl -X POST http://localhost:8080/api/v1/login \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"email": "john@example.com",
"password": "password123"
}'
```
### Using the Token
Include the token in the `Authorization` header for protected routes:
```bash
curl -X GET http://localhost:8080/api/v1/me \
-H "Authorization: Bearer 1|abc123..." \
-H "Accept: application/json"
```
### Logout
```bash
curl -X POST http://localhost:8080/api/v1/logout \
-H "Authorization: Bearer 1|abc123..." \
-H "Accept: application/json"
```
## API Endpoints
### Version 1 (`/api/v1`)
| Method | Endpoint | Auth | Description | Rate Limit |
|--------|-------------|------|--------------------------|------------|
| POST | /register | No | Register new user | 5/min |
| POST | /login | No | Get authentication token | 5/min |
| POST | /logout | Yes | Revoke current token | 60/min |
| GET | /me | Yes | Get current user profile | 60/min |
## Response Format
All API responses follow a consistent format:
### Success Response
```json
{
"success": true,
"message": "Operation successful",
"data": {
// Response data here
}
}
```
### Error Response
```json
{
"success": false,
"message": "Error description",
"errors": {
"field": ["Validation error message"]
}
}
```
### HTTP Status Codes
| Code | Description |
|------|-------------|
| 200 | Success |
| 201 | Resource created |
| 204 | No content |
| 400 | Bad request |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| 422 | Validation error |
| 429 | Too many requests |
| 500 | Server error |
## Project Structure
```
laravel-api-kit/
├── app/
│ ├── Actions/ # Single-purpose action classes
│ ├── DTOs/ # Data Transfer Objects (spatie/laravel-data)
│ ├── Http/
│ │ ├── Controllers/
│ │ │ └── Api/
│ │ │ ├── ApiController.php # Base controller with ApiResponse
│ │ │ └── V1/ # Version 1 controllers
│ │ │ └── AuthController.php
│ │ ├── Requests/
│ │ │ └── Api/V1/ # Form Requests per version
│ │ │ ├── LoginRequest.php
│ │ │ └── RegisterRequest.php
│ │ └── Resources/ # API Resources
│ │ └── UserResource.php
│ ├── Models/
│ │ └── User.php # With HasApiTokens trait
│ ├── Providers/
│ │ └── AppServiceProvider.php # Rate limiting config
│ ├── Services/ # Business logic services
│ └── Traits/
│ └── ApiResponse.php # Standardized responses
├── config/
│ ├── apiroute.php # API versioning config
│ ├── cors.php # CORS settings
│ ├── sanctum.php # Token auth config
│ └── scramble.php # API docs config
├── routes/
│ └── api.php # API routes with versioning
├── tests/
│ └── Feature/Api/V1/
│ └── AuthTest.php # Authentication tests
├── docker-compose.yml
├── Dockerfile
└── CLAUDE.md # AI assistant instructions
```
## API Versioning
This kit uses [grazulex/laravel-apiroute](https://github.com/Grazulex/laravel-apiroute) for API versioning with support for:
- **URI Path** (default): `/api/v1/users`, `/api/v2/users`
- **Header**: `X-API-Version: 2`
- **Query Parameter**: `?api_version=2`
- **Accept Header**: `Accept: application/vnd.api.v2+json`
### Adding a New API Version
1. Create controllers in `app/Http/Controllers/Api/V2/`
2. Create requests in `app/Http/Requests/Api/V2/`
3. Update `routes/api.php`:
```php
use Grazulex\ApiRoute\Facades\ApiRoute;
// Version 2 - New current version
ApiRoute::version('v2', function () {
Route::post('register', [V2\AuthController::class, 'register']);
// ... more routes
})->current();
// Version 1 - Mark as deprecated
ApiRoute::version('v1', function () {
Route::post('register', [V1\AuthController::class, 'register']);
// ... existing routes
})->deprecated('2025-06-01')->sunset('2025-12-01');
```
### Deprecation Headers
When accessing deprecated versions, responses include RFC-compliant headers:
```http
Deprecation: @1717200000
Sunset: Sun, 01 Dec 2025 00:00:00 GMT
Link: </api/v2>; rel="successor-version"
```
## Query Building
Use [spatie/laravel-query-builder](https://spatie.be/docs/laravel-query-builder) for filtering, sorting, and including relationships:
```php
use Spatie\QueryBuilder\QueryBuilder;
use Spatie\QueryBuilder\AllowedFilter;
// In your controller
$users = QueryBuilder::for(User::class)
->allowedFilters([
'name',
'email',
AllowedFilter::exact('id'),
AllowedFilter::scope('active'),
])
->allowedSorts(['name', 'created_at'])
->allowedIncludes(['posts', 'comments'])
->paginate();
return UserResource::collection($users);
```
**Request examples:**
```
GET /api/v1/users?filter[name]=john
GET /api/v1/users?sort=-created_at
GET /api/v1/users?include=posts,comments
GET /api/v1/users?filter[name]=john&sort=name&include=posts
```
## Data Transfer Objects
Use [spatie/laravel-data](https://spatie.be/docs/laravel-data) for type-safe DTOs:
```php
// app/DTOs/UserData.php
use Spatie\LaravelData\Data;
class UserData extends Data
{
public function __construct(
public string $name,
public string $email,
public ?string $password = null,
) {}
}
// In controller - validates and transforms automatically
public function store(UserData $data): JsonResponse
{
$user = User::create($data->toArray());
return $this->created(UserResource::make($user));
}
```
## Rate Limiting
Configured in `app/Providers/AppServiceProvider.php`:
| Limiter | Limit | Use Case |
|---------|-------|----------|
| `api` | 60/min | Default for all API routes |
| `auth` | 5/min | Login/register (brute force protection) |
| `authenticated` | 120/min | Logged-in users |
### Applying Rate Limiters
```php
// In routes/api.php
Route::middleware('throttle:auth')->group(function () {
Route::post('login', [AuthController::class, 'login']);
Route::post('register', [AuthController::class, 'register']);
});
Route::middleware(['auth:sanctum', 'throttle:authenticated'])->group(function () {
// Protected routes with higher limits
});
```
### Rate Limit Headers
Responses include rate limit information:
```http
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 59
Retry-After: 60 # When limit exceeded
```
## Testing
This kit uses [Pest PHP](https://pestphp.com/) for testing:
```bash
# Run all tests
docker compose run --rm app ./vendor/bin/pest
# Run specific test file
docker compose run --rm app ./vendor/bin/pest tests/Feature/Api/V1/AuthTest.php
# Run with coverage
docker compose run --rm app ./vendor/bin/pest --coverage
# Run in parallel
docker compose run --rm app ./vendor/bin/pest --parallel
```
### Writing Tests
```php
// tests/Feature/Api/V1/UserTest.php
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
it('lists users for authenticated user', function () {
$user = User::factory()->create();
$token = $user->createToken('test')->plainTextToken;
User::factory()->count(5)->create();
$response = $this->withHeader('Authorization', "Bearer {$token}")
->getJson('/api/v1/users');
$response->assertStatus(200)
->assertJsonStructure([
'success',
'data' => [
'*' => ['id', 'name', 'email']
]
]);
});
it('requires authentication', function () {
$this->getJson('/api/v1/users')
->assertStatus(401);
});
```
## Development Commands
```bash
# Code formatting (Laravel Pint)
docker compose run --rm app ./vendor/bin/pint
# Check code style without fixing
docker compose run --rm app ./vendor/bin/pint --test
# List all routes
docker compose run --rm app php artisan route:list
# Clear all caches
docker compose run --rm app php artisan optimize:clear
# Generate IDE helper files (if using Laravel IDE Helper)
docker compose run --rm app php artisan ide-helper:generate
docker compose run --rm app php artisan ide-helper:models -N
# Export OpenAPI spec to file
docker compose run --rm app php artisan scramble:export
```
## Environment Configuration
Key `.env` variables:
```env
# Application
APP_NAME="Laravel API Kit"
APP_ENV=local
APP_DEBUG=true
APP_URL=http://localhost:8080
# Database (SQLite for development)
DB_CONNECTION=sqlite
DB_DATABASE=/var/www/database/database.sqlite
# For MySQL/PostgreSQL
# DB_CONNECTION=mysql
# DB_HOST=mysql
# DB_PORT=3306
# DB_DATABASE=laravel_api_kit
# DB_USERNAME=laravel
# DB_PASSWORD=secret
# Sanctum
SANCTUM_STATEFUL_DOMAINS=localhost,localhost:3000,127.0.0.1
# API Versioning
API_VERSION_STRATEGY=uri
API_DEFAULT_VERSION=latest
# Rate Limiting
API_RATE_LIMIT=60
# Documentation
API_DOCS_URL=http://localhost:8080/docs/api
```
## Deployment
### Production Checklist
- [ ] Set `APP_ENV=production` and `APP_DEBUG=false`
- [ ] Configure proper database (MySQL/PostgreSQL)
- [ ] Set `APP_URL` to your production URL
- [ ] Configure `SANCTUM_STATEFUL_DOMAINS` for your frontend domains
- [ ] Review and tighten CORS settings in `config/cors.php`
- [ ] Set up proper rate limiting for production load
- [ ] Configure caching (Redis recommended)
- [ ] Set up queue worker for background jobs
- [ ] Enable HTTPS and update URLs
### Docker Production
```dockerfile
# Example production Dockerfile additions
FROM php:8.3-fpm-alpine
# Install opcache for performance
RUN docker-php-ext-install opcache
# Production PHP settings
COPY docker/php/opcache.ini /usr/local/etc/php/conf.d/
COPY docker/php/php.ini /usr/local/etc/php/conf.d/
```
## Extending the Kit
### Adding a New Resource (CRUD Example)
1. **Create Model & Migration:**
```bash
docker compose run --rm app php artisan make:model Post -m
```
2. **Create Controller:**
```php
// app/Http/Controllers/Api/V1/PostController.php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Api\ApiController;
use App\Http\Resources\PostResource;
use App\Models\Post;
use Spatie\QueryBuilder\QueryBuilder;
class PostController extends ApiController
{
public function index()
{
$posts = QueryBuilder::for(Post::class)
->allowedFilters(['title', 'status'])
->allowedSorts(['title', 'created_at'])
->allowedIncludes(['author', 'comments'])
->paginate();
return $this->success(PostResource::collection($posts));
}
public function show(Post $post)
{
return $this->success(new PostResource($post));
}
// ... store, update, destroy methods
}
```
3. **Create Resource:**
```php
// app/Http/Resources/PostResource.php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class PostResource extends JsonResource
{
public function toArray($request): array
{
return [
'id' => $this->id,
'title' => $this->title,
'content' => $this->content,
'author' => new UserResource($this->whenLoaded('author')),
'created_at' => $this->created_at?->toIso8601String(),
];
}
}
```
4. **Add Routes:**
```php
// routes/api.php
ApiRoute::version('v1', function () {
// ... existing routes
Route::middleware('auth:sanctum')->group(function () {
Route::apiResource('posts', PostController::class);
});
})->current();
```
5. **Create Tests:**
```php
// tests/Feature/Api/V1/PostTest.php
uses(RefreshDatabase::class);
it('lists posts', function () {
$user = User::factory()->create();
Post::factory()->count(3)->create();
$this->actingAs($user)
->getJson('/api/v1/posts')
->assertStatus(200)
->assertJsonCount(3, 'data');
});
```
## Contributing
1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request
## License
This project is open-sourced software licensed under the [MIT license](LICENSE).
## Credits
- [Laravel](https://laravel.com) - The PHP Framework
- [Laravel Sanctum](https://laravel.com/docs/sanctum) - API Token Authentication
- [grazulex/laravel-apiroute](https://github.com/Grazulex/laravel-apiroute) - API Versioning
- [spatie/laravel-query-builder](https://github.com/spatie/laravel-query-builder) - Query Building
- [spatie/laravel-data](https://github.com/spatie/laravel-data) - Data Transfer Objects
- [dedoc/scramble](https://github.com/dedoc/scramble) - API Documentation
- [Pest PHP](https://pestphp.com) - Testing Framework
## Support
- [Documentation](https://github.com/grazulex/laravel-api-kit/wiki)
- [Issues](https://github.com/grazulex/laravel-api-kit/issues)
- [Discussions](https://github.com/grazulex/laravel-api-kit/discussions)