Creating your First ApiResource
We're about to build an API for the very important job of allowing dragons to show off their treasure. Right now, our project doesn't have a single database entity... but we're going to need one to store all that treasure.
Generating our First Entity
Find your terminal and first run
composer require maker --dev
to install Maker Bundle. Then run:
php bin/console make:entity
Perfect! Let's call our entity DragonTreasure. Then it asks us a question that you maybe haven't seen before - Mark this class as an API platform resource? It asks because API Platform is installed. Say no because we're going to do this step manually in a moment.
Okay, let's start adding properties. Start with name as a string, with a Length of the default 255, and make it not nullable. Then, add description with a text type, and make that not nullable. We also need a value, like... how much the treasure is worth. That will be an integer not nullable. And we simply must have a coolFactor: dragons need to specify just how awesome this treasure is. That'll be a number from 1 to 10, so make it an integer and not nullable. Then, createdAt datetime_immutable that's not nullable... and Finally, add an isPublished property, which will be a boolean type, also not nullable. Hit " enter" to finish.
Phew! There's nothing very special so far. This created two classes: DragonTreasureRepository (which we're not going to worry about), and the DragonTreasure entity itself with $id, $name, $description, $value, etc
| // ... lines 1 - 2 | |
| namespace App\Entity; | |
| use App\Repository\DragonTreasureRepository; | |
| use Doctrine\DBAL\Types\Types; | |
| use Doctrine\ORM\Mapping as ORM; | |
| #[ORM\Entity(repositoryClass: DragonTreasureRepository::class)] | |
| class DragonTreasure | |
| { | |
| #[ORM\Id] | |
| #[ORM\GeneratedValue] | |
| #[ORM\Column] | |
| private ?int $id = null; | |
| #[ORM\Column(length: 255)] | |
| private ?string $name = null; | |
| #[ORM\Column(type: Types::TEXT)] | |
| private ?string $description = null; | |
| #[ORM\Column] | |
| private ?int $value = null; | |
| #[ORM\Column] | |
| private ?int $coolFactor = null; | |
| #[ORM\Column] | |
| private ?\DateTimeImmutable $plunderedAt = null; | |
| #[ORM\Column] | |
| private ?bool $isPublished = null; | |
| // ... lines 35 - 110 | |
| } |
along with the getter and setter methods. Beautifully boring. There is one little bug in this version of MakerBundle, though. It generated an isIsPublished() method. Let's change that to getIsPublished().
| // ... lines 1 - 9 | |
| class DragonTreasure | |
| { | |
| // ... lines 12 - 99 | |
| public function getIsPublished(): ?bool | |
| { | |
| return $this->isPublished; | |
| } | |
| public function setIsPublished(bool $isPublished): self | |
| { | |
| // ... lines 107 - 109 | |
| } | |
| } |
Setting up the Database
All right, so we have our entity. Now we need a migration for its table... but that might be a bit difficult since we don't have our database set up yet! I'm going to use Docker for this. The DoctrineBundle recipe gave us a nice docker-compose.yml file that boots up Postgres, so... let's use that! Spin over to your terminal and run:
Tip
In modern versions of Docker, run docker compose up -d instead of docker-compose.
docker-compose up -d
If you don't want to use Docker, feel free to start your own database engine and then, in .env or .env.local, configure DATABASE_URL. Because I'm using Docker as well as the symfony binary, I don't need to configure anything. The Symfony web server will automatically see the Docker database and set the DATABASE_URL environment variable for me.
Okay, to make the migration, run:
symfony console make:migration
This symfony console is just like ./bin/console except it injects the DATABASE_URL environment variable so that the command can talk to the Docker database. Perfect! Spin over and check out the new migration file... just to make sure it doesn't contain any weird surprises.
| // ... lines 1 - 4 | |
| namespace DoctrineMigrations; | |
| use Doctrine\DBAL\Schema\Schema; | |
| use Doctrine\Migrations\AbstractMigration; | |
| /** | |
| * Auto-generated Migration: Please modify to your needs! | |
| */ | |
| final class Version20230104160057 extends AbstractMigration | |
| { | |
| // ... lines 15 - 19 | |
| public function up(Schema $schema): void | |
| { | |
| // this up() migration is auto-generated, please modify it to your needs | |
| $this->addSql('CREATE SEQUENCE dragon_treasure_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); | |
| $this->addSql('CREATE TABLE dragon_treasure (id INT NOT NULL, name VARCHAR(255) NOT NULL, description TEXT NOT NULL, value INT NOT NULL, cool_factor INT NOT NULL, plundered_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, is_published BOOLEAN NOT NULL, PRIMARY KEY(id))'); | |
| $this->addSql('COMMENT ON COLUMN dragon_treasure.plundered_at IS \'(DC2Type:datetime_immutable)\''); | |
| } | |
| public function down(Schema $schema): void | |
| { | |
| // this down() migration is auto-generated, please modify it to your needs | |
| $this->addSql('CREATE SCHEMA public'); | |
| $this->addSql('DROP SEQUENCE dragon_treasure_id_seq CASCADE'); | |
| $this->addSql('DROP TABLE dragon_treasure'); | |
| } | |
| } |
Looks good! So spin back over and run this with:
symfony console doctrine:migrations:migrate
Done!
Exposing our First API Resource
We now have an entity and a database table. But if you go and refresh the documentation... there's still nothing there. What we need to do is tell API Platform to expose our DragonTreasure entity as an API resource. To do this, go above the class and add a new attribute called ApiResource. Hit "tab" to add that use statement.
| // ... lines 1 - 4 | |
| use ApiPlatform\Metadata\ApiResource; | |
| // ... lines 6 - 9 | |
| #[ORM\Entity(repositoryClass: DragonTreasureRepository::class)] | |
| class DragonTreasure | |
| { | |
| // ... lines 14 - 112 | |
| } |
Done! As soon as we do that... and refresh... whoa! The documentation is alive! It now shows that we have six different endpoints: One to retrieve all of the DragonTreasure resources, one to retrieve an individual DragonTreasure, one to create a DragonTreasure, two that edit a DragonTreasure plus one to delete it. And this is more than just documentation. These endpoints work.
Go over and click "Try it Out", then "Execute". It doesn't actually return anything because our database is empty, but it does gives us a 200 status code with some empty JSON. We'll talk about all of the other fancy keys in the response shortly.
Oh, but I do want to mention one thing. As we just saw, the easiest way to create a set of API endpoints is by adding this ApiResource attribute above your entity class. But you can actually add this attribute above any class: not just entities. That's something we're going to talk about in a future tutorial: it can be a nice way to separate what your API looks like from what your entity looks like, especially in bigger APIs. But again, that's for later. Right now, using ApiResource on top of our entity is going to work great.
Let's discover this cool, interactive documentation a bit more. Where did this come from? How does our app magically have a bunch of new routes? And do dragons really love tacos? Let's find out next!
36 Comments
Hello,
When I run
symfony console make:migrationthen:How to fix it? I am working with PHP8.2 and Symfony6...
Hey Szymon!
I know this error! If you're using the code from the course, then we're using Postgres. The error means that you need to install the
pgsqlphp extension - e.g. like described here (the exact setup instructions will depend on your operating system): https://stackoverflow.com/questions/10085039/postgresql-pdoexception-with-message-could-not-find-driver#answer-48858539Or, if you'd rather use Mysql, you can skip the migrations and run
doctrine:schema:update --forceinstead (after configuring your app to talk to your mysql database).Let me know if this helps!
Cheers!
yeah, that's right. I am using Windows, and I just removed semicolon in xampp/php/php.ini
and it's running good.
But it's kind weird for me, because I am not using XAMPP, I installed php directly in my computer, but most important is that working anyway :)
additional query: what is the difference between using MySQL and PostgreSQL in this tutorial? I remember in Symfony5 tutorial we used MySQL.
Is chance to see this database tables in friendly environemnt like phpMyAdmin or something similar? does it require a more difficult setup?
Hey Szymon,
In this tutorial, the only difference you'll find if you use MySQL instead of PostgreSQL is the migration files. The queries generated by Doctrine will be specific to MySQL, but besides that, nothing else will be different.
I found this tool for Postgres. However, I don't know how hard it might be to set it up. https://github.com/phppgadmin/phppgadmin
Cheers!
I'm trying to follow this series with newer versions of Symfony (7.3) and API Platform (4.2). Because I can't use the downloaded code, I'm starting from scratch and just trying to follow along. So far so good, but when I made the entity I used snake case names for the fields.
When I converted the entity to an API Resource, the docs list both snake_case and camelCase field options, for all operations that use fields. How can I control that?
Just a note: both types of fields are listed, but only the camelCase fields work for post and patch.
OK - I found the answer. At least to remove the camelCase fields.
https://api-platform.com/docs/core/serialization/#name-conversion-for-symfony
I did try the reverse SnakeCaseToCamelCaseNameConverter and it worked as expected.
But it turns out that using
#[Groups...]will prevent the duplication of the fields as well. Then using[#SerializedName...]can adjust the name to whatever is preferred. No need for the explicit Name Converters.That said, the Name Converters only required editing two config files and would apply to all API Resources. Using the Groups and SerializedName as to be applied to every field - a lot more work.
Of course, I believe all of this could be handled very simply by using DTOs instead of Doctrine Entities. I just haven't gotten that far following this tutorial.
Yes, DTOs would help you in that situation, although DTOs add their own complexity :)
Hey Jeff,
Good job found the answer yourself! And thanks for sharing this solution with others
Cheers!
When I tried using the postgresql db included with the docker-compose.yml file, I ran into errors where I could not connect to the database through doctrine or even through plain PHP using the pg_connect() function or PDO. The error I was getting said "could not initiate GSSAPI security context" When I tried using the DB tool included with PhpStorm, everything seemed fine with the database.
The connection DSN was postgres://app:!ChangeMe!@127.0.0.1:63122/app?sslmode=disable&charset=utf8&serverVersion=16.7
Hey @David-L
Sorry for the slow reply. How are you starting up the web server? Remember that you need to do it through the Symfony CLI so it can set up docker automatically
symfony serve -dCheers!
I'm not even getting to the point of running the web server, just running
symfony console doctrine:migrations:statusresults in the error message after a 60 second time out. I ended up using mysql for my database instead.I think there's an issue with your DB authentication, for some reason you're using
GSSAPI. Try disabling it by appending this to your DB connection string?gssencmode=disableThe connection string was generated by the Symfony CLI and stored in the DATABASE_URL env var, I was following the video and using the course files verbatim. Is there any other way to disable that so i can avoid explicitly defining the connection string?
I think you can do this: in your
doctrine.yamlconfig fileOr, you could try changing the authentication mode directly in your
docker-compose.ymlfile:(I'm not 100% sure this latter option will work but you can give it a try)
Cheers!
Hello,
I've created a first entity, a migration, migrated the migration. After adding
#[ApiResource]to the new entity, I'm gettingWhat did I do wrong?
I changed the "api-platform/core" version in the composer from 3.2.2 to 3.2.0, now the error is gone, https://127.0.0.1:8000/api is loading. But when I try to execute a get request, I still get the response:
Hey @Elena
That's unexpected, I believe there's something odd with your vendors, try re-installing them
rm -r vendor/and runcomposer installagain. Oh, and restart the web server just in caseCheers!
Hello,
When I run symfony console make:migration then:
An exception occurred in the driver: SQLSTATE[08006] [7] SCRAM authentication requires libpq version 10 or above
And as I'm not a docker nor postgreSQL expert, I have a hard time.
I just (normally) followed your tutorial.
How to fix it ?
Thanks in advance.
Eric
Hey @Eric-J!
Ah! That's not a friendly error :). From what I can tell... this looks like something weird and/or annoying :/. There are various solutions here - https://stackoverflow.com/questions/62807717/how-can-i-solve-postgresql-scram-authentication-problem - and none of them look great.
My advice: change the
DATABASE_URLto use mysql or sqlite - something you're more familiar with. Then turn OFF the docker container and follow the tutorial. There's no need to get stopped by a silly detail like this. The tutorial will work 100% the same with pgsql, mysql or sqlite.Cheers!
Hello,
Thanks for your help.
It seems that I had 2 Symfony local server running from 2 project at the same time and on the same database.
Anyhow, I got the error handled and could proceed on.
Kind regards
Good debugging Eric! The Docker container names are based on your directory name (not the full directory path, just the final name). So if you have 2 Symfony projects in directories named
my_app, then they'll share the same containers... which isn't ideal :p. I hit this issue sometimes as well.Cheers!
How can I get apiPlatform working on an entity in a different namespace?
I updated the autoloaded in composer.json and doctrine.yaml
but the resource still doesn't show up in url:
/apiit was exhausting , but I found it:
api_platform.yml:
Hey Cameron,
I'm happy to hear you were able to find the solution yourself, well done! And thanks for sharing it with others
Cheers!
Are there anyway to create custom API such as api/hello-world without entity
Hey @Ken
Yes, APIPlatform does not force you to create entities for your resources, you can do the exact same thing with data model classes
Here are the docs: https://api-platform.com/docs/core/dto/#using-data-transfer-objects-dtos
Cheers!
Hi !
Thanks for the tutorial :)
I have some trouble about the
symfony console make:migrationpart.Without modifing
docker-compose.ymlfile, when I try to apply the migrations (after boot the container withdocker compose up -d) i get the next error :Do you have a solution ?
For information, I'm working on Windows with WSL.
Thank you !
Hey @Yorane
I'm afraid I cannot help you with your Docker setup because I don't use it under WSL, but I have a question, does it only happens when you try to load the migrations?
Hi MolloKhan,
It's OK, I resolved my problem :)
Not only, every time my server try to connect to the container database.
To resolve the problem, we have to change the IP address in the
.envfile with the container IP.Oh cool, well done @Yorane
Tip: when you use Docker, you need to run any command through the Symfony CLI, so it can inject the correct env var values
symfony console app:fooCheers!
Thank's a lot dude!!!
My line is now like that and it works :D
Hey i always get this
This is my docker-compose.yaml regarding the mysql container
and my .env
and response body:
... more log below
I am sure i have the drivers i need but..any suggestion would be welcomed. Thank you!
Hey @Kev!
Well that's annoying! You're using Mysql, so the "could not find driver" is referring to the PHP pdo_mysql driver. Exactly HOW you install this depends on your system - e.g. with Ubuntu, it's often something like
apt install php-mysql. Also, remember to restart your web server (e.g. the symfony binary) after installing the driver. And FINALLY. if you have multiple php binaries installed and you're not sure which one thesymfonybinary is using, you can always runsymfony local:php:listto find out.Cheers!
Hey @weaverryan ,
Thanks for your answer, the general idea is that i already have the php-mysql driver installed. I have it as a docker-compose procedure script for a container, AND i have it on my local ubuntu machine installed.
The default .env build with the default postrgres variable is the same result.
Weirdish issue really!
Cheers!
That IS weird. That usually means that some different, unexpected PHP binary is being used (that doesn't have the driver installed).... but these are a pain to figure out!
"Houston: no signs of life"
Start the conversation!