With JavaScript frameworks, there’s an increasing need to access data via an API, without necessarily bringing out the heavy artillery (such as the excellent API Platform).
The API we will develop will be ultra-simple (no authentication, minimal error handling, no versioning, …).
It will include 5 routes to manage books:
- GET:
/api/book/{id}: Retrieve a single book. - GET:
/api/books: Retrieve all books. - POST:
/api/book: Create a book. - PUT:
/api/book/{id}: Update a book. - DELETE:
/api/book/{id}: Delete a book.
Project Creation
To start, create a new Symfony project with PHP 8 and Symfony 6:
symfony new custom-api
symfony server:startDatabase
Add the necessary components to manage a database and install the Maker Bundle:
composer require orm
composer require --dev maker-bundleConfigure your connection in the .env.local file:
DATABASE_URL="mysql://login:password@localhost:3306/customApi?serverVersion=5.7"Create an entity to represent a book:
bin/console make:entity BookUpdate the database:
bin/console doctrine:database:create
bin/console make:migration
bin/console doctrine:migrations:migrateFixtures
To generate sample data:
composer require --dev orm-fixtures
composer require fzaninotto/faker --dev
bin/console make:fixturesExample of fixtures for 50 books:
class BookFixtures extends Fixture
{
public function load(ObjectManager $manager): void
{
$faker = \Faker\Factory::create('en_US');
for ($i = 0; $i < 50; $i++) {
$book = new Book();
$book->setAuthor($faker->firstName . ' ' . $faker->lastName);
$book->setTitle($faker->realText($faker->numberBetween(10, 100)));
$book->setPageCount(rand(50, 999));
$manager->persist($book);
}
$manager->flush();
}
}Load the fixtures:
bin/console doctrine:fixtures:loadGET Routes
Create a controller to manage books:
bin/console make:controller BookControllerAdd a route to retrieve a specific book:
#[Route('/api/book/{id}', name: 'get_book', methods: ['GET'])]
public function get($id): Response
{
$book = $this->entityManager->getRepository(Book::class)->find($id);
if (!$book) {
return $this->json(null, Response::HTTP_NOT_FOUND);
}
return $this->json($book, Response::HTTP_OK);
}Add another route to retrieve all books:
#[Route('/api/books', name: 'get_books', methods: ['GET'])]
public function getAll(): Response
{
$books = $this->entityManager->getRepository(Book::class)->findAll();
return $this->json($books, Response::HTTP_OK);
}POST, PUT, and DELETE Routes
To create a book:
#[Route('/api/book', name: 'add_book', methods: ['POST'])]
public function add(Request $request): Response
{
$data = json_decode($request->getContent(), true);
$book = new Book();
$book->setTitle($data['title'])->setAuthor($data['author'])->setPageCount($data['pageCount']);
$this->entityManager->persist($book);
$this->entityManager->flush();
return $this->json(['message' => 'Book created'], Response::HTTP_CREATED);
}To update a book:
#[Route('/api/book/{id}', name: 'update_book', methods: ['PUT'])]
public function update(Request $request, $id): Response
{
$book = $this->entityManager->getRepository(Book::class)->find($id);
if (!$book) {
return $this->json(null, Response::HTTP_NOT_FOUND);
}
$data = json_decode($request->getContent(), true);
$book->setTitle($data['title'] ?? $book->getTitle())
->setAuthor($data['author'] ?? $book->getAuthor())
->setPageCount($data['pageCount'] ?? $book->getPageCount());
$this->entityManager->flush();
return $this->json($book, Response::HTTP_OK);
}To delete a book:
#[Route('/api/book/{id}', name: 'delete_book', methods: ['DELETE'])]
public function delete($id): Response
{
$book = $this->entityManager->getRepository(Book::class)->find($id);
if ($book) {
$this->entityManager->remove($book);
$this->entityManager->flush();
}
return $this->json(null, Response::HTTP_NO_CONTENT);
}Conclusion
You have just created a simple API with Symfony. The complete source code is available on my GitHub.
