Building a Rest API with Symfony and PHP

This tutorial will show you how to create a simple Rest API with Symfony and PHP. We are going to connect our API to a MySQL database and store some data there.

The app

The functionality of the app will be very simple. Our API will receive some JSON data from the client (this could, for example, be a React or Angular App) and then store it in a MySQL database.

What we are going to learn

This tutorial is going to show you a couple of things:

  • Create a new Symfony project
  • How to take advantage of the command line in a Symfony project
  • How the Symfony framework is structured (MVC)
  • How to connect to a MySQL database
  • How to register new API endpoints
  • How to accept JSON data from the client side
  • How to store data in the database

We have a lot to do, so let’s get started!

Prerequisites

Your don’t need to know anything about developing an API with Symfony. However, I assume that you have all the tools installed that are necessary to create Symfony apps. That means, XAMPP (or MAMP or anything similar) and composer.

Creating the project

If you use XAMPP like me, open a new terminal and navigate to your htdocs folder. Then, type the following command:

composer create-project symfony/skeleton symfony_react

This will create a folder named „symfony_react“. In there, we will find a „skeleton“ Symfony project. We could have also used the command „symfony/website-skeleton“, which would have installed more packages and features. However, we won’t need most of these as we are only using Symfony to build an API and not to build a traditional web app.

Installing the necessary packages

