# Installation ## Meet Lomkit Lomkit is an organization dedicated to the Laravel community. We offer a collection of innovative open source packages for developers. Our commitment to sharing knowledge and expertise aims to make development easier, while keeping our packages up-to-date with the latest practices. Join our collaborative ecosystem where community contributions are welcome. Together, we're building a more powerful and inclusive software development future, pushing the boundaries of innovation. Explore our packages, contribute and let us accompany you on your software development journey with enthusiasm and dedication. ## Requirements - PHP **8.2** or higher - Laravel **12.0** or higher ## Installing Laravel Rest Api Install the package with: ```bash composer require lomkit/laravel-rest-api ``` (Optional) Publish the config file: ```bash php artisan vendor:publish --tag=rest-config ``` ::tip The quickest way to get up and running is the `rest:quick-start` command — it scaffolds a resource, controller, and route registration in one step. ```bash php artisan rest:quick-start ``` :: ### Setup your first project [Have a look at our getting started section](https://laravel-rest-api.lomkit.com/#first-setup) # Upgrade Guide ## Upgrade from v1 to v2 ::warning There is one breaking change in v2: the search request body must now be wrapped in a `search` key. :: Wrap the entire search body in a `search` key on every `POST .../search` call: ::code-group ```json [Before] { "filters": [ { "field": "id", "operator": ">", "value": 1 } ] } ``` ```json [After] { "search": { "filters": [ { "field": "id", "operator": ">", "value": 1 } ] } } ``` :: # Details ## How to use it When you are ready to get the details, you can use the `details` method by making a GET call: ```json // (GET) api/users ``` ## Response As a response, you'll receive the resource details: ```json { "data": { "actions": [ { "name": "Send Welcome Notification", "uriKey": "send-welcome-notification", "fields": { "delay": [ "required", "numeric" ] }, "meta": { "color": "#FFFFFF" }, "standalone": false, "targeted": false } ], "instructions": [ { "name": "Odd Even Id", "uriKey": "odd-even-id", "fields": { "type": [ "in:odd,even" ] }, "meta": { "color": "#FFFFFF" } } ], "fields": [ "id", "name" ], "limits": [ 1, 10, 25, 50 ], "scopes": [ "withTrashed" ], "relations": [ { "resources": [ "App\\Rest\\Resources\\UserResource" ], "relation": "posts", "constraints": { "required_on_creation": false, "prohibited_on_creation": false, "required_on_update": false, "prohibited_on_update": false }, "name": "HasMany" } ], "rules": { "all": {"id": ["numeric"]}, "create": {"password": ["required"]}, "update": {"password": ["prohibited"]} } } } ``` Keep in mind that these details are really helpful because, in some way, some data won't be exposed such as fields / actions / instructions depending on the user's rights and backend configuration. `uriKey` is the data to specify when making operations. It's a unique identifier in string format also known as slug. # Search ## Usage Here is a quick look at what you can do: ```json // (POST) api/posts/search { "search": { "text": { "value": "my full text search" }, "scopes": [ {"name": "withTrashed", "parameters": [true]} ], "filters": [ { "field": "id", "operator": ">", "value": 1, "type": "or" }, { "nested": [ {"field": "user.id", "operator": "<", "value": 2}, {"field": "id", "operator": ">", "value": 100, "type": "or"} ] } ], "sorts": [ {"field": "user_id", "direction": "desc"}, {"field": "id", "direction": "asc"} ], "selects": [ {"field": "id"} ], "includes": [ { "relation": "posts", "filters": [ {"field": "id", "operator": "in", "value": [1, 3]} ], "limit": 2 }, { "relation": "posts", "alias": "draftPosts", "filters": [ {"field": "published", "value": false} ] }, { "relation": "user", "filters": [ { "field": "languages.pivot.boolean", "operator": "=", "value": true } ] } ], "aggregates": [ { "relation": "stars", "type": "max", "field": "rate", "alias": "approved_max_stars", "filters": [ {"field": "approved", "value": true} ] } ], "instructions": [ { "name": "odd-even-id", "fields": [ { "name": "type", "value": "odd" } ] } ], "gates": ["create", "view"], "page": 2, "limit": 10 } } ``` ### Specifications | **Key** | **Type** | **Required** | **Default** | **Description** | | --------------------- | -------- | ------------------------------------ | ----------- | -------------------------------------------------------------------------------------------------------------- | | **Text** | | | | | | `text.value` | `string` | X | | The text you want to search | | **Scopes** | | | | | | `scopes.name` | `string` | X | | The name of the scope | | `scopes.parameters` | `array` | | | The parameters associated with the scope | | **Filters** | | | | | | `filters.field` | `string` | when not nested | | The name of the field | | `filters.operator` | `string` | | `=` | The field operator | | `filters.value` | `mixed` | when not nested | | The value you want to filter with | | `filters.type` | `string` | | `and` | The filter condition type | | `filters.nested` | `array` | | | The nested parameters | | **Sorts** | | | | | | `sorts.field` | `string` | X | | The name of the field | | `sorts.direction` | `string` | | `asc` | The direction in which results should be sorted | | **Selects** | | | | | | `selects.field` | `string` | X | | The name of the field | | **Includes** | | | | | | `includes.relation` | `string` | X | | The relation you are querying | | `includes.alias` | `string` | | | The key the relation is returned under, allowing the same relation to be included multiple times | | `includes.other` | `mixed` | | `50` | You can specify all the arguments in the current page such as `filters`, `limit`, `scopes`, etc except include | | **Aggregates** | | | | | | `aggregates.relation` | `string` | X | | The relation you are querying | | `aggregates.type` | `string` | X | | The type of the aggregates you want to use in: `min`, `max`, `avg`, `sum`, `count` and `exists` | | `aggregates.field` | `string` | when type is not `exists` or `count` | `*` | The field you want to execute your aggregate on | | `aggregates.alias` | `string` | | | The alias given for the aggregate | | `aggregates.filters` | `array` | | | You can specify all the arguments for the `filters` section | | **Instructions** | | | | | | `instructions.name` | `string` | X | | The instruction `uriKey` | | `instructions.fields` | `array` | | | The fields provided with the instruction. Use instruction listing to see which ones can be provided. | | **Pagination** | | | | | | `page` | `number` | | 1 | The actual page | | `limit` | `number` | | 50 | The maximum number of results | | **Gates** | | | | | | `gates` | `array` | | [] | The gates you want to get in: `create`, `view`, `update`, `delete`, `restore` and `forceDelete` | ### Response As a response you'll receive the filtered records and related metadata: ```json { "current_page": 1, "data": [ { "id": 1, "name": "Lou West", "gates": { "authorized_to_view": true, "authorized_to_update": true, "authorized_to_delete": true, "authorized_to_restore": true, "authorized_to_force_delete": true } }, { "id": 2, "name": "Bridget Wilderman", "gates": { "authorized_to_view": true, "authorized_to_update": true, "authorized_to_delete": true, "authorized_to_restore": true, "authorized_to_force_delete": true } } ], "from": 1, "last_page": 1, "per_page": 50, "to": 2, "total": 2, "meta": { "gates": { "authorized_to_create": true } } } ``` ### Scopes Scopes corresponds to [Laravel's scopes](https://laravel.com/docs/eloquent#query-scopes){rel=""nofollow""}. You'll need to [specify them in your resource](https://laravel-rest-api.lomkit.com/resources/exposed-data#scopes) first to allow their usage. Use it as the following: ```json // (POST) api/posts/search { "search": { "scopes": [ {"name": "withTrashed", "parameters": [true]} ] } } ``` ### Filters Filters in Laravel Rest Api are the means by which you specify the data you want to retrieve, based on specified fields. All fields specified here must be defined in the [fields method of your resource](https://laravel-rest-api.lomkit.com/resources/exposed-data#fields) first. Use it as the following: ```json // (POST) api/posts/search { "search": { "filters": [ { "field": "id", "operator": ">", "value": 1 } ] } } ``` Field is the column you want to interact with. Operator must be one of these: - \= - != - \> - \>= - < - <= - like - not like - in - not in #### Distant Field You may specify fields related to relationships by specifying the relationship(s): ```json // (POST) api/posts/search { "search": { "filters": [ { "field": "languages.label", "operator": "=", "value": "fr" } ] } } ``` #### Type In many cases you want to condition your multiple filters using an "OR" operation instead of an "AND" one. You can achieve this by specifying the type of the filter: ```json // (POST) api/posts/search { "search": { "filters": [ {"field": "user.id", "operator": "<", "value": 2}, {"field": "id", "operator": ">", "value": 100, "type": "or"} ] } } ``` Here the query will look if the user related id is less than 2 or if the id of the post is greater than 100. #### Pivot Filtering When you deal with a relation that has a pivot such as the `BelongsToMany` relation, you might want to filter in the pivot table. You can achieve this by doing: ```json // (POST) api/posts/search { "search": { "filters": [ { "field": "languages.pivot.boolean", "operator": "=", "value": true } ] } } ``` #### Nested Filtering You may want to prioritize your condition depending on the filter type because an "AND" operation takes advantage of an "OR" operation. You can achieve this by doing: ```json // (POST) api/posts/search { "search": { "filters": [ { "field": "id", "operator": "=", "value": 159, "type": "or" }, { "field": "name", "operator": "like", "value": "%super post%", "type": "or" }, { "nested": [ {"field": "user.id", "operator": "<", "value": 2}, {"field": "id", "operator": ">", "value": 100, "type": "or"} ] } ] } } ``` Here, Laravel Rest Api will look if the id of the posts is 159 OR the name like "%super post%" OR (the user id is less than 2 and the id of the post is greater than 100) ##### Nesting depth By default, filters may only nest one level deep. You can control how deep filters are allowed to nest into each other with the `search.max_nesting_depth` option in your `config/rest.php` file: ```php [ // ... 'search' => [ 'max_nesting_depth' => 1, // The maximum depth filters may nest into each other ] // ... ] ``` A value of `1` allows a single group of nested filters (the historical behavior), while higher values allow groups nested inside other groups. Keep this value modest: deeply nested filters build large, complex boolean queries. ::note Nesting stays disabled when performing a [full text search](https://laravel-rest-api.lomkit.com/#text), regardless of this setting. :: ### Sorts Sorts allows you to specify in which order you want to sort your results. All fields specified here must be defined in the [fields method of your resource](https://laravel-rest-api.lomkit.com/resources/exposed-data#fields) first. ```json // (POST) api/posts/search { "search": { "sorts": [ {"field": "user_id", "direction": "desc"}, {"field": "id", "direction": "asc"} ] } } ``` #### Default sort By default, Laravel Rest Api provides a default sort based on `id` descending. If you want to change this, please see the [configuration](https://laravel-rest-api.lomkit.com/resources/exposed-data#default-sort) ### Selects In some cases, you may want to specify the columns you want to select because it makes your API faster to not query unnecessary data. By default, Laravel Rest Api will query all your `fields`. You cannot query columns that are not present in the `fields` method. You can achieve this by doing: ```json // (POST) api/posts/search { "search": { "selects": [ {"field": "id"}, {"field": "title"} ] } } ``` ### Includes In order to limit the number of queries made to the API, Laravel Rest Api allows you to query distant relationships through a single endpoint. You can achieve this by doing: ```json // (POST) api/posts/search { "search": { "includes": [ { "relation": "posts" } ] } } ``` #### More powerful include In order to make the include operation much more powerful, Laravel Rest Api allows to specify each argument on this page except `include` to avoid caveats. This allows you to do the following: ```json // (POST) api/posts/search { "search": { "includes": [ { "relation": "posts", "filters": [ {"field": "id", "operator": "in", "value": [1, 3]} ], "limit": 2 } ] } } ``` #### Aliasing an include By default a relation is returned under its own name, which means it can only appear once in the response. Provide an `alias` to choose the key it is returned under, and the same relation can be included several times with different constraints: ```json // (POST) api/posts/search { "search": { "includes": [ { "relation": "comments", "alias": "approvedComments", "filters": [ {"field": "approved", "value": true} ] }, { "relation": "comments", "alias": "pendingComments", "filters": [ {"field": "approved", "value": false} ] } ] } } ``` Each post then carries an `approvedComments` and a `pendingComments` key instead of a single `comments` key. Including a relation both natively and under an alias is allowed, and both keys are returned. The alias is used verbatim as the response key, so it is not converted to snake case the way a relation name is. ::warning Because the alias becomes a key of the response, it is validated: - it must look like an identifier, matching `^[A-Za-z_][A-Za-z0-9_]*$`, and be at most 255 characters - it must be unique across the includes of the same level - it may not collide with a field of the resource, one of its relations, or the [gates](https://laravel-rest-api.lomkit.com/digging-deeper/gates) key - it is not allowed on a dotted relation path such as `posts.comments` Anything else returns a `422` response. :: A dotted path has no single level the alias could key into, so alias the relation through a nested `includes` entry instead: ```json // (POST) api/users/search { "search": { "includes": [ { "relation": "posts", "includes": [ { "relation": "comments", "alias": "approvedComments", "filters": [ {"field": "approved", "value": true} ] } ] } ] } } ``` ### Aggregates If you don't know what an aggregate is, please have a look at the [Laravel documentation](https://laravel.com/docs/queries#aggregates){rel=""nofollow""} first. Laravel Rest Api supports all Laravel's aggregates, here is a quick look at how to specify your aggregate: ```json // (POST) api/posts/search { "search": { "aggregates": [ { "relation": "comments", "type": "avg", "field": "stars" } ] } } ``` Here we are getting the average stars for the comments linked to the posts. The type could be one of these: - min - max - avg - sum - count - exists ::warning For the `exists` and `count` operation you must not specify the field since these aggregates don't base themselves on a column. :: #### Aggregates aliases You may optionally define an alias for your aggregate using the `alias` method. This allows you to customize the name of the aggregate column in the response, instead of relying on Laravel's default naming. ```json // (POST) api/posts/search { "search": { "aggregates": [ { "relation": "comments", "type": "avg", "field": "stars", "alias": "average_stars" } ] } } ``` #### Aggregates filtering For more complex aggregates, Laravel Rest Api allows you to specify filters. These filters are the same as [the basic ones](https://laravel-rest-api.lomkit.com/endpoints/search#filters). ```json // (POST) api/posts/search { "search": { "aggregates": [ { "relation": "comments", "type": "avg", "field": "stars", "filters": [ {"field": "approved", "value": true} ] } ] } } ``` ### Instructions Instructions is a way for api builder to define strong query operations. They'll be defined in the [resource details](https://laravel-rest-api.lomkit.com/endpoints/details) when exposed. Here is how to specify an instruction: ```json // (POST) api/posts/search { "search": { "instructions": [ { "name": "odd-even-id" } ] } } ``` Here we are getting the posts that have an even id. The specified name is the `uriKey` of the instruction. #### Instructions fields For more complex instructions, Laravel Rest Api allows you to specify fields. You can have full access to the fields and validation in the [resource details](https://laravel-rest-api.lomkit.com/endpoints/details). ```json // (POST) api/posts/search { "search": { "instructions": [ { "name": "odd-even-id", "fields": [ {"field": "type", "value": "odd"} ] } ] } } ``` ### Pagination You might either want to limit the data you are querying or to specify a page to load data by sequence. All limits specified here must be defined in the [limits method of your resource](https://laravel-rest-api.lomkit.com/resources/exposed-data#limits) first. You can achieve this by doing: ```json // (POST) api/posts/search { "search": { "page": 2, "limit": 10 } } ``` ### Gates If you want to retrieve permissions on the models you are getting, you need to specify the gates you want to get. Use this with care this can slow down you application queries. Be aware before that Gates needs to be enabled in order to be retrieved. Have a look at [Automatic Gates](https://laravel-rest-api.lomkit.com/digging-deeper/gates) if you need detail. The gates could be one of these: - create - view - update - delete - restore - forceDelete ```json // (POST) api/posts/search { "search": { "gates": ["create", "view"] } } ``` ### Text If you want to specify a full text search you'll need to use the text argument. First be sure you [activated full text search for the resource](https://laravel-rest-api.lomkit.com/digging-deeper/full-text-search). ```json // (POST) api/posts/search { "search": { "text": { "value": "my text search" } } } ``` ::note When `text` is provided but its `value` is empty or `null`, the endpoint returns an empty paginated result immediately, without querying the search engine. :: #### Impacts When using full text search, some restrictions happens to the query: | **Key** | **Changes** | | ------------------- | --------------------------------------------------------------- | | **Scopes** | | | `scopes.name` | Not allowed | | `scopes.parameters` | Not allowed | | **Filters** | | | `filters.field` | take scout fields instead of fields in resource detail | | `filters.operator` | `=`/`in`/`not in` | | `filters.type` | Not allowed | | `filters.nested` | Not allowed | | **Sorts** | | | `sorts.field` | uses Scout fields instead of fields defined in resource details | #### Text search with trashed models Because the Laravel Scout Builder differs from Eloquent, `withTrashed` is not a scope when performing text search. :br Instead, control trashed handling via the `text.trashed` option: - "with" → include both non-trashed and trashed records - "only" → include only trashed records - omitted → exclude trashed records (default) ```json // (POST) api/posts/search { "search": { "text": { "value": "my text search", "trashed": "with" } } } ``` # Mutate ## Usage Here is a quick look at what you can do: ```json // (POST) api/users/mutate { "mutate": [ { "operation": "create", "attributes": {"name": "test", "email": "uniq4@uniq.fr", "password": "hidden"}, "relations": { "star": { "operation": "create", "attributes": {"number": 2} } } }, { "operation": "create", "attributes": {"name": "test2", "email": "uniq5@uniq.fr", "password": "hidden"}, "relations": { "star": { "operation": "attach", "key": [1, 2] } } }, { "operation": "update", "key": 2, "attributes": {}, "relations": { "star": { "operation": "detach", "key": 1 } } }, { "operation": "create", "attributes": {"name": "test2", "email": "uniq6@uniq.fr", "password": "hidden"}, "relations": { "posts": [ { "operation": "sync", "without_detaching": true, "key": 1, "attributes": {"number": 4}, "pivot": {"color": "#2271B3"} }, { "operation": "toggle", "key": 2, "attributes": {"number": 4}, "pivot": {"color": "#C51D34"} } ] } }, { "operation": "update", "key": 1, "attributes": {"name": "new name :)"}, "relations": { "posts": [ { "operation": "create", "attributes": {}, "relations": { "star": { "operation": "create", "attributes": {"number": 2} } } }, { "operation": "detach", "key": 2 } ] } } ] } ``` Keep in mind that all fields specified here must be defined in the [fields method of your resource](https://laravel-rest-api.lomkit.com/resources/exposed-data#fields) first. ### Specifications | **Key** | **Type** | **Required** | **Default** | **Description** | | ------------------- | --------- | ------------------------------------------------------------------ | ----------- | ----------------------------------------------------- | | `operation` | `string` | x | | The type of operation you want to achieve | | `attributes` | `string` | x | | The attributes for the creation | | `key` | `mixed` | When operation is `attach`, `detach`, `update`, `toggle` or `sync` | | The model identifier or an array of model identifiers | | `without_detaching` | `boolean` | | false | Specify if sync should detach | | `relations` | `array` | | | The model relations you want to access | ### Response As a response you'll receive the keys of the models that were impacted, grouped by `created` or `updated`. ```json { "created": [ 72979 ], "updated": [] } ``` ### Create To indicate that you want to create a resource you must use the "create" operation: ```json // (POST) api/users/mutate { "mutate": [ { "operation": "create", "attributes": {"name": "Gautier Deleglise", "email": "gautier@mail.com", "password": "password"} }, { "operation": "create", "attributes": {"name": "My other user", "email": "him@mail.com", "password": "password"} } ] } ``` ### Update To indicate that you want to update a resource you must use the "update" operation. This also means you have to specify the entry you want to modify with the "key" key. ```json // (POST) api/users/mutate { "mutate": [ { "operation": "update", "key": 1, "attributes": {"name": "new name"} }, { "operation": "update", "key": [2, 3], "attributes": {"name": "other name"} } ] } ``` ### Relations In order to make the Api more flexible, Laravel Rest Api allows the user to modify / create / attach / detach distant relations with a single call. You'll need to specify the `relations` key as follows: #### Create ```json // (POST) api/users/mutate { "mutate": [ { "operation": "update", "key": 1, "relations": { "posts": [ { "operation": "create", "attributes": { "title": "My Post" }, "relations": { "star": { "operation": "create", "attributes": {"number": 2} } } }, ] } } ] } ``` #### Update ```json // (POST) api/users/mutate { "mutate": [ { "operation": "update", "key": 1, "relations": { "posts": [ { "operation": "update", "key": 1, "attributes": { "title": "My Post" }, "relations": { "star": { "operation": "update", "key": [1, 2], "attributes": {"number": 2} } } }, ] } } ] } ``` #### Attach For the `attach` operation you only need to specify the related key. ```json // (POST) api/users/mutate { "mutate": [ { "operation": "update", "key": 1, "relations": { "posts": [ { "operation": "attach", "key": 5, "relations": { "star": { "operation": "attach", "key": 5 } } } ] } } ] } ``` #### Detach For the `detach` operation you only need to specify the related key. ```json // (POST) api/users/mutate { "mutate": [ { "operation": "update", "key": 1, "relations": { "posts": [ { "operation": "detach", "key": 1, "relations": { "star": { "operation": "detach", "key": 1 } } } ] } } ] } ``` #### Sync For the `sync` operation you only need to specify the related key. You also can specify if you want the sync operation to detach already related records. ```json // (POST) api/users/mutate { "mutate": [ { "operation": "update", "key": 1, "relations": { "posts": [ { "operation": "sync", "without_detaching": true, "key": 1, "relations": { "star": { "operation": "detach", "key": 1 } } } ] } } ] } ``` You can pass an empty array as parameter if you want to detach all records related to the parent model. Example to detach all related records: ```json // (POST) api/users/mutate { "mutate": [ { "operation": "update", "key": 1, "relations": { "posts": [ { "operation": "sync", "key": [] } ] } } ] } ``` #### Toggle For the `toggle` operation you only need to specify the related key. ```json // (POST) api/users/mutate { "mutate": [ { "operation": "update", "key": 1, "relations": { "posts": [ { "operation": "toggle", "key": 1, "relations": { "star": { "operation": "detach", "key": 1 } } } ] } } ] } ``` #### Morph to relation Since you can only specify one resource in Laravel Rest Api, your relation always point to a model. You don't need to specify it. ```json // (POST) api/tags/mutate { "mutate": [ { "operation": "update", "key": 1, "relations": { "taggable": [ { "operation": "attach", "key": 1 } ] } } ] } ``` The type you specify is the `Resource`. Laravel Rest Api will automatically take the model linked to the resource to create your entry. This allows you to have multiple resources with the same model. #### Pivot creation In some cases, when you are dealing with a relation that has a pivot, you might want to fill it. You can do this by specifying the "pivot" key: ```json // (POST) api/users/mutate { "mutate": [ { "operation": "update", "key": 1, "relations": { "posts": [ { "operation": "create", "attributes": { "title": "My super post" }, "pivot": { "number": 20 } } ] } } ] } ``` ::warning The relation pivot fields must be [declared in your resource](https://laravel-rest-api.lomkit.com/resources/relationships#pivot-fields). :: # Actions ## How to use it When you are ready to perform an action, you can use the `operate` method by making a POST call: ```json // (POST) api/users/actions/send-welcome-notification { "fields": [ { "name": "expires_at", "value": "2023-04-29" } ] } ``` You may specify the fields provided by the user using the `fields` argument. You can have full access to the fields and validation in the [resource details](https://laravel-rest-api.lomkit.com/endpoints/details). `send-welcome-notification` is the uriKey of the action provided in the [resource details](https://laravel-rest-api.lomkit.com/endpoints/details). As a response, you'll receive the number of models that were impacted: ```json { "data": { "impacted": 150 } } ``` ### Complex filtering In most cases you'll want to specify which models will be dynamically impacted by the action. The action endpoint provides full support for the search operation. If you are not familiar with the search method, have a quick look at [the documentation](https://laravel-rest-api.lomkit.com/endpoints/search). Specify your arguments in the `search` argument: ```json // (POST) api/users/actions/send-welcome-notification { "fields": [ { "name": "expires_at", "value": "2023-04-29" } ], "search": { "filters": [ { "field": "has_received_welcome_notification", "value": false } ] } } ``` ### Targeting models explicitly When you already know which models the action applies to, an action declared as [targeted](https://laravel-rest-api.lomkit.com/digging-deeper/actions#targeted-actions) takes the list of ids in the `resources` argument instead of a search: ```json // (POST) api/users/actions/deactivate-users { "resources": [1, 5, 9], "fields": [ { "name": "reason", "value": "spam" } ] } ``` `resources` is required on a targeted action, and a search is prohibited on it, so the action can never impact every model because the caller forgot to narrow it down. Omitting `resources`, passing an empty array, naming an id that does not exist, or passing more ids than the action accepts all return a `422` response. Ids the current user is not allowed to see are silently skipped, since the resource's [search query](https://laravel-rest-api.lomkit.com/resources/interactions#search-query) still constrains the targeted models. ## Response As a response you'll receive the number of models that were impacted. ```json { "data": { "impacted": 2 } } ``` ## Targeting states An action has exactly one of three targeting states, which decides what its request body may carry. The [details endpoint](https://laravel-rest-api.lomkit.com/endpoints/details) exposes the state through the `standalone` and `targeted` keys of each action. | State | `search` | `resources` | Models impacted | | ----------------- | ---------- | ----------- | --------------------------------------------------------------------------------- | | Standalone | prohibited | prohibited | none, the action receives an empty collection | | Classic (default) | optional | prohibited | whatever the search resolves, which is **every model** when the search is omitted | | Targeted | prohibited | required | exactly the ids named, minus those the user may not see | ## Standalone actions A standalone action is an action that does not require any models to run. You may know if an action is standalone in the detail endpoint with the `standalone` key. Standalone actions works the same, you just can't specify a search operation. # Delete ## Usage Call the specified endpoint and add in the body the specified entries you want to destroy: ```json // (DELETE) my-api.com/api/users { "resources": [5,6] } ``` ## Response As a response you'll receive the deleted records. ```json { "data": [ { "id": 1, "name": "Evan Sauer" } ], "meta": { "gates": { "authorized_to_create": true } } } ``` ## Soft Deletes If you deal with softDeletes you should specify it first when registering the controller: ```php [api.php] use \Lomkit\Rest\Facades\Rest; Rest::resource('users', \App\Rest\Resources\UsersController::class)->withSoftDeletes() ``` ### Restore Call the specified endpoint and add in the body the specified entries you want to restore: ```json // (POST) my-api.com/api/users/restore { "resources": [5,6] } ``` ### Force Delete Call the specified endpoint and add in the body the specified entries you want to force delete: ```json // (DELETE) my-api.com/api/users/force { "resources": [5,6] } ``` # Basics ## Defining Resources By default, Rest resources are stored in the app/Rest/Resources directory of your application. You may generate a new resource using the rest\:resource Artisan command: ```bash php artisan rest:resource UserResource ``` You might now define the model property. This property tells Laravel Rest Api which Eloquent model the resource corresponds to: ```php public static $model = \App\Models\User::class; ``` Freshly created Rest resources only contain an ID exposed field. Don't worry, we'll add more fields to our resource soon. ## Registering Resources By default, resources are not automatically registered to let you take advantage of the logic. First, you need to declare a Controller: ```bash php artisan rest:controller UsersController ``` Then, specify the resource in your controller: ```php class UsersController extends Controller { /** * The resource the controller corresponds to. * * @var class-string<\Lomkit\Rest\Http\Resource> */ public static $resource = \App\Rest\Resources\UserResource::class; } ``` Since the basic usage of these will be on the api side, you can declare your controller in your `api.php` file: ```php [api.php] use \Lomkit\Rest\Facades\Rest; Rest::resource('users', \App\Rest\Controllers\UsersController::class) ``` Once your resources are registered, you can verify that by using `php artisan route:list`: | Method | URI | Name | | ------ | ---------------------------- | ------------------- | | GET | `api/users` | `api.users.details` | | POST | `api/users/search` | `api.users.search` | | POST | `api/users/actions/{action}` | `api.users.operate` | | POST | `api/users/mutate` | `api.users.mutate` | | DELETE | `api/users` | `api.users.destroy` | ## Soft Deletes If you want to expose soft delete routes for one of your resources, you can achieve this by using the \`withSoftDeletes' method while registering the resource: ```php [api.php] use \Lomkit\Rest\Facades\Rest; Rest::resource('users', \App\Rest\Controllers\UsersController::class)->withSoftDeletes() ``` You now have two more routes registered: | Method | URI | Name | | ------ | ------------------- | ------------------- | | POST | `api/users/restore` | `api.users.restore` | | DELETE | `api/users/force` | `api.users.force` | If you don't want to expose all soft deletes routes you can specify so: ```php [api.php] use \Lomkit\Rest\Facades\Rest; Rest::resource('users', \App\Rest\Controllers\UsersController::class)->withSoftDeletes(['forceDelete', 'restore']) ``` # Exposed Data ## Fields Each Rest resource contains a `fields` method. This method returns an array of strings specifying the columns you want to expose. This fields are accessible in terms of selecting and mutating. To add a field to a resource, you may simply add it to the resource's `fields` method. ```php public function fields(\Lomkit\Rest\Http\Requests\RestRequest $request) { return [ 'id', 'name', ]; } ``` ### Conditional field exposing You might want to choose the fields you are exposing depending on the user. You can achieve this by conditioning the `fields` method: ```php public function fields(\Lomkit\Rest\Http\Requests\RestRequest $request) { $fields = [ 'id', 'name' ]; if ($request->user()->isAdministrator()) { array_push($fields, 'password'); } return $fields; } ``` ## Sorts The fields allowed are the one you define in the `fields` method ### Default sort By default, Rest Api sorts by `id` descending, if you want to change this, extend the `defaultOrderBy` method. ```php /** * Return the default ordering for resource queries. * * @param RestRequest $request * * @return array */ public function defaultOrderBy(RestRequest $request): array { return [ 'id' => 'desc', ]; } ``` ## Scopes Each Rest resource contains a `scopes` method. This method returns an array of strings specifying the scopes you want to expose. To add a scope to a resource, you may simply add it to the resource's `scopes` method. ```php public function scopes(\Lomkit\Rest\Http\Requests\RestRequest $request) { return [ 'withTrashed' ]; } ``` ### Conditional scope exposing You might want to choose the scopes you are exposing depending on the user. You can achieve this by conditioning the `scopes` method: ```php public function scopes(\Lomkit\Rest\Http\Requests\RestRequest $request) { $scopes = [ 'withTrashed' ]; if ($request->user()->isAdministrator()) { array_push($scopes, 'numbered'); } return $scopes; } ``` ## Limits Each Rest resource contains a `limits` method. This method returns an array of numbers specifying the limits you want to allow. To add a limit to a resource, you may simply add it to the resource's `limits` method. ```php public function limits(\Lomkit\Rest\Http\Requests\RestRequest $request) { return [ 10, 25, 50 ]; } ``` If no limit is specified in the request, Rest Api paginates with 50 items. You can also set a default limit by specifying it in your resource: ```php public int $defaultLimit = 20; ``` ### Conditional limits specifying You might want to choose the limits you are exposing depending on the user. You can achieve this by conditioning the `limits` method: ```php public function limits(\Lomkit\Rest\Http\Requests\RestRequest $request) { $limits = [ 10, 25, 50 ]; if ($request->user()->isAdministrator()) { array_push($limits, 1000); } return $limits; } ``` # Relationships Be aware that all relations must be declared in the resource even though they'll be linked to Laravel relationships. You can't link models without using a resource. This allows Laravel Rest Api to take full advantage of Rest Resources to make your requests powerful and secure. Your relationships should all be declared in the "relations" method of your resource: ```php class ModelResource extends Resource { public function relations(RestRequest $request) { return [ HasOne::make('hasOneRelation', HasOneResource::class), BelongsTo::make('belongsToRelation', BelongsToResource::class), HasMany::make('hasManyRelation', HasManyResource::class), BelongsToMany::make('belongsToManyRelation', BelongsToManyResource::class) ->withPivotFields(['created_at']), ]; } } ``` The first argument of the relation is the name of the relationship in your model, the second one is the linked resource(s). ## Has One The `HasOne` relation corresponds to a `hasOne` Eloquent relationship. For example, let's assume a User model hasOne Address model. We may add the relationship to our User Rest resource like so: ```php use Lomkit\Rest\Relations\HasOne; HasOne::make('address', AddressResource::class), ``` ### Has One Of Many The `HasOneOfMany` relation corresponds to a `hasOne ofMany` Eloquent relationship. For example, let's assume a Restaurant model hasOneOfMany Order model. We may add the relationship to our Restaurant Rest resource like so: ```php use Lomkit\Rest\Relations\HasOneOfMany; HasOneOfMany::make('order', OrderResource::class), ``` ## Has Many The `HasMany` relation corresponds to a `hasMany` Eloquent relationship. For example, let's assume a User model hasMany Post models. We may add the relationship to our User Rest resource like so: ```php use Lomkit\Rest\Relations\HasMany; HasMany::make('posts', PostResource::class), ``` ## Has One Through The `HasOneThrough` relation corresponds to a `hasOneThrough` Eloquent relationship. For example, let's assume a User model hasOne car through a Company model. We may add the relationship to our User Rest resource like so: ```php use Lomkit\Rest\Relations\HasOneThrough; HasOneThrough::make('car', CarResource::class), ``` ::warning You can't mutate models using "HasOneThrough" relationships, please use a double "HasOne" relationship if you want to mutate the distant entry :: ## Has Many Through The `HasManyThrough` relation corresponds to a `hasManyThrough` Eloquent relationship. For example, let's assume a User model hasMany stars through a Company model. We may add the relationship to our User Rest resource like so: ```php use Lomkit\Rest\Relations\HasManyThrough; HasManyThrough::make('star', StarResource::class), ``` ::warning You can't mutate models using "HasManyThrough" relationships, please use a "HasOne" relationship followed by a "HasMany" relationship if you want to mutate the distant entry :: ## Belongs To The `BelongsTo` relation corresponds to a `belongsTo` Eloquent relationship. For example, let's assume a User model belongsTo a Company model. We may add the relationship to our User Rest resource like so: ```php use Lomkit\Rest\Relations\BelongsTo; BelongsTo::make('company', CompanyResource::class), ``` ## Belongs To Many The `BelongsToMany` relation corresponds to a `belongsToMany` Eloquent relationship. For example, let's assume a User model belongsToMany Role models. We may add the relationship to our User Rest resource like so: ```php use Lomkit\Rest\Relations\BelongsToMany; BelongsToMany::make('roles', RoleResource::class), ``` ### Pivot fields If you want to specify pivot fields, you can achieve this by using: ```php use Lomkit\Rest\Relations\BelongsToMany; BelongsToMany::make('roles', RoleResource::class)->withPivotFields(['created_at']), ``` ::warning Don't forget to specify those pivot fields on your model relationship. :::collapsible{name="relations"} ```php public function roles() { return $this->belongsToMany(Role::class) ->withPivot('created_at', 'updated_at'); } ``` ::: :: ## MorphOne The `MorphOne` relation corresponds to a `morphOne` Eloquent relationship. For example, let's assume a User model MorphOne Address model. We may add the relationship to our User Rest resource like so: ```php use Lomkit\Rest\Relations\MorphOne; MorphOne::make('address', AddressResource::class), ``` ### Morph One Of Many The `MorphOneOfMany` relation corresponds to a `morphOneOfMany` Eloquent relationship. For example, let's assume a User model MorphOneOfMany color models. We may add the relationship to our User Rest resource like so: ```php use Lomkit\Rest\Relations\MorphOneOfMany; MorphOneOfMany::make('color', ColorResource::class), ``` ## Morph Many The `MorphMany` relation corresponds to a `morphMany` Eloquent relationship. For example, let's assume a User model MorphMany Color models. We may add the relationship to our User Rest resource like so: ```php use Lomkit\Rest\Relations\MorphMany; MorphMany::make('colors', ColorResource::class), ``` ## Morph To The `MorphTo` relation corresponds to a `morphTo` Eloquent relationship. For example, let's assume a Comment model MorphTo a Post or an Video model. We may add the relationship to our Comment Rest resource like so: ```php use Lomkit\Rest\Relations\MorphTo; MorphTo::make('commentable', PostResource::class), ``` ::warning You must specify multiple relations for each morph to resource possible. Please consider declaring multiple relations in your model. :::collapsible{name="relations"} ```php // You can't declare this relation in Laravel Rest Api public function commentable(): MorphTo { return $this->morphTo(); } public function post(): MorphTo { return $this->morphTo('post', 'commentable_type', 'commentable_id')->whereHas('comment', function (Builder $query) { $query->where('commentable_type', Post::class); }); } public function video(): MorphTo { return $this->morphTo('video', 'commentable_type', 'commentable_id')->whereHas('comment', function (Builder $query) { $query->where('commentable_type', Video::class); }); } ``` ::: :: ## Morph To Many The `MorphToMany` relation corresponds to a `morphToMany` Eloquent relationship. For example, let's assume a Post model MorphToMany Tag models. We may add the relationship to our Post Rest resource like so: ```php use Lomkit\Rest\Relations\MorphToMany; MorphToMany::make('taggable', TagResource::class), ``` ### Defining the inverse of the relationship As Laravel allows, you can use the "morphedByMany" relationship to define the inverse. The `morphedByMany` relation allows to define on the `TagResource` the relation to the `PostResource` as follows: ```php use Lomkit\Rest\Relations\MorphedByMany; MorphedByMany::make('taggable', PostResource::class), ``` ### Pivot fields If you want to specify pivot fields, you can achieve this by using: ```php use Lomkit\Rest\Relations\MorphToMany; use Lomkit\Rest\Relations\MorphedByMany; MorphToMany::make('taggable', TagResource::class)->withPivotFields(['created_at']), MorphedByMany::make('taggable', PostResource::class)->withPivotFields(['created_at']), ``` ::warning Don't forget to specify those pivot fields on your model relationship. :::collapsible{name="relations"} ```php $this->morphToMany(Tag::class, 'taggable')->withPivot('created_at', 'updated_at'); $this->morphedByMany(Post::class, 'taggable')->withPivot('created_at', 'updated_at'); ``` ::: :: ## Constrained Relations In some cases you might want to constrain the relation on mutation, Laravel Rest Api offers you constraints such has `requiredOnCreation`, `prohibitedOnCreation`, `requiredOnUpdate` and `prohibitedOnUpdate` You can apply a constraint on a relation by using: ```php use Lomkit\Rest\Relations; BelongsTo::make('company', CompanyResource::class) ->requiredOnCreation(), ``` You can also specify a closure to condition this: ```php use Lomkit\Rest\Relations; BelongsTo::make('company', CompanyResource::class) ->requiredOnCreation(function(\Lomkit\Rest\Http\Requests\RestRequest $request) { return true; }), ``` # Validations Keep in mind that if you want your API to provide the best user experience to your consumers you always want to have 0 direct database errors. For example, if one of your fields is not nullable, you'll want to make it required in these rules. You can specify all [Laravel rules](https://laravel.com/docs/validation#available-validation-rules){rel=""nofollow""} you want. ## Rules You may use the `rules` method from your resource to specify the rules for your attributes. ```php [UserResource.php] public function rules(\Lomkit\Rest\Http\Requests\RestRequest $request) { return [ 'name' => 'required' ]; } ``` ### Create rules You may use the `createRules` method from your resource to specify the creating rules. ```php [UserResource.php] public function createRules(\Lomkit\Rest\Http\Requests\RestRequest $request) { return [ 'name' => 'required' ]; } ``` ### Update rules You may use the `updateRules` method from your resource to specify the updating rules. ```php [UserResource.php] public function updateRules(\Lomkit\Rest\Http\Requests\RestRequest $request) { return [ 'name' => 'required' ]; } ``` ### Relation pivot rules In some particular cases, you might want to validate the pivot fields provided by the incoming request. You can achieve this with the `withPivotRules` method when declaring your relationship: ```php use Lomkit\Rest\Relations\BelongsToMany; BelongsToMany::make('roles', RoleResource::class) ->withPivotFields(['created_at']) ->withPivotRules([ 'created_at' => ['required', 'date'] ]), ``` # Interactions ## Queries Here you can modify the queries before they are run by Laravel Rest Api. ::warning You may only want to modify the search query since all other queries use Policies to verify if the user is allowed to access/modify the data. :: ::note The constraints you add in `searchQuery`, `destroyQuery`, `restoreQuery` and `forceDeleteQuery` are applied as a nested group, so an `or` inside them cannot leak into the constraints Laravel Rest Api appends afterwards. A query written as `$query->where('user_id', $request->user()->id)->orWhere('public', true)` therefore behaves as `(user_id = ? or public = ?) and ...`, and keeps constraining when the endpoint narrows the results down further with the caller's filters or the ids it named. :: ### Search Query ```php [UserResource.php] /** * Build a query for searching resource. * * @param \Lomkit\Rest\Http\Requests\RestRequest $request * @param \Illuminate\Contracts\Database\Eloquent\Builder $query * @return \Illuminate\Contracts\Database\Eloquent\Builder */ public function searchQuery(RestRequest $request, \Illuminate\Contracts\Database\Eloquent\Builder $query) { return $query; } ``` ### Mutate Query ```php [UserResource.php] /** * Build a query for mutating resource. * * @param \Lomkit\Rest\Http\Requests\RestRequest $request * @param \Illuminate\Contracts\Database\Eloquent\Builder $query * @return \Illuminate\Contracts\Database\Eloquent\Builder */ public function mutateQuery(RestRequest $request, \Illuminate\Contracts\Database\Eloquent\Builder $query) { return $query; } ``` ### Destroy Query ```php [UserResource.php] /** * Build a "destroy" query for the given resource. * * @param \Lomkit\Rest\Http\Requests\RestRequest $request * @param \Illuminate\Contracts\Database\Eloquent\Builder $query * @return \Illuminate\Contracts\Database\Eloquent\Builder */ public function destroyQuery(RestRequest $request, \Illuminate\Contracts\Database\Eloquent\Builder $query) { return $query; } ``` ### Restore Query ```php [UserResource.php] /** * Build a "restore" query for the given resource. * * @param \Lomkit\Rest\Http\Requests\RestRequest $request * @param \Illuminate\Contracts\Database\Eloquent\Builder $query * @return \Illuminate\Contracts\Database\Eloquent\Builder */ public function restoreQuery(RestRequest $request, \Illuminate\Contracts\Database\Eloquent\Builder $query) { return $query; } ``` ### Force Delete Query ```php [UserResource.php] /** * Build a "forceDelete" query for the given resource. * * @param \Lomkit\Rest\Http\Requests\RestRequest $request * @param \Illuminate\Contracts\Database\Eloquent\Builder $query * @return \Illuminate\Contracts\Database\Eloquent\Builder */ public function forceDeleteQuery(RestRequest $request, \Illuminate\Contracts\Database\Eloquent\Builder $query) { return $query; } ``` ## Operations Laravel Rest API exposes endpoints for performing actions, giving you the freedom to customize them to meet your specific requirements and alter the default behavior of these actions: ### Delete ```php [UserResource.php] /** * Build a "delete" query for the given resource. * * @param \Lomkit\Rest\Http\Requests\RestRequest $request * @param \Illuminate\Database\Eloquent\Model $query * @return void */ public function performDelete(RestRequest $request, Model $model) { $model->delete(); } ``` ### Restore ```php [UserResource.php] /** * Build a "restore" query for the given resource. * * @param \Lomkit\Rest\Http\Requests\RestRequest $request * @param \Illuminate\Database\Eloquent\Model $query * @return void */ public function performRestore(RestRequest $request, Model $model) { $model->restore(); } ``` ### Force Delete ```php [UserResource.php] /** * Build a "forceDelete" query for the given resource. * * @param \Lomkit\Rest\Http\Requests\RestRequest $request * @param \Illuminate\Database\Eloquent\Model $query * @return void */ public function performForceDelete(RestRequest $request, Model $model) { $model->forceDelete(); } ``` # Actions Your resource automatically exposes the actions you have defined on it. Configure them, register them and you are done ! ## Overview Actions can be generated using the `rest:action` Artisan command. By default, every action is placed in your `App\Rest\Actions` directory. ```bash php artisan rest:action SendWelcomeNotificationAction ``` You are now ready to configure your action: ```php namespace App\Rest\Actions; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Collection; use Lomkit\Rest\Actions\Action; use Lomkit\Rest\Http\Requests\RestRequest; class SendWelcomeNotificationAction extends Action { /** * Perform the action on the given models. * * @param array $fields * @param \Illuminate\Support\Collection $models * @return mixed */ public function handle(array $fields, \Illuminate\Support\Collection $models) { foreach ($models as $model) { $model->notify(new \App\Notifications\SendWelcomeNotification($fields['expires_at'])) ->delay($this->resource instanceof \App\Rest\UserResource ? 50 : 0); } } /** * The action fields. * * @param \Lomkit\Rest\Http\Requests\RestRequest $request * @return array */ public function fields(\Lomkit\Rest\Http\Requests\RestRequest $request) { return [ 'expires_at' => [ 'required', 'date' ] ]; } } ``` In the `handle` method you receive a Collection of models. The models correspond to the resource the action has been registered on. You are free to do whatever you want in this `handle` method: update records, send jobs, etc... You can access the resource that launched the action by using `$this->resource` at any moment. ## Register an action To register an action, you simply need to specify it in your resource: ```php use App\Rest\Actions\SendWelcomeNotificationAction; class UserResource extends Resource { /** * The actions that should be linked * @param RestRequest $request * @return array */ public function actions(RestRequest $request): array { return [ SendWelcomeNotificationAction::make() ]; } } ``` ### Authorizations In case you don't want to expose your actions to all users, you simply need to condition this method: ```php use App\Rest\Actions\SendWelcomeNotificationAction; class UserResource extends Resource { /** * The actions that should be linked * @param RestRequest $request * @return array */ public function actions(RestRequest $request): array { $actions = []; if ($request->user()->isAdministrator()) { array_push($actions, SendWelcomeNotificationAction::make()) } return $actions; } } ``` ## Fields Sometimes, you want to directly collect data from your frontend users. This is what fields are made for. You need to define them in your action first: ```php namespace App\Rest\Actions; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Collection; use Lomkit\Rest\Actions\Action; use Lomkit\Rest\Http\Requests\RestRequest; class SendWelcomeNotificationAction extends Action { /** * The action fields. * * @param \Lomkit\Rest\Http\Requests\RestRequest $request * @return array */ public function fields(\Lomkit\Rest\Http\Requests\RestRequest $request) { return [ 'expires_at' => [ 'required', 'date' ] ]; } } ``` In the returned array, the key should correspond to the field name provided in the call, while the value should represent the desired set of validations for that field. Laravel Rest API automatically validates all fields based on your defined validation rules. You get those fields in the `$field` variable in your `handle` method. ::note Field rules are evaluated as a standard Laravel validation. This means presence and cross-field rules such as `required`, `required_if` and `present` fire **even when the field is absent** from the request — omitting a `required` field returns a `422` response. Validation errors are keyed by field name under `fields.{name}` (for example `fields.expires_at`), while an unauthorized field name is reported positionally as `fields.{index}.name`. :: ## Meta Because your actions are exposed to frontend users, they do receive certain information about them, such as `uriKey`, `name` and `fields`. However, you are also welcome to provide your own data. You can achieve this by invoking `withMeta` within your action's constructor: ```php class SendWelcomeNotificationAction extends Action { public function __construct() { $this->withMeta([ 'color' => '#FFFFFF' ]); } } ``` Alternatively, you can call it during the action registration if you wish to define distinct meta information based on the resource: ```php use App\Rest\Actions\SendWelcomeNotificationAction; class UserResource extends Resource { /** * The actions that should be linked * @param RestRequest $request * @return array */ public function actions(RestRequest $request): array { return [ SendWelcomeNotificationAction::make() ->withMeta(['color' => '#FFFFFF']) ]; } } ``` ## Queued actions If your actions require a significant amount of processing time, you might want to queue them. To instruct Laravel Rest API to queue your actions dynamically, you should use the `ShouldQueue` interface: ```php namespace App\Rest\Actions; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Collection; use Lomkit\Rest\Actions\Action; use Illuminate\Contracts\Queue\ShouldQueue; use Lomkit\Rest\Http\Requests\RestRequest; class SendWelcomeNotificationAction extends Action implements ShouldQueue { // ... } ``` Laravel Rest Api will chunk your results and create a job for each chunk. By default, the chunk size is set to `100`, if you wish to change this, declare the `chunkCount` property on your model: ```php namespace App\Rest\Actions; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Collection; use Lomkit\Rest\Actions\Action; use Illuminate\Contracts\Queue\ShouldQueue; use Lomkit\Rest\Http\Requests\RestRequest; class SendWelcomeNotificationAction extends Action implements ShouldQueue { /** * The number of models that should be included in each chunk. * * @var int */ public $chunkCount = 100; } ``` ::note Chunking is also done when you are not using queue, the `handle` method will be directly called for each chunk. :: ### Customizing the queue and connection You may customize the queue connection and queue name that the action is queued on by setting the `$connection` and `$queue` properties on your class: ```php namespace App\Rest\Actions; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Collection; use Lomkit\Rest\Actions\Action; use Illuminate\Contracts\Queue\ShouldQueue; use Lomkit\Rest\Http\Requests\RestRequest; class SendWelcomeNotificationAction extends Action implements ShouldQueue { /** * The name of the connection the job should be sent to. * * @var string|null */ public $connection; /** * The name of the queue the job should be sent to. * * @var string|null */ public $queue; } ``` ### Batchable Actions You may instruct Laravel Rest Api that an action is batchable by using the `BatchableAction` and `ShouldQueue` interfaces on your class: ```php namespace App\Rest\Actions; use Illuminate\Contracts\Queue\ShouldQueue; use Lomkit\Rest\Actions\Action; use Lomkit\Rest\Contracts\BatchableAction; class SendWelcomeNotificationAction extends Action implements ShouldQueue, BatchableAction { /** * Register callbacks on the pending batch. * * @param array $fields * @param \Illuminate\Bus\PendingBatch $batch * @return void */ public function withBatch(array $fields, PendingBatch $batch) { $batch->then(function (Batch $batch) { // ... })->catch(function (Batch $batch, Throwable $e) { // ... })->finally(function (Batch $batch) { // ... }); } } ``` The new `withBatch` method allows you to register callbacks on your pending batch. This means that these callbacks will be called depending on the batch status. For example, if you want to send a notification when jobs are successfully completed, you should use the `then` method. This method will trigger once at the end of all jobs triggered depending on your chunk size. If you are not familiar with batches please see the [Laravel Documentation.](https://laravel.com/docs/queues#job-batching){rel=""nofollow""} ## Standalone actions You may have sometimes an action that does not require any models to run. If you are in this situation, you might want to register the action as `standalone` by invoking the `standlone` method when registering the action. These actions will always receive an empty collection of models in the `handle` method. ```php use App\Rest\Actions\ResetPasswordAction; class UserResource extends Resource { /** * The actions that should be linked * @param RestRequest $request * @return array */ public function actions(RestRequest $request): array { return [ ResetPasswordAction::make()->standalone() ]; } } ``` ## Targeted actions By default an action resolves the models it applies to from the search given by the caller, which means that omitting the search runs the action against **every** model of the resource. When this is not an acceptable outcome, register the action as `targeted` by invoking the `targeted` method. The caller then has to name the models by id in the `resources` argument, and providing a search is prohibited. ```php use App\Rest\Actions\DeactivateUsersAction; class UserResource extends Resource { /** * The actions that should be linked * @param RestRequest $request * @return array */ public function actions(RestRequest $request): array { return [ DeactivateUsersAction::make()->targeted() ]; } } ``` You may also declare the state on the action itself with the `targeted` property: ```php namespace App\Rest\Actions; use Lomkit\Rest\Actions\Action; class DeactivateUsersAction extends Action { /** * Indicates if the action requires an explicit list of resource ids. * * @var bool */ public $targeted = true; } ``` The targeted models still go through the resource's [search query](https://laravel-rest-api.lomkit.com/resources/interactions#search-query), so an id the current user is not allowed to see is skipped rather than acted on. Each id is validated with its own existence query, so the number of ids a single request may carry is capped at `1000`. Declare the `maxResources` property to lower it on an action whose blast radius should stay small: ```php namespace App\Rest\Actions; use Lomkit\Rest\Actions\Action; class DeactivateUsersAction extends Action { public $targeted = true; /** * The maximum number of ids the action accepts per request. */ public int $maxResources = 50; } ``` ::warning `maxResources` is typed on the parent class, so an override must repeat the `int` type. Declaring `public $maxResources = 50;` without it is a PHP fatal error. :: ::note An action cannot be both standalone and targeted, the two states are mutually exclusive. Combining them throws an `InvalidActionStateException`. :: # Hooks Hooks are designed in two ways: one on the **controller** (around HTTP endpoints) and one on the **resource** (around model lifecycle events). ## Controller Hooks On your rest controller you can react to every endpoint. Hooks are defined as `protected` methods and receive the current request as their only argument. | Hook | Fires | | -------------------- | --------------------------------------- | | `beforeDetails` | Before the details endpoint response | | `beforeSearch` | Before the search query runs | | `afterSearch` | After the search results are built | | `beforeMutate` | Before any mutate operations run | | `afterMutate` | After all mutate operations complete | | `beforeOperate` | Before an action is dispatched | | `afterOperate` | After an action completes | | `beforeDestroy` | Before models are deleted | | `afterDestroy` | After models are deleted | | `beforeForceDestroy` | Before models are force-deleted | | `afterForceDestroy` | After models are force-deleted | | `beforeRestore` | Before soft-deleted models are restored | | `afterRestore` | After soft-deleted models are restored | ```php class UsersController extends RestController { public static $resource = App\Rest\Resources\UserResource::class; protected function beforeSearch(SearchRequest $request): void { // Runs before the search query executes } protected function afterMutate(MutateRequest $request): void { // Runs after all mutate operations complete } } ``` ## Resource Hooks On your resource, you can listen to model lifecycle events. These hooks fire for every part of the API, **including nested relation operations**. | Hook | Fires | | ----------------- | --------------------------------------- | | `mutating` | Before a model is created or updated | | `mutated` | After a model is created or updated | | `destroying` | Before a model is deleted | | `destroyed` | After a model is deleted | | `restoring` | Before a soft-deleted model is restored | | `restored` | After a soft-deleted model is restored | | `forceDestroying` | Before a model is force-deleted | | `forceDestroyed` | After a model is force-deleted | ::note Methods ending with `ing` fire **before** the event; methods ending with `ed` fire **after**. :: ```php class UserResource extends Resource { public function mutated(MutateRequest $request, array $requestBody, Model $model): void { if ($requestBody['operation'] === 'update') { Storage::put('images/avatars/'.$model->getKey().'.jpg', $requestBody['attributes']['file']); } } } ``` # Gates If you are not familiar with what a "Gate" is, please have a look at the [Laravel Documentation](https://laravel.com/docs/authorization){rel=""nofollow""}. Laravel Rest Api takes advantage of this feature to provide your frontend users direct access to the current authenticated user rights. ## Using gates Gates are enabled by default. You just have to provide the one you want in your [search endpoint.](https://laravel-rest-api.lomkit.com/endpoints/search#gates) If you don't want to use this feature you have two ways to disable it: ### Globally In your `config/rest.php` file, you can directly specify to disable this feature: ```php [ // ... 'gates' => [ 'enabled' => false, // Switch this to false ] // ... ] ``` ### Resource If you want to disable this feature for certain resources only, you can use the `DisableGates` trait on your resource file: ```php [UserResource.php] class UserResource extends Resource { use \Lomkit\Rest\Concerns\Resource\DisableGates; // ... } ``` ## Policy messages in gates To surface policy messages explaining authorization failures, first set the config `rest.gates.message.enabled` to `true`. ::warning Enabling this changes the `gates` payload shape returned by the `search` endpoint and may require frontend updates. :: In your policy, return an authorization `Response`: ```php use App\Models\Post; use App\Models\User; use Illuminate\Auth\Access\Response; /** * Determine if the given post can be updated by the user. */ public function update(User $user, Post $post): Response { return $user->id === $post->user_id ? Response::allow() : Response::deny('You do not own this post.'); } ``` This changes the `search` gates payload by adding a `message` and `allowed` keys: ```json { "data": [ { "id": 1, "gates": { "authorized_to_update": { "allowed": false, "message": "You do not own this post." } } } ] } ``` # Authorizations ::warning Laravel Rest Api doesn't provide authentication, you can define it on your project using for example `Laravel Passport`, `Laravel Sanctum` or make your own. :: ## Policies To restrict access to viewing, creating, updating, or deleting resources, Laravel Rest Api relies on Laravel's [authorization policies](https://laravel.com/docs/authorization#creating-policies){rel=""nofollow""}. By default, Laravel automatically associates the appropriate authorization policies with your models. You can also manually associate a policy in your `AppServiceProvider`. In either case, Laravel REST API will use it automatically, without requiring any additional configuration. This concerns the following methods: | Policy method | Used when | | ------------- | ------------------------------ | | `viewAny` | Listing / searching resources | | `view` | Reading a specific model | | `create` | Creating a new model | | `update` | Updating an existing model | | `replicate` | Duplicating a model via mutate | | `delete` | Deleting a model | | `restore` | Restoring a soft-deleted model | | `forceDelete` | Force-deleting a model | For example, to determine which users are allowed to view a User model, you simply need to define a `view` method on the model's corresponding policy class: ```php is($model); } } ``` ### Disable Authorizations By default, authorizations are enabled but you can disable them in certain situations. ::note When you are mutating / searching distant relations, this will apply the relation resource state. :: ### Globally In your `config/rest.php` file, you can directly specify to disable this feature: ```php [ // ... 'authorizations' => [ 'enabled' => false, // Switch this to false ] // ... ] ``` ### Resource If you want to disable this feature for certain resources only, you can use the `DisableAuthorizations` trait on your resource file: ```php [UserResource.php] class UserResource extends Resource { use \Lomkit\Rest\Concerns\Resource\DisableAuthorizations; // ... } ``` ## Relationships Because you may want to be able to control what models front end users are linking, Laravel Rest Api provides full control over relationship operations. ### Attaching / Detaching When working with relationships, Laravel Rest Api uses a simple policy method naming convention: `attach{Model}` / `detach{Model}`. To illustrate this convention, let's assume your application has a `Post` resource and a `Comment` resource. If you would like to authorize which users can add comments to a post, you should define an `attachComment` method on your post model's policy class: ```php getShortName()); return sprintf( 'rest.authorization.%s.%s.%s', $class, $identifier, $request->user()?->getKey() ); } } ``` ### Cache time to live Cache is by default persisted for 5 minutes, if you want to change this feature: #### Globally In your `config/rest.php` file, you can directly specify the number of minutes for the cache: ```php [ // ... 'authorizations' => [ 'cache' => [ 'default' => 5 // Cache minutes by default ], ], // ... ] ``` #### Resource If you want to adjust this in your resource, please add the `cacheFor` method in your resource: ```php class UserResource { /** * Determine for how much time the cache should be kept. * * @return \DateTimeInterface|\DateInterval|float|int|null */ public function authorizationCacheFor() { return now()->addMinutes(5); } } ``` ### Disable cache #### Globally If you want to disable it globally, change your config in the `rest.php` file ```php [ 'cache' => [ 'enabled' => false, // Set this to false ], ], ] ``` #### Resource If you want to disable this feature for certain resources only, you can use the `DisableAuthorizationsCache` trait on your resource file: ```php [UserResource.php] class UserResource extends Resource { use \Lomkit\Rest\Concerns\Resource\DisableAuthorizationsCache; // ... } ``` # Full Text Search ::warning Be sure you have installed Laravel Scout before going further: {rel=""nofollow""} :: To be more specific about how it works, Laravel Rest Api will implement a Scout Builder and apply to it the `filters`, `sorts` and `instructions`. After this, it will pass other parameters to the query callback of the Laravel Scout Builder. This allows you to eager load data / aggregates for example. Be aware that using full text search disable features listed [here](https://laravel-rest-api.lomkit.com/endpoints/search#text) ## Model Full text search is only implemented on models that are `Searchable` as given in the Laravel Scout documentation: ```php 1, default => 0, }; return $query ->whereRaw('MOD(id, 2) = '.$number); } /** * The instruction fields. * * @param \Lomkit\Rest\Http\Requests\RestRequest $request * @return array */ public function fields(\Lomkit\Rest\Http\Requests\RestRequest $request): array { return [ 'type' => [ 'string', 'in:odd,even' ] ]; } } ``` In the `handle` method, you have the flexibility to interact with the current search query as you see fit. You can access the resource that launched the instruction by using `$this->resource` at any moment. ## Register an instruction To register an instruction, you simply need to specify it in your resource: ```php use App\Rest\Instructions\OddEvenIdInstruction; class UserResource extends Resource { /** * The instructions that should be linked * @param RestRequest $request * @return array */ public function instructions(RestRequest $request): array { return [ OddEvenIdInstruction::make() ]; } } ``` ### Authorizations In case you don't want to expose your instructions to all users, you simply need to condition this method: ```php use App\Rest\Instructions\OddEvenIdInstruction; class UserResource extends Resource { /** * The instructions that should be linked * @param RestRequest $request * @return array */ public function instructions(RestRequest $request): array { $instructions = []; if ($request->user()->isAdministrator()) { array_push($instructions, OddEvenIdInstruction::make()) } return $instructions; } } ``` ## Fields Sometimes, you want to directly collect data from your frontend users. This is what fields are made for. You need to define them in your instruction first: ```php namespace App\Rest\Instructions; use Illuminate\Database\Eloquent\Model; use Lomkit\Rest\Instructions\Instruction; use Lomkit\Rest\Http\Requests\RestRequest; class OddEvenIdInstruction extends Instruction { /** * The instruction fields. * * @param \Lomkit\Rest\Http\Requests\RestRequest $request * @return array */ public function fields(\Lomkit\Rest\Http\Requests\RestRequest $request) { return [ 'type' => [ 'string', 'in:odd,even' ] ]; } } ``` In the returned array, the key should correspond to the field name provided in the call, while the value should represent the desired set of validations for that field. Laravel Rest API automatically validates all fields based on your defined validation rules. You get those fields in the `$field` variable in your `handle` method. ::note Instruction fields are validated exactly like action fields: as a standard Laravel validation, so presence and cross-field rules (`required`, `required_if`, `present`, …) fire even when the field is absent from the request. Validation errors are keyed by field name under `search.instructions.{index}.fields.{name}`. :: ## Meta Because your instructions are exposed to frontend users, they do receive certain information about them, such as `uriKey`, `name` and `fields`. However, you are also welcome to provide your own data. You can achieve this by invoking `withMeta` within your instruction's constructor: ```php class OddEvenIdInstruction extends Instruction { public function __construct() { $this->withMeta([ 'color' => '#FFFFFF' ]); } } ``` Alternatively, you can call it during the instruction registration if you wish to define distinct meta information based on the resource: ```php use App\Rest\Instructions\OddEvenIdInstruction; class UserResource extends Resource { /** * The instructions that should be linked * @param RestRequest $request * @return array */ public function instructions(RestRequest $request): array { return [ OddEvenIdInstruction::make() ->withMeta(['color' => '#FFFFFF']) ]; } } ``` # Responses ::warning The response is the final piece of the API and should be used judiciously, as it can override all the underlying logic of Laravel Rest API. :: ## Defining Responses By default, Rest responses are stored in the app/Rest/Responses directory of your application. You may generate a new Response by using the `rest:response` Artisan command: ```bash php artisan rest:response UserResponse ``` You are now free to modify the `map` method from your new Response: ```php /** * This maps on each model returned by the API, use it at your ease. * * @var \Illuminate\Database\Eloquent\Model $model * @var array $responseModel * * @return array */ protected function map(\Illuminate\Database\Eloquent\Model $model, array $responseModel) : array { return $responseModel; } ``` The `$model` represents the original model, whereas `$responseModel` is the response that would typically be initiated by Laravel Rest Api. ::note Responses should be considered as a final resort for leveraging the API. In most cases, they may not be necessary. :: ## Registering Responses You now have to specify the Response within the corresponding `Resource` file. ```php [UserResource.php] /** * The reponse the entry corresponds to. * * @var class-string */ public static $response = App\Rest\Responses\UserResponse::class; ``` # Documentation Laravel Rest API provides first-class integration with [Scramble](https://scramble.dedoc.co){rel=""nofollow""} to automatically generate your API documentation from your resources, fields, relations and validation rules — with zero manual configuration. > **The legacy `rest:documentation` command is deprecated** and will be removed in a future major version. We recommend migrating to the Scramble-based integration described below. ## Scramble integration (recommended) ### Installation Install Scramble in your project: ```bash composer require dedoc/scramble ``` That's it. Visit `/docs/api` to see your fully generated documentation. --- ### What is auto-generated The extension reads your resources at boot time and automatically documents: - **Fields** — all fields exposed via `fields()`, with their inferred OpenAPI type - **Validation rules** — rules from `rules()`, `createRules()` and `updateRules()` are reflected as field descriptions and types - **Relations** — all relations from `relations()`, including their Lomkit type (`BelongsTo`, `HasMany`, `BelongsToMany`, etc.) - **Search body** — filters, sorts, selects, includes, scopes, pagination - **Mutate body** — `attributes` object with all fields, nested `relations` block with the correct structure (single object vs array) per relation type - **Destroy / Restore body** — `resources` array of IDs --- ### Customization The Lomkit extension is a standard Scramble extension. Every option Scramble provides — authentication schemes, servers, route filtering, custom routes, UI customization — is available to you without any restriction. For a complete reference, refer to the [Scramble documentation](https://scramble.dedoc.co){rel=""nofollow""}. #### Filtering routes by authentication A common pattern is to hide auth-protected routes from unauthenticated users: ```php use Dedoc\Scramble\Scramble; use Illuminate\Routing\Route; use Lomkit\Rest\Http\Controllers\Controller as LomkitController; Scramble::routes(function (Route $route) { $controller = $route->getController(); if (!$controller instanceof LomkitController) { return false; } if (!auth()->check()) { $protected = ['auth', 'auth:sanctum', 'auth:api', 'auth:passport']; if (collect($route->gatherMiddleware())->intersect($protected)->isNotEmpty()) { return false; } } return true; }); ``` --- ## Legacy documentation (deprecated) > ⚠️ The following approach is **deprecated** and will be removed in the next major release. Please migrate to the Scramble integration above. ### Generating documentation ```bash php artisan rest:documentation ``` The documentation is generated based on registered routes. It is stored in your `public` folder and should be committed to version control. Avoid running this command directly on production — generate locally during development instead. By default the generated documentation is accessible at `/api-documentation`. To customize or disable this, update the `rest.routing` configuration. ### Configure data You can configure OpenAPI information, servers and security in `config/rest.php`: ```php 'documentation' => [ 'info' => [ 'title' => config('app.name'), 'summary' => 'This is my project\'s documentation', 'description' => 'Find out all about my project\'s API', 'termsOfService' => null, 'contact' => [ 'name' => 'My Company', 'email' => 'email@company.com', 'url' => 'https://company.com', ], 'license' => [ 'url' => null, 'name' => 'Apache 2.0', 'identifier' => 'Apache-2.0', ], 'version' => '1.0.0', ], 'servers' => [ ['url' => '/', 'description' => 'The current server'], ], 'security' => [], ], ``` ### Extend operations If you need to customize a specific operation, override the corresponding method on your controller: ```php namespace App\Rest\Controllers; use Lomkit\Rest\Documentation\Schemas\Operation; class UsersController { public function generateDocumentationDetailOperation(Operation $operation): Operation { return $operation->withTags(['my custom tag']); } } ``` Available methods: `generateDocumentationDetailOperation`, `generateDocumentationSearchOperation`, `generateDocumentationMutateOperation`, `generateDocumentationActionsOperation`, `generateDocumentationDestroyOperation`, `generateDocumentationRestoreOperation`, `generateDocumentationForceDeleteOperation`. ### Add your own routes Generate a dedicated service provider: ```bash php artisan rest:documentation-provider ``` Register it in `bootstrap/providers.php`: ```php return [ App\Providers\RestDocumentationServiceProvider::class, ]; ``` Then declare custom routes in its `boot()` method: ```php use Lomkit\Rest\Facades\Rest; use Lomkit\Rest\Documentation\Schemas\OpenAPI; use Lomkit\Rest\Documentation\Schemas\Operation; use Lomkit\Rest\Documentation\Schemas\Path; public function boot(): void { Rest::withDocumentationCallback(function (OpenAPI $openAPI) { $openAPI->withPaths([ 'myPath' => (new Path) ->withDescription('my custom path') ->withGet( (new Operation) ->withTags(['Callable']) ->withSummary('You should call this !') ), ]); return $openAPI; }); } ``` For all available schemas and methods, see the [GitHub repository](https://github.com/Lomkit/laravel-rest-api/tree/master/src/Documentation/Schemas){rel=""nofollow""}. # Precognition Laravel Rest API supports [Laravel’s Precognition](https://laravel.com/docs/precognition){rel=""nofollow""} feature for live frontend validation. :br Precognition allows you to anticipate the outcome of an HTTP request by running validation rules without executing the controller logic. :br In other words, the request is “simulated” purely for validation. ## Installation Precognition support is disabled by default. To enable it, set `rest.precognition.enabled` to true in your config/rest.php. ```php // config/rest.php 'precognition' => [ 'enabled' => true, ], ``` This flag toggles Laravel’s `HandlePrecognitiveRequests` middleware support. ## Usage Example Once enabled, any API endpoint generated via `Rest::resource` will automatically have `HandlePrecognitiveRequests` applied. :br This means that any request with the `Precognition` header to true will be considered precognitive. :br For example, consider a search endpoint on the users resource. If the client sends: ```http request POST /api/users/search Precognition: true Content-Type: application/json { "search": { // ... search parameters ... } } ``` Laravel will execute all middleware and run the form request or validator rules as usual, then immediately return the validation result. In this mode: - If the data fails validation, you will receive a 422 Unprocessable Entity with the validation errors. - If it passes validation, you will receive a 204 No Content response and no database action is taken. In short, the request is validated but the controller method is never called, allowing you to get “live” validation feedback from the API without performing the actual operation. # Flutter ::note This is a **community package**, not an official Laravel REST API integration. It is maintained independently by [xefi](https://github.com/xefi){rel=""nofollow""}. :: ## Overview The [`laravel_rest_api_flutter`](https://pub.dev/packages/laravel_rest_api_flutter){rel=""nofollow""} package provides a clean, typed integration between your Flutter app and a Laravel REST API backend. It supports searching, mutating, deleting, and triggering actions. The full documentation is available at **[xefi.github.io/laravel-rest-api-flutter-doc](https://xefi.github.io/laravel-rest-api-flutter-doc/){rel=""nofollow""}**. ## Installation ```bash flutter pub add laravel_rest_api_flutter ``` ## Features - **Search** — Query and filter resources with a typed builder - **Mutate** — Create, update, and manage related records - **Actions** — Trigger custom backend actions - **Delete / Restore / Force Delete** — Full resource lifecycle support - **Responses & Gates** — Handle typed responses and authorization gates - **Testing utilities** — Built-in support for unit testing repositories