Quick start
Expose your models
Run the quick start command in your application:
php artisan rest:quick-start
The command asks Which models should the API expose? and lists the models of app/Models. Pick one or more. For each model, it writes:
- a resource in
app/Rest/Resources, with one field per column of the table and the relations to the other chosen models, asrest:resource --generatedoes; - a controller in
app/Rest/Controllers; - a policy in
app/Policiesthat allows every ability to a signed-in user, when Gate finds no policy for the model; - a route in
routes/api.php, behind the authentication middleware of the guard.
It also creates the base classes app/Rest/Resources/Resource.php and app/Rest/Controllers/Controller.php when they are missing. An existing class is never changed: the command reports it as kept, so a second run changes nothing.
For a Product model, the route looks like this:
use App\Rest\Controllers\ProductsController;
use Lomkit\Rest\Facades\Rest;
Rest::resource('products', ProductsController::class)
->middleware('auth:sanctum');
Without a terminal
In CI, in a script, or with --no-interaction, name each model with --model. The command never answers its own questions, so pass --install-api too when routes/api.php or Laravel Sanctum is missing:
php artisan rest:quick-start --no-interaction --model=Product --model=Category --install-api
Choosing the guard
The routes use the sanctum guard by default. Pass --guard to use another one:
sanctumuses Laravel Sanctum.apiuses Laravel Passport, whenconfig/auth.phpdoes not define it or defines it with thepassportdriver.- Any other guard that
config/auth.phpdefines is used as it is, for example--guard=web.
php artisan rest:quick-start --model=Product --guard=api --install-api
When routes/api.php does not exist, or when the package of the guard is missing, the command asks before it runs php artisan install:api (Laravel Sanctum) or php artisan install:api --passport (Laravel Passport). --install-api runs it without asking.
When such an install is needed, --guard is not passed and a terminal can answer, the command first asks Which guard should protect the API routes?, with sanctum as the default. Without a terminal, the guard is sanctum.
api guard with the passport driver in config/auth.php. The command never edits that file: add the guard yourself.What the command checks
Before it writes anything, the command stops with exit code 1 when:
- the routes of the application are cached: run
php artisan route:clearfirst; - a model does not exist or has no table;
- two chosen models would get the same resource name, such as
App\Models\NoteandApp\Models\Archive\Note; - an existing route already uses the URIs or the names of the new routes, such as
Route::apiResource('products', ...); - Gate would not find the policy that it writes, for example for a model outside the namespace of the application. Register a policy with
Gate::policy(), then run the command again.
After writing, it checks the result in a new PHP process: each route is behind an authentication middleware whose guard exists, and Gate finds a policy for each model.
checks (fresh process) ... 6 routes authenticated, 1 policy found
It does not check the perimeters of the resources or the row checks of the actions: run php artisan rest:check for these, see Perimeter.
A relation to a model that you did not choose is skipped and reported, with the option to add:
Skipped: Post.author: no resource for App\Models\User. Rerun with --model=User to expose users.
Authenticate
The command ends with the next steps: a command that creates a token for your first user, then a curl request to the search route of the first model. The request goes to the app.url of your configuration, http://localhost when it is empty. When the command wrote policies, a reminder that they allow every ability follows. With Laravel Sanctum:
Next:
php artisan tinker --execute="echo App\Models\User::first()->createToken('cli')->plainTextToken;"
curl -X POST http://localhost/api/products/search -H "Authorization: Bearer <token>" -H "Accept: application/json" -H "Content-Type: application/json" -d '{"search":{"limit":10}}'
ProductPolicy lets signed-in users do everything. Edit app/Policies before production.
- With Laravel Passport, the token command reads
accessTokeninstead ofplainTextToken. - With another guard, the command prints
Authenticate with the web guard to call the API.(for--guard=web) instead of a token command, and thecurlrequest has noAuthorizationheader. - The user model needs the
HasApiTokenstrait of Laravel Sanctum or Laravel Passport. The command never edits the user model: it warns when the trait is missing.
Your first search
Assume the generated ProductResource declares id, name and price, and the table holds a desk lamp at 39 and a floor lamp at 129. Search the products cheaper than 100:
POST /api/products/search
Authorization: Bearer <token>
Accept: application/json
{
"search": {
"filters": [
{"field": "price", "operator": "<", "value": 100}
],
"selects": [
{"field": "id"},
{"field": "name"},
{"field": "price"}
]
}
}
{
"current_page": 1,
"data": [
{"id": 1, "name": "Desk lamp", "price": 39}
],
"from": 1,
"last_page": 1,
"per_page": 50,
"to": 1,
"total": 1,
"meta": {}
}
- A page holds 50 rows unless the request sends
limit, one of the resource'slimits(). - Without
selects, each row holds every selectable field of the resource. - A filter on a field that the resource does not declare gives a
422.
The search endpoint also takes sorts, includes, aggregates, scopes and a text search. See Search.
Your first mutation
Create a product and attach it to the category 1:
POST /api/products/mutate
{
"mutate": [
{
"operation": "create",
"attributes": {"name": "Floor lamp", "price": 129},
"relations": {
"category": {"operation": "attach", "key": 1}
}
}
]
}
The response gives one entry for each entry of mutate, with the key of the created row:
{
"operations": [
{"operation": "create", "key": 2, "relations": {"category": {"operation": "attach", "key": 1}}}
]
}
The field rules validate each attribute. The generator marks a column that is not nullable and has no default with creationRules('required'). Without such a name, the request gives a 422 and writes nothing:
{
"message": "The mutate.0.attributes.name field is required.",
"errors": {
"mutate.0.attributes.name": ["The mutate.0.attributes.name field is required."]
}
}
See Mutate for updates, nested writes and the response block that returns the written rows.
Next steps
- Resources: write and register a resource by hand.
- Fields: choose what clients read, filter, sort and write.
- Perimeter: decide which rows exist for each client.
- Configuration: change the defaults of the package.