Next, we are going to install a few packages that will make life a little bit easier for us. In the terminal, type the following commands (lines staring with ‚#‘ should be understood as comments):

# Facilitate route declaration by enabling declaration within the controller files:
composer require annotations

# Database functionality
composer require doctrine

# Live server:
composer require server

# Easily create controllers, entities and much more via command line
composer require make

# Declare new form via the command line
composer require form validator

If you’re getting errors while trying to install the server package, it might be because of version issues. You might find a solution in this Stackoverflow topic.

Connection to the database: phpMyAdmin

As we want to store our posts in a database, we of course have connect our app to one. Since we installed doctrine, this is going to be quite easy. All we need to do is to create a DSN statement, just like in any other PHP application. If you are not familiar with that, you might want to first refresh your PHP basics before diving into something more complex like Symfony. Anyways, if you are using XAMPP, open your browser of choice and go to localhost/phpmyadmin. If you haven’t already, create a new user with read and write permissions on localhost. And that’s it for now in phpMyAdmin.

Connection to the database: Symfony

As we installed doctrine, connecting to the database will be a piece of cake. All we need to do is to change the DSN statement in the .env file, which you can find here:

Symfony .env file

Towards the bottom, you will find the DSN statement, where you must change username and password according to your phpMyAdmin credentials. Also, change the database name to a name that is currently NOT taken in your phpMyAdmin panel:

Syfmony DSN statement .env file

To connect our app to the database, only one step is left. We will again use doctrine for this. Jump back to the terminal and execute the following command:

php bin/console doctrine:database:create

What is this „php bin/console“? Well, it is just a PHP script that Symfony provides and allows us to execute other scripts. Literally, if we check the folder structure of our project, we will find a the file „console“ in the „bin“ directory. If we just execute the command „php bin/console“ we will get a list with all the commands at our disposal. These vary depending on the installed packages. When we skim through the list, we will also see the command „doctrine:database:create“. As the name already indicates, it will create the database for us given the details in the DSN statement. If everything goes well, we should now see a database named the same as in our DSN statement. Congrats, we are now connected!

Starting the development server for

Now its time to actually start our development server. The nicest way to do this, especially when creating an API, is to create a virtual server. We already installed the necessary packages for this. If we execute the following command in the terminal:

php bin/console server

we will get a list of all the available server commands. We are going to choose the following one

php bin/console server:run

This will start a server for us on localhost:8000. If you visit this URL in your browser, you should see a placeholder view that comes with Symfony. We are not going to access Symfony files from the browser but still, this verifies that everything is working fine.

The MVC framework

The MVC (Mode, View, Controller) framework is one of the most popular frameworks across all programming languages – for good reason. As the name indicates, an MVC app is split in three separate parts. The Model and ONLY the Model takes care of the business login, i.e., interactions with the database. The View and ONLY the View takes care of what is visible on the screen to the user, i.e, the User Interface. The Controller kind of „controls“ the app. It asks the Model for data, receives data from the view and sends data back to the view. The MVC framework has the great advantage of decoupling different parts of an app, therefore making it maintainable in the future.

MVC in our API with Symfony

Symfony relies on the MVC framework. The controllers are stored in folder src/Controller and the models lie in src/Entity. When building a traditional web-app with Symfony, the views could be found in the templates directory and Twig would be used as the inhouse templating language. However, as we only build the Rest API with Symfony, we could use anything we want in the frontend. The current state of the art would be to implement a Single Page Application (SPA) with React, Angular, Vue or some other Javascript Framework.

Creating the PostController

We could manually build a new file in the src/Controller file. However, for a Controller to properly integrate into Symfonys framework, a couple of dependencies and namespaces have to be used and imported in every controller file. To automate that task we installed a package called „make“. Using this, we can set up a controller via the command line. Hence, execute the following command in the terminal:

php bin/console make:controller PostController

In the src/Controller folder we should now find a file named „PostController.php“, which looks like this:

Symfony Controller file

And just like that we created our first controller!

Declaring the API routes

As we notice on the picture, there is a comment and there is something with „@Route“. In this comment, we can declare which route has to be hit in order for the index() method to be called. This is made possible by a package named „annotations“, which we installed at the beginning. The „name“ can become important later on if we want to reference to this route. By default, a route accepts all kinds of HTTP requests, e.g., get, post, patch, delete etc. We will see how we can declare the allowed methods a little bit later.

Declaring the API route prefixes

It is common to start the names of API routes with /api/… . Specifying this in every route would be tedious, but luckily Symfony comes with a simple solution. We just declare a route name BEFORE the Controller class, like this:

<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;

/**
 * @Route("/api/posts", name="posts.")
*/
class PostController extends AbstractController
{
    /**
     * @Route("/post", name="post")
     */
    public function index()
    {
        return $this->json([
            'message' => 'Welcome to your new controller!',
            'path' => 'src/Controller/PostController.php',
        ]);
    }
}

Now, every of our Post routes starts with /api/posts and their name starts with posts.

Entities

Before we start the heavy lifting in the PostController, we should create the post entity, i.e. the post model. Our post entity will be a class that has the same properties as the posts table in our database (which we are going to create in a second) and getters and setters for each of these properties. If you ever worked with MongoDB and mongoose, this concept will be very familiar to you (in mongoose, these structures are called schemas).

Creating the Post Entity

As you might have guessed, we can again use the command line to create an entity. Execute the following command:

php bin/console make:entity Post

You will now be asked to specify the properties of the post entity. To keep things simple, we only create the „title“ property and accept the defaults by smashing enter a couple of times. The instructions in the terminal should be self explanatory. Once we are done with that, we will find the file src/Entity/Post.php in our project. It has the private properties $title and even $id, which is pretty convenient!

Creating the Post table in the database

So we have the post entity which shall mimic the properties of a post that we store in our database. However, we don’t yet have a table named „post“ in our database. Again, Symfony has us covered. In the terminal, hit:

php bin/console doctrine:schema:update --force

If we now go to phpMyAdmin we will see a new table named „post“ with the fields „id“ and „title“. Our executed command just scanned our entities and updated our database where necessary.

Creating the method

It is now time to process data in the PostController and pass it to the Post Entity.
First of all, we create a new method in PostController.php and assign a route to it:

/**
 * @Route("/create", name="create", methods={"POST"})
 */
    public function create(Request $request)
    {
    }

We notice a few things here. First of all, we assigned the route“/create“ to our route and named it „create“. Secondly, we defined that the route is only accessible via the HTTP Post method by declaring „method={„POST“}“. By default, a Symfony route accepts all HTTP methods. But since we expect data from the client that we want to store in the database, the Post method is the way to go. Lastly, we pass a Request object to our method, which we will cover in more detail now.

The Request object

Whenever a client requests an API, it sends bunch of data. E.g., the used HTTP method, the browser from which the request was started, the IP address and much more. Symfony comes with a class named „Request“ that allows us to access these request information. One important thing is that we have to use the correct namespace in order to access the Request class and instantiate an object from in. Therefore, at the top of the PostController file where all other namespaces are used, add:

use Symfony\Component\HttpFoundation\Request;

Good IDEs like PHP Storm will do this automatically for us. For Visual Studio Code there are also some great Symfony extensions that get the work done.

Accessing JSON data from the request

We now have the possibility to access all the data that is sent to our API with Symfony via the client. Most modern client side applications will send the data via JSON. Thus, we need to find a way to convert the JSON from the client into a format that we can process in PHP. This is a lot easier that it may sound:

/**
 * @Route("/create", name="create", methods={"POST"})
 */
    public function create(Request $request)
    {
        $data = json_decode($request->getContent(), true);
    }

To convert JSON data in PHP we can use the json_decode method. It takes the JSON that should be converted as the first argument and optionally a second boolean parameter. If true, the JSON will be converted into an associative array. We can easily access the content of the request (in other words, the title of the post we want to store in the database) by calling the getContent method of the $request object.

Creating a new Post object

So we now have access to the title that we want to store in the database. For this, we firstly need to import the correct namespace for our post entity. Thus, at the top of our PostController, paste:

use App\Entity\Posts;

After that, we create a new object from the Post class and use the setTitle method on that object in order to set the title to the title given by the client request:

/**
 * @Route("/create", name="create", methods={"POST"})
 */
    public function create(Request $request)
    {
        //decode request content:
        $data = json_decode($request->getContent(), true);
                
    //create new post obj.
    $post = new Post();
    $post->setTitle($data["title"]);
    }

Saving the new post to the database

So we have the new post object that we now want to store to the database. Again, Symfony has our back with some build in methods. Specifically, it has the so called Entity Manager which, as the name indicates, allows us to conveniently manage our entities and their relations to the database:

/**
 * @Route("/create", name="create", methods={"POST"})
 */
    public function create(Request $request)
    {
    //decode request content:
        $data = json_decode($request->getContent(), true);
                
    //create new post obj.
    $post = new Post();
    $post->setTitle($data["title"]);

    //instantiate the entity manager
    $em = $this->getDoctrine()->getManager();
    }

The entity manager has two methods that we need to call, to save our post to the database:

/**
 * @Route("/create", name="create", methods={"POST"})
 */
    public function create(Request $request)
    {
    //decode request content:
        $data = json_decode($request->getContent(), true);
                
    //create new post obj.
    $post = new Post();
    $post->setTitle($data["title"]);

    //instantiate the entity manager
    $em = $this->getDoctrine()->getManager();

    //save post to database
    $em->persist($post):
    $em->flush();
    }

And that’s it! This is all we need to do to store our newly created post to the database.

Sending a response from our API with Symfony

If we run the above code we will still get an error, however, because we did not send a response back to the client. In order to send a response, we again have to import a namespace at the top of our file:

use Symfony\Component\HttpFoundation\Response;

Then, in our create method, we have to return a new response object:

/**
 * @Route("/create", name="create", methods={"POST"})
 */
    public function create(Request $request)
    {
    //decode request content:
        $data = json_decode($request->getContent(), true);
                
    //create new post obj.
    $post = new Post();
    $post->setTitle($data["title"]);

    //instantiate the entity manager
    $em = $this->getDoctrine()->getManager();

    //save post to database
    $em->persist($post):
    $em->flush();

    //return response to client
    return new Response("post created");
    }

So now the client gets the message „post created“, when the post was created.

Summary and next steps

This tutorial showed you how to set up a very simple API with PHP and Symfony. But although it is very simple it gives you all the foundations you need in order to extend it in any way you like. The next steps could be to fetch all posts from the database, edit and delete posts. Also, connecting the API to a frontend application like React would be a pretty cool thing.
Also, if you are interested in more generic PHP development, check out this post about flash messages in PHP. However, if you are more into WordPress development, check out this post about Plugin Development.