API Template
CRUD Templates for Laravel comes with the API template out of the box. It generates a complete RESTful API with all necessary components following Laravel best practices.
Using the Template
The API template is the default, so you don't need to specify it:
php artisan crud:generate Post --fields="title:string,content:text"But you can also explicitly specify it:
php artisan crud:generate Post --template=api --fields="title:string"Generated Files
When you generate a CRUD using the API template, the following files are created:
1. Controller (app/Http/Controllers/Api/{Model}Controller.php)
A RESTful controller with five methods:
class PostController extends Controller
{
public function index() // GET /api/posts
public function store() // POST /api/posts
public function show() // GET /api/posts/{id}
public function update() // PUT/PATCH /api/posts/{id}
public function destroy() // DELETE /api/posts/{id}
}Features:
- Paginated index listings
- Authorization via policies
- Request validation
- API resource responses
- Proper HTTP status codes
2. Model (app/Models/{Model}.php)
An Eloquent model with:
class Post extends Model
{
protected $fillable = [...]; // Based on your fields
protected $casts = [...]; // Auto-detected from field types
// Relationship methods for belongsTo, hasMany, etc.
public function category()
{
return $this->belongsTo(Category::class);
}
}3. Policy (app/Policies/{Model}Policy.php)
Authorization logic for all CRUD operations:
class PostPolicy
{
public function viewAny(User $user): bool
public function view(User $user, Post $post): bool
public function create(User $user): bool
public function update(User $user, Post $post): bool
public function delete(User $user, Post $post): bool
}Default Behavior:
- All methods return
true(you should customize this) - Ready for your authorization logic
4. Request Classes
Two separate validation classes:
StoreRequest (app/Http/Requests/Store{Model}Request.php):
class StorePostRequest extends FormRequest
{
public function rules(): array
{
return [
'title' => ['required', 'string', 'max:255'],
'content' => ['required', 'string'],
'published' => ['required', 'boolean'],
'category_id' => ['bail', 'required', 'exists:categories,id'],
];
}
}UpdateRequest (app/Http/Requests/Update{Model}Request.php):
class UpdatePostRequest extends FormRequest
{
public function rules(): array
{
return [
'title' => ['required', 'string', 'max:255'],
'content' => ['required', 'string'],
'published' => ['required', 'boolean'],
'category_id' => ['bail', 'required', 'exists:categories,id'],
];
}
}5. API Resource (app/Http/Resources/{Model}Resource.php)
Transforms your model for API responses:
class PostResource extends JsonResource
{
public function toArray($request): array
{
return [
'id' => $this->id,
'title' => $this->title,
'content' => $this->content,
'published' => $this->published,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
'category' => CategoryResource::make($this->whenLoaded('category')),
];
}
}6. Migration (database/migrations/{timestamp}_create_{table}_table.php)
Complete database schema:
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('content');
$table->boolean('published');
$table->foreignId('category_id')->constrained();
$table->timestamps();
});7. Factory (database/factories/{Model}Factory.php)
Model factory for testing and seeding:
class PostFactory extends Factory
{
protected $model = Post::class;
public function definition(): array
{
return [
'title' => fake()->words(3, true),
'content' => fake()->paragraphs(3, true),
'published' => fake()->boolean(),
'category_id' => Category::factory(),
];
}
}8. Feature Test (tests/Feature/Api/{Model}ControllerTest.php)
Adds happy path tests for all of the endpoints generated. You're then free to add other test cases to the generated file.
class PostControllerTest extends TestCase
{
public function test_can_list_posts(): void;
public function test_can_create_post(): void;
public function test_can_show_post(): void;
public function test_can_update_post(): void;
public function test_can_delete_post(): void;
}9. API Routes
Routes are automatically added to your API routes file:
Route::apiResource('posts', PostController::class);This creates all five RESTful endpoints:
| HTTP Method | URI | Action | Description |
|---|---|---|---|
| GET | /api/posts | index | List all posts (paginated) |
| POST | /api/posts | store | Create a new post |
| GET | /api/posts/{id} | show | Show a specific post |
| PUT/PATCH | /api/posts/{id} | update | Update a post |
| DELETE | /api/posts/{id} | destroy | Delete a post |
10. Code Formatting
Laravel Pint is automatically run to format all generated files according to Laravel's coding standards.
Scopes
The API template supports scoping resources to users or teams using the --options flag:
No Scope (Default)
Resources are not scoped to any user or team:
php artisan crud:generate Post --template=api --fields="title:string"The generated policy methods return Response::allow() by default:
public function update(User $user, Post $post): bool|Response
{
return Response::allow(); // Customize with your authorization logic
}You should customize the policy to implement your authorization logic based on roles, permissions, or other criteria.
User Scope
Resources are automatically scoped to the authenticated user:
php artisan crud:generate Post --template=api --fields="title:string" --options="scope:user"A user_id foreign key is automatically added to the migration:
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->foreignId('user_id')->constrained();
$table->timestamps();
});This adds user_id filtering in the controller's index() and store() methods:
// Index: Only show posts belonging to the authenticated user
Post::where('user_id', Auth::id())
->paginate($request->integer('per_page'))
// Store: Automatically set user_id
Post::create([
...$request->validated(),
'user_id' => Auth::id(),
])The generated policy methods verify that the authenticated user owns the resource:
public function update(User $user, Post $post): bool|Response
{
return $post->user_id === $user->id;
}
public function delete(User $user, Post $post): bool|Response
{
return $post->user_id === $user->id;
}Team Scope
Resources are automatically scoped to the user's current team:
php artisan crud:generate Post --template=api --fields="title:string" --options="scope:team"A team_id foreign key is automatically added to the migration:
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->foreignId('team_id')->constrained();
$table->timestamps();
});Teams Table Required
The teams table must already exist in your database before running this migration, as the foreign key constraint references it.
This adds team_id filtering:
// Index: Only show posts belonging to the user's team
Post::where('team_id', Auth::user()->current_team_id)
->paginate($request->integer('per_page'))
// Store: Automatically set team_id
Post::create([
...$request->validated(),
'team_id' => Auth::user()->current_team_id,
])The generated policy methods verify that the user's team owns the resource:
public function update(User $user, Post $post): bool|Response
{
return $post->team_id === $user->current_team_id;
}
public function delete(User $user, Post $post): bool|Response
{
return $post->team_id === $user->current_team_id;
}User Model Requirement
Your User model must have a current_team_id column for team scoping to work.
Skipping Files
You can skip specific files using the --skip option. This is useful when you want to customize certain parts of the generated code, for example if they have already been created prior to running the command.
Available Generators to Skip
All generators listed in the Generated Files section can be skipped:
controller- Skip the controller generationmodel- Skip the model generationpolicy- Skip the policy generationstore-request- Skip the store request generationupdate-request- Skip the update request generationresource- Skip the resource generationmigration- Skip the migration generationfactory- Skip the factory generationtest- Skip the test generationapi-route- Skip the route registrationpint- Skip code formatting
Next Steps
If the API template doesn't fit your needs, worry not! You can create your own template, read further below:
- Create Your Own Template to generate different types of CRUDs beyond the default API pattern
- Explore Customizing Stubs to modify generated code
- Learn about Customizing Generators to customize or create new generators
- Check Customizing Field Types to add or customize existing field types