1. Go to this page and download the library: Download devespresso/laravel-api-kit library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
// Invokable class that returns the current user's role (string or int, or null)
'role_resolver' => App\Support\RoleResolver::class,
// String roles — ordered lowest to highest
'roles' => ['moderator', 'editor', 'admin'],
// OR — numeric roles, no list needed
'numeric_roles' => false,
'transformers' => [
'prefixes' => [
'hidden_attributes' => '!', // selected from DB but excluded from the JSON response
'custom_attributes' => '@', // computed via a transformer method, not read from the DB
'accessor_attributes'=> '~', // Laravel model accessor — not selected from DB, but
use Devespresso\LaravelApiKit\Traits\EnableDatabaseFiltering;
class Post extends Model
{
use EnableDatabaseFiltering;
protected $defaultFilterService = PostFilterService::class; // optional
protected $searchableColumns = ['title', 'body']; // used by the search scope
}
Post::filter(
data: $request->validated(), // drives filter methods and sorting
user: $request->user(), // available via $this->user and getEffectiveRoles()
query: $query, // pre-scoped Builder — base constraints before filters run
extras: $extras, // arbitrary context — read via $this->getExtraProperty('key')
);
// Only show posts belonging to the current team — enforced before filters run
$query = Post::where('team_id', $team->id);
$posts = Post::filter($request->validated(), $request->user(), query: $query);
$posts = Post::filter(
$request->validated(),
$request->user(),
extras: ['team' => $team]
);
// Inside PostFilterService — read the extra value via getExtraProperty():
public function setConditions(): void
{
$team = $this->getExtraProperty('team');
$this->query->where('visibility', $team->default_visibility);
}
public function setConditions(): void
{
// Scope results to the authenticated user
$this->query->where('user_id', $this->user->id);
}
public function status(string $value): void
{
// Only admins can filter by draft status
if ($value === 'draft' && !in_array('admin', $this->getEffectiveRoles())) {
return;
}
$this->query->where('status', $value);
}
use Devespresso\LaravelApiKit\Services\Filters\BaseFilterService;
class PostFilterService extends BaseFilterService
{
// Columns users are allowed to sort by
protected $sortColumns = ['created_at', 'updated_at', 'id', 'title'];
// Alias => real column name mappings for sort
protected $customSortColumns = ['date' => 'created_at'];
// Default sort when no 'sort' key is in the request
protected $defaultSortingColumn = ['created_at,desc'];
// Methods that cannot be triggered by request data
protected $guardedMethods = ['sensitiveMethod'];
// Methods restricted by role
protected $roleMethods = ['admin' => [' function onlyPublished(bool $value): void
{
$this->query->where('published', true);
}
// Only callable by users with the 'admin' role
public function
$this->getDataValue('key', $default); // get a value from request data
$this->dataHasValue('key', 'value'); // check if key equals a specific value
$this->dataHasKeys(['key1', 'key2']); // check all keys are present
$this->getExtraProperty('tenant_id'); // get a value from $extras
$this->with(['comments', 'tags']); // eager load relations
$this->withCount(['comments']); // eager load relation counts
$this->disableConditions(); // skip setConditions() for the next filter() call — useful in admin or internal contexts
$this->setSelect(['id', 'title']); // override the auto-selected columns; has no effect when auto_select is disabled
protected function applyTeamScope(): void
{
// safe — cannot be triggered from request data
$this->query->where('team_id', $this->user->team_id);
}
// app/Support/RoleResolver.php
class RoleResolver
{
public function __invoke(?Authenticatable $user): mixed
{
return $user?->role; // e.g. 'admin', 'editor', 'moderator', or null
}
}
// app/Services/Filters/PostFilterService.php
class PostFilterService extends BaseFilterService
{
protected $roleMethods = [
'moderator' => [' public function $this->query->where('published', false);
}
// Admin only
public function
// Inside a controller or repository — restrict to safe filters for this endpoint
$posts = Post::filter(
$request->validated(),
$request->user(),
explicitFilters: ['status', 'category_id', 'published']
);
protected $sortColumns = ['created_at', 'updated_at', 'id', 'title'];
// 'date' in the request maps to 'created_at' on the query
protected $customSortColumns = ['date' => 'created_at'];
class PostTransformer extends BaseTransformer
{
// Declare the highest version this transformer explicitly supports.
// Any version within this boundary that has no method will throw.
// Leave null to silently skip missing version methods.
protected ?string $latestVersion = 'v3';
// The starting point — used by all versions.
// Use this method instead of $formats when versioning is enabled.
protected function baseFormat(): array
{
return [
'*' => ['id', 'title', 'status'],
'show' => ['email', 'created_at'],
];
}
// v2 builds on base
protected function v2Format(): array
{
return [
'append' => [
'*' => ['avatar'],
'show' => ['phone'],
],
'remove' => [
'*' => ['status'], // removed in v2
],
];
}
// v3 builds on v2
protected function v3Format(): array
{
return [
'append' => [
'*' => ['verified_at'],
],
];
}
}
class PostTransformer extends BaseTransformer
{
protected $formats = [
'*' => [
'id',
'title',
'status',
'user_id', // foreign key — must be m attribute — excluded from SELECT, resolved via transformer method
'~reading_time', // accessor attribute — excluded from SELECT, resolved via model accessor
'!team_id', // hidden from output — but still SELECTed (useful for auth checks)
'author' => [ // relation — auto eager-loaded
'id',
'name',
'!email', // hidden from output — but still SELECTed
],
],
];
}
// @word_count and ~reading_time are omitted from SELECT — they are resolved
// after the query via the transformer method and model accessor respectively.
Post::select('posts.id', 'posts.title', 'posts.status', 'posts.user_id', 'posts.team_id')
->with(['author' => fn ($q) => $q->select('users.id', 'users.name', 'users.email')])
->where('team_id', $user->team_id)
->where('published', true)
->simplePaginate($perPage);
use Devespresso\LaravelApiKit\Repositories\BaseRepository;
class PostRepository extends BaseRepository
{
// Optional: override auto-resolved model
protected $model = Post::class;
// Hooks
protected function beforeCreate(array &$attributes): void
{
$attributes['slug'] = Str::slug($attributes['title']);
}
protected function afterCreated(Model $model, array $attributes): void
{
event(new PostCreated($model));
}
protected function beforeUpdate(?Model $model, array &$attributes): void
{
if (isset($attributes['title'])) {
$attributes['slug'] = Str::slug($attributes['title']);
}
}
protected function afterUpdated(Model $model, array $attributes): void
{
Cache::forget("post:{$model->id}");
}
protected function beforeDelete(Model $model): void
{
// runs before delete
}
protected function afterDeleted(Model $model): void
{
// runs after delete
}
}
$repo->index($data, $user); // filtered, paginated list
$repo->index($data, $user, explicitFilters: ['status', 'name']); // with explicit filter allowlist
$repo->get($id); // single record
$repo->create($attributes); // create with hooks
$repo->update($model, $attributes); // update with hooks
$repo->delete($model); // delete with hooks
// Skip all hooks
$repo->withoutHooks()->delete($model);
// Skip specific hooks only
$repo->withoutHooks('afterCreated')->create($attributes);
$repo->withoutHooks('beforeUpdate', 'afterUpdated')->update($model, $attributes);
use Devespresso\LaravelApiKit\Controllers\ApiController;
class PostController extends ApiController
{
public function index(PostRequest $request): JsonResponse
{
return $this->setData(
$this->repository->index($request->validated(), $request->user())
)->respond();
}
public function store(PostRequest $request): JsonResponse
{
return $this->setData(
$this->repository->create($request->validated())
)->respondCreated();
}
public function destroy(Post $post): JsonResponse
{
$this->repository->delete($post);
return $this->respondNoContent();
}
}
// Merge extra keys into the response
return $this->setData($post)->respond(['extra' => 'value']);
// Override a key set by setData()
return $this->setData($post)->respond(['post' => $customPayload], override: true);
return $this->setData($post, 'post')->respond();
// produces: {"post": {...}} instead of the transformer default
return $this->setData($post, format: 'show')->respond();
// uses the 'show' format from PostTransformer
bash
php artisan devespresso:api-kit:scaffold Post
bash
# Only generate specific components
php artisan devespresso:api-kit:scaffold Post --only=model,repository,transformer
# Skip specific components
php artisan devespresso:api-kit:scaffold Post --except=model
# Overwrite existing files
php artisan devespresso:api-kit:scaffold Post --force