API Platform is the easiest way to create state-of-the-art web APIs using Laravel!
With API Platform, you can:
Let’s discover how to use API Platform with Laravel!
API Platform can be installed easily on new and existing Laravel projects. If you already have an existing project, skip directly to the next section.
If you don’t have an existing Laravel project, create one. All Laravel installation methods are supported. For instance, you can use Composer:
composer create-project laravel/laravel my-api-platform-laravel-app
cd my-api-platform-laravel-app
In your Laravel project, install the API Platform integration for Laravel:
composer require api-platform/laravel
After installing API Platform, publish its assets and config:
php artisan api-platform:install
If it’s not already done, start the built-in web server:
php artisan serve
Open http://127.0.0.1:8000/api/
, your API is already active and documented… but empty!
After installing API Platform, you can publish its assets and config using the api-platform:install
Artisan command.
php artisan api-platform:install
To discover how API Platform framework works, we will create an API to manage a bookshop.
Let’s start by creating a Book
model:
php artisan make:model Book
By default, Laravel uses SQLite. You can open the database/database.sqlite
file with your preferred SQLite client (PhpStorm works like a charm), create a table named books
, and add some columns, Eloquent and API Platform will detect these columns automatically.
But there is a better alternative: using a migration class.
First, create a migration class for the books
table:
php artisan make:migration create_books_table
Open the generated migration class (database/migrations/<timestamp>_create_books_table.php
) and add some columns:
public function up(): void
{
Schema::create('books', function (Blueprint $table) {
$table->id();
+ $table->string('isbn')->nullable();
+ $table->string('title');
+ $table->text('description');
+ $table->string('author');
+ $table->date('publication_date')->nullable();
$table->timestamps();
});
}
Finally, execute the migration:
php artisan migrate
The table and columns have been created for you!
Open app/Models/Book.php
that we generated in the previous step and mark the class it contains with the #[ApiResource]
attribute:
namespace App\Models;
+use ApiPlatform\Metadata\ApiResource;
use Illuminate\Database\Eloquent\Model;
+#[ApiResource]
class Book extends Model
{
}
Open http://127.0.0.1:8000/api/
, tadam, your API is ready and entirely functional 🎉:
You can play with your API with the sandbox provided by SwaggerUI.
Under the hood, API Platform:
http://127.0.0.1:8000/api/docs.json
) and JSON-LD/Hydra formats using this metadataImagine doing it all again, properly, by hand? How much time have you saved? Weeks, months? And you’ve seen nothing yet!
If you access any API URL with the .html
extension appended, API Platform displays
the corresponding API request in the UI. Try it yourself by browsing to http://127.0.0.1:8000/api/books.html
. If no extension is present, API Platform will use the Accept
header to select the format to use.
So, if you want to access the raw data, you have two alternatives:
Accept
header (or don’t set any Accept
header at all if you don’t care about security) - preferred when writing API clientsFor instance, go to http://127.0.0.1:8000/api/books.jsonld
to retrieve the list of Book
resources in JSON-LD.
ℹ️ Note
Documentation for Eloquent “API resources” encourages using the JSON:API community format. While we recommend preferring JSON-LD when possible, JSON:API is also supported by API Platform, read the Content Negotiation section to learn how to enable it.
Of course, you can also use your favorite HTTP client to query the API. We are fond of Hoppscotch, a free and open source API client with good support of API Platform.
While exposing directly the data in the database is convenient for Rapid Application Development, using different classes for the internal data and the public data is a good practice for more complex projects.
As explained in our general design considerations, API Platform allows us to use the data source of our choice using a provider and Data Transfer Objects (DTOs) are first-class citizens!
Let’s create our DTO:
<?php
namespace App\ApiResource;
use ApiPlatform\Metadata\Get;
#[Get(uriTemplate: '/my_custom_book/{id}')]
class Book
{
public string $id;
public string $title;
}
and register our new directory to API Platform:
// config/api-platform.php
// ...
return [
'resources' => [
app_path('ApiResource'),
app_path('Models'),
],
// ...
];
Then we can create the logic to retrieve the state of our Book
DTO:
<?php
namespace App\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\Models\Book as BookModel;
final class BookProvider implements ProviderInterface
{
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
{
$book = BookModel::find($uriVariables['id']);
return new Book(id: $book->id, title: $book->title);
}
}
Register the state provider:
<?php
namespace App\Providers;
use ApiPlatform\State\ProviderInterface;
use App\State\BookProvider;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\ServiceProvider;
class ApiServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->singleton(BookProvider::class, function (Application $app) {
return new BookProvider();
});
$this->app->tag([BookProvider::class], ProviderInterface::class);
}
}
Apply the provider to your operation:
<?php
namespace App\ApiResource;
use ApiPlatform\Metadata\Get;
use App\State\BookProvider;
#[Get(uriTemplate: '/my_custom_book/{id}', provider: BookProvider::class)]
class Book
{
public string $id;
public string $title;
}
By default, a JSON-LD response is sent but many other formats, including CSV and JSON:API are supported.
You can enable or disable formats in config/api-platform.php
:
// config/api-platform.php
// ...
return [
'formats' => [
'jsonld' => ['application/ld+json'],
'jsonapi' => ['application/vnd.api+json'],
'csv' => ['text/csv'],
],
'patch_formats' => [
'json' => ['application/merge-patch+json'],
],
'docs_formats' => [
'jsonld' => ['application/ld+json'],
'jsonapi' => ['application/vnd.api+json'],
'jsonopenapi' => ['application/vnd.openapi+json'],
'html' => ['text/html'],
],
// ...
];
By default, only the REST endpoints are enabled, but API Platform also supports GraphQL!
Install the GraphQL support package:
composer require api-platform/graphql:^4
Then, enable GraphQL in config/api-platform.php
:
'graphql' => [
- 'enabled' => false,
+ 'enabled' => true,
Then open http://127.0.0.1:8000/api/graphql
and replace the default GraphQL query example with:
{
books(first: 3) {
edges {
node {
title
author
publicationDate
}
}
}
}
You now have a REST and a GraphQL API with the same code!
As you can see, a nice UI (GraphiQL) is also available. The documentation is automatically generated using the GraphQL introspection endpoint.
API Platform allows to control which fields will be publicly exposed by the API using the same syntax as Eloquent serialization:
namespace App\Models;
use ApiPlatform\Metadata\ApiResource;
use Illuminate\Database\Eloquent\Model;
#[ApiResource]
class Book extends Model
{
/**
* The attributes that should be hidden (deny list).
*
* @var array
*/
protected $hidden = ['isbn'];
}
namespace App\Models;
use ApiPlatform\Metadata\ApiResource;
use Illuminate\Database\Eloquent\Model;
#[ApiResource]
class Book extends Model
{
/**
* The attributes that should be visible (allow list).
*
* @var array
*/
protected $visible = ['title', 'description'];
}
Let’s replace our author column with a relation to a new author
table:
public function up(): void
{
Schema::create('books', function (Blueprint $table) {
$table->id();
$table->string('description');
- $table->string('author');
+ $table->integer('author_id')->unsigned();
+ $table->foreign('author_id')->references('id')->on('authors');
$table->timestamps();
});
+ Schema::create('authors', function (Blueprint $table): void {
+ $table->id();
+ $table->string('name');
+ $table->timestamps();
+ });
}
By doing so, API Platform will automatically handle links to that relation using your preferred format (JSON:API, JSON-LD, etc) and when we request a Book we obtain:
{
"@context": "/api/contexts/Book",
"@id": "/api/books/1",
"@type": "Book",
"name": "Miss Nikki Senger V",
"isbn": "9784291624633",
"publicationDate": "1971-09-04",
"author": "/api/authors/1"
}
To create a Book related to an author, you should use IRIs to reference the relation:
PATCH /api/books/1
Content-Type: application/merge-patch+json
{
"author": "/api/authors/2"
}
There’s a powerful mechanism inside API Platform to create routes using relation (e.g.: /api/authors/2/books
), read more about subresources here.
A must have feature for APIs is pagination. Without pagination, collection responses quickly become huge and slow, and can even lead to crashes (Out of Memory, timeouts…).
Fortunately, the Eloquent state provider provided by API Platform automatically paginates data!
To test this feature, let’s inject some fake data into the database.
Instead of manually creating the data you need to test your API, it can be convenient to automatically insert fake data in the database.
Laravel provides a convenient way to do that: Eloquent Factories.
First, create a factory class for our Book
model:
php artisan make:factory BookFactory
Then, edit database/factories/BookFactory.php
to specify which generator to use for each property of the model:
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Book>
*/
class BookFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
- //
+ 'title' => mb_convert_case(fake()->words(4, true), MB_CASE_TITLE),
+ 'isbn' => fake()->isbn13(),
+ 'description' => fake()->text(),
+ 'author' => fake()->name(),
+ 'publication_date' => fake()->date(),
];
}
}
Then, update the app/Models/Book.php
to hint Eloquent that it has an associated factory:
namespace App\Models;
use ApiPlatform\Metadata\ApiResource;
+use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
#[ApiResource]
class Book extends Model
{
+ use HasFactory;
}
Reference this factory in the seeder (database/seeder/DatabaseSeeder.php
):
namespace Database\Seeders;
+use App\Models\Book;
use App\Models\User;
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*/
public function run(): void
{
// User::factory(10)->create();
User::factory()->create([
'name' => 'Test User',
'email' => '[email protected]',
]);
+ Book::factory(100)->create();
}
}
Finally, seed the database:
php artisan db:seed
ℹ️ Note
The
fake()
helper provided by Laravel lets you generate different types of random data for testing and seeding purposes. It uses the Faker library, which has been created by François Zaninotto. François is also a member of the API Platform Core Team. He maintains API Platform Admin, a tool built on top of his popular React-Admin library that makes creating admin interfaces consuming your API data super easy. What a small world!
Send a GET
request on http://127.0.0.1:8000/api/books
.
By default, API Platform paginates collections by slices of 30 items.
This is configurable, to change to 10 items per page, change app/Models/Book.php
like this:
namespace App\Models;
use ApiPlatform\Metadata\ApiResource;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
-#[ApiResource]
+#[ApiResource(
+ paginationItemsPerPage: 10,
+)]
class Book extends Model
{
use HasFactory;
}
Read the pagination documentation to learn all you can do!
API Platform has a ton of knobs and gives you full control over what is exposed.
For instance, here is how to make your API read-only by enabling only the GET
operations:
// app/Models/Book.php
namespace App\Models;
+use ApiPlatform\Metadata\Get;
+use ApiPlatform\Metadata\GetCollection;
use Illuminate\Database\Eloquent\Model;
-#[ApiResource]
#[ApiResource(
paginationItemsPerPage: 10,
+ operations: [
+ new GetCollection(),
+ new Get(),
+ ],
)]
class Book extends Model
{
}
We’ll use configuration options provided by API Platform all along this getting started guide, but there are tons of features!
A good way to discover them is to inspect the properties of the ApiResource
and ApiProperty
attributes and, of course, to read the core library documentation.
You can change the default configuration (for instance, which operations are enabled by default) in the config (config/api-platform.php
).
For the rest of this tutorial, we’ll assume that at least all default operations are enabled (you can also enable PUT
if you want to support upsert operations).
API Platform provides an easy shortcut to some useful filters, for starters you can enable a PartialSearchFilter
the title property:
// app/Models/Book.php
namespace App\Models;
use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Laravel\Eloquent\Filter\PartialSearchFilter;
use Illuminate\Database\Eloquent\Model;
#[ApiResource]
+#[QueryParameter(key: 'title', filter: PartialSearchFilter::class)]
class Book extends Model
{
}
It’s also possible to enable filters on every exposed property:
// app/Models/Book.php
namespace App\Models;
use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Laravel\Eloquent\Filter\PartialSearchFilter;
+use ApiPlatform\Laravel\Eloquent\Filter\OrderFilter;
use Illuminate\Database\Eloquent\Model;
#[ApiResource]
+#[QueryParameter(key: ':property', filter: PartialSearchFilter::class)]
+#[QueryParameter(key: 'sort[:property]', filter: OrderFilter::class)]
class Book extends Model
{
}
The OrderFilter
allows us to sort the collection.
The :property
placeholder gives the ability to create a parameter for each exposed property. These filters will be automatically documented:
On top of that, some validation rules are automatically added based on the given JSON Schema. You can customize the set of rules inside the constraints
option of a QueryParameter
.
API Platform comes with several filters dedicated to Laravel, check them out!
API Platform hooks into the native Laravel authentication mechanism.
It also natively supports:
Follow the official instructions for the tool(s) you want to use.
In Swagger UI, you can authenticate your requests using the Authorize
button in the top right corner.
To use it, you need to add some configuration in the config/api-platform.php
file.
Here is an example of how to configure API key authentication:
// config/api-platform.php
'swagger_ui' => [
'enabled' => true,
'apiKeys' => [
'api' => [
'type' => 'header',
'name' => 'X-API-Key'
]
]
]
Or if you are using Laravel Passport (or any other OAuth server):
// config/api-platform.php
'swagger_ui' => [
'enabled' => true,
'oauth' => [
'enabled' => true,
'type' => 'oauth2',
'flow' => 'authorizationCode',
'tokenUrl' => '<oauth_token_endpoint>',
'authorizationUrl' =>'<oauth_authorization_endpoint>',
'refreshUrl' => '<oauth_refresh_endpoint>',
'scopes' => ['scope' => 'Description of the scope'],
'pkce' => true,
]
]
A combination of both is also possible. For more information, you can also check the Swagger UI documentation.
It’s sometimes convenient to enforce the use of middleware for all API routes.
In the following example, we enable the Laravel Sanctum middleware for all API routes:
// config/api-platform.php
return [
// ..
'defaults' => [
'middleware' => 'auth:sanctum',
],
];
To authorize write operations (POST
, PATCH
, PUT
) and validate user input, you may generate a Form Request class:
php artisan make:request BookFormRequest
Then, add validation rules to the generated class (app/Http/Requests/BookFormRequest.php
in our example):
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class BookFormRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
- return false;
+ return user()->isAdmin();
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
- //
+ 'title' => 'required|unique:books|max:255',
+ 'description' => 'required',
+ 'author' => 'required|max:100',
];
}
}
In this example, we only authorize admin users to do write operations, and we add some validation rules.
If the standard Laravel conventions are followed, the Form Request class is autodetected and used automatically.
Otherwise, reference it explicitly in the rules
parameter:
// app/Models/Book.php
namespace App\Models;
+use App\Http\Requests\BookFormRequest;
use Illuminate\Database\Eloquent\Model;
-#[ApiResource]
+#[ApiResource(
+ rules: BookFormRequest::class,
+)]
class Book extends Model
{
}
API Platform will transform any exception in the RFC 7807 (Problem Details for HTTP APIs) format.
You can create your own Error
resource following this guide.
Read the detailed documentation about Laravel data validation in API Platform.
To protect an operation and ensure that only authorized users can access it, start by creating a Laravel policy:
php artisan make:policy BookPolicy --model=Book
Laravel will automatically detect your new policy and use it when manipulating a Book.
Read the detailed documentation about using Laravel gates and policies with API Platform.
Wouldn’t it be nice to have an administration backend to manage the data exposed by your API? Checkout API Platform Admin!
This Material Design admin is a Single Page App built with React Admin. It is powerful and fully customizable.
It leverages the Hydra documentation exposed by the API component to build itself. It’s 100% dynamic - no code generation occurs.
API Platform also has an awesome client generator able to scaffold fully working Next.js, Nuxt.js, React/Redux, Vue.js, Quasar, and Vuetify Progressive Web Apps/Single Page Apps that you can easily tune and customize. The generator also supports React Native if you prefer to leverage all capabilities of mobile devices.
The generated code contains a list (including pagination), a delete button, a creation and an edit form. It also includes Tailwind CSS classes and ARIA roles to make the app usable by people with disabilities.
Checkout the dedicated documentation.
API Platform supports Caching Metadata out of the box. It uses the Laravel cache system to store that information.
Caching is automatically enabled in production environments (when APP_DEBUG
is set to false
).
Calling php artisan optimize
will cache the metadata and improve the performance of your API drastically.
To clear the cache, use php artisan optimize:clear
.
Now that you learned the basics, be sure to read the general design considerations and how to extend API Platform to understand how API Platform is designed, and how to hook your custom business logic!
IsApiResourceTrait
Instead of AttributesWhile attributes (introduced in PHP 8) are the preferred way to configure your API Platform resources, it’s also possible to use a trait instead.
These two classes are strictly equivalent:
// Attributes
namespace App\Models;
use ApiPlatform\Metadata\ApiResource;
use Illuminate\Database\Eloquent\Model;
#[ApiResource]
class Book extends Model
{
}
// Trait
namespace App\Models;
use ApiPlatform\Metadata\IsApiResource;
use Illuminate\Database\Eloquent\Model;
class Book extends Model
{
use IsApiResource;
}
When using the IsApiResourceTrait
, it’s also possible to return advanced configuration by defining an apiResource()
static method.
These two classes are strictly equivalent:
// Attributes
namespace App\Models;
use ApiPlatform\Metadata\ApiResource;
use Illuminate\Database\Eloquent\Model;
#[ApiResource(
paginationItemsPerPage: 10,
operations: [
new GetCollection(),
new Get(),
],
)]
class Book extends Model
{
}
// Trait
namespace App\Models;
use ApiPlatform\Metadata\IsApiResource;
use Illuminate\Database\Eloquent\Model;
class Book extends Model
{
use IsApiResource;
public static function apiResource(): ApiResource
{
return new ApiResource(
paginationItemsPerPage: 10,
operations: [
new GetCollection(),
new Get(),
],
);
}
}
It’s quite common to define multiple ApiResource
, ApiProperty
, and Filter
attributes on the same class.
To mimic this behavior, the apiResource()
function can return an array instead of a single instance of metadata class.
These two classes are strictly equivalent:
// Attributes
namespace App\Models;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\QueryParameter;
use ApiPlatform\Laravel\Eloquent\Filter\PartialSearchFilter;
use App\Http\Requests\BookFormRequest;
use Illuminate\Database\Eloquent\Model;
#[ApiResource(
paginationEnabled: true,
paginationItemsPerPage: 5,
rules: BookFormRequest::class,
operations: [
new Put(),
new Patch(),
new Get(),
new Post(),
new Delete(),
new GetCollection(),
]
)]
#[QueryParameter(key: ':property', filter: PartialSearchFilter::class)]
class Book extends Model
{
}
// Trait
namespace App\Models;
use ApiPlatform\Metadata\IsApiResource;
use ApiPlatform\Metadata\QueryParameter;
use ApiPlatform\Laravel\Eloquent\Filter\PartialSearchFilter;
use App\Http\Requests\BookFormRequest;
use Illuminate\Database\Eloquent\Model;
class Book extends Model
{
use IsApiResource;
public static function apiResource(): array
{
return [
new ApiResource(
paginationEnabled: true,
paginationItemsPerPage: 5,
rules: BookFormRequest::class,
operations: [
new Put(),
new Patch(),
new Get(),
new Post(),
new Delete(),
new GetCollection(),
]
),
new QueryParameter(key: ':property', filter: PartialSearchFilter::class),
];
}
}
Made with love by
Les-Tilleuls.coop can help you design and develop your APIs and web projects, and train your teams in API Platform, Symfony, Next.js, Kubernetes and a wide range of other technologies.
Learn more