REST API in Symfony (without FosRestBundle) using JWT authentication. Part 1

Translation of the article prepared in advance of the start of the course Symfony Framework.


In the first part of the article, we will look at the easiest way to implement the REST API in a Symfony project without using a FosRestBundle. In the second part, which I will post next, we will look at JWT authentication. Before we begin, we first need to understand what REST really means.

What does Rest mean?

REST (Representational State Transfer) is an architectural style of developing web services that cannot be ignored, because in the modern ecosystem there is a great need for creating Restful applications. This may be due to a steady rise in the position of JavaScript and related frameworks.

The REST API uses the HTTP protocol. This means that when a client makes any request to such a web service, he can use any of the standard HTTP verbs: GET, POST, DELETE and PUT. The following describes what will happen if the client indicates the appropriate verb.

  • GET: will be used to get a list of resources or information about them.
  • POST: will be used to create a new resource.
  • PUT: will be used to update an existing resource.
  • DELETE: will be used to delete an existing resource.

REST has no state, which means that there are no request states on the server side either. The states remain on the client side (an example is the use of JWT for authentication, with which we are going to secure our REST API). Thus, when using authentication in the REST API, we need to send an authentication header to get the correct response without storing state.

Creating a symfony project:

First, we assume that you have already installed PHP and Сomposer package manager to create a new Symfony project. With all this in stock, create a new project using the following command in the terminal:

composer create-project symfony/skeleton demo_rest_api


Creating a Symfony Project

We use the basic Symfony skeleton, which is recommended for microservices and APIs. Here’s what the directory structure looks like:


Project structure

Config: contains all bundle settings and a list of bundles in bundle.php.
Public: provides access to the application through index.php.
Src: contains all controllers, models and services
Var: contains system logs and cache files.
Vendor: contains all external packages.

Now let’s install some necessary packages using Сomposer:

composer require symfony/orm-pack
composer require sensio/framework-extra-bundle

We set sensio/framework-extra-bundle, which will help us simplify the code using annotations to determine our routes.

We also need to install symphony/orm-pack for integration with Doctrine ORM to connect to the database. Below is the configuration of the database I created, which can be specified in the .env file.


.env configuration file

Now let’s create our first entity. Create a new file named Post.php in folder src/Entity.

id;
  }
  /**
   * @param mixed $id
   */
  public function setId($id)
  {
   $this->id = $id;
  }
  /**
   * @return mixed
   */
  public function getName()
  {
   return $this->name;
  }
  /**
   * @param mixed $name
   */
  public function setName($name)
  {
   $this->name = $name;
  }
  /**
   * @return mixed
   */
  public function getDescription()
  {
   return $this->description;
  }
  /**
   * @param mixed $description
   */
  public function setDescription($description)
  {
   $this->description = $description;
  }

  /**
   * @return mixed
   */
  public function getCreateDate(): ?DateTime
  {
   return $this->create_date;
  }

  /**
   * @param DateTime $create_date
   * @return Post
   */
  public function setCreateDate(DateTime $create_date): self
  {
   $this->create_date = $create_date;
   return $this;
  }

  /**
   * @throws Exception
   * @ORMPrePersist()
   */
  public function beforeSave(){

   $this->create_date = new DateTime('now', new DateTimeZone('Africa/Casablanca'));
  }

  /**
   * Specify data which should be serialized to JSON
   * @link https://php.net/manual/en/jsonserializable.jsonserialize.php
   * @return mixed data which can be serialized by json_encode,
   * which is a value of any type other than a resource.
   * @since 5.4.0
   */
  public function jsonSerialize()
  {
   return [
    "name" => $this->getName(),
    "description" => $this->getDescription()
   ];
  }
 }

And after that run the command: php bin/console doctrine:schema:create to create a database table according to our Post entity.

Now let’s create PostController.phpwhere we will add all the methods that interact with the API. It should be placed in a folder src/Controller.

findAll();
   return $this->response($data);
  }

  /**
   * @param Request $request
   * @param EntityManagerInterface $entityManager
   * @param PostRepository $postRepository
   * @return JsonResponse
   * @throws Exception
   * @Route("/posts", name="posts_add", methods={"POST"})
   */
  public function addPost(Request $request, EntityManagerInterface $entityManager, PostRepository $postRepository){

   try{
    $request = $this->transformJsonBody($request);

    if (!$request || !$request->get('name') || !$request->request->get('description')){
     throw new Exception();
    }

    $post = new Post();
    $post->setName($request->get('name'));
    $post->setDescription($request->get('description'));
    $entityManager->persist($post);
    $entityManager->flush();

    $data = [
     'status' => 200,
     'success' => "Post added successfully",
    ];
    return $this->response($data);

   }catch (Exception $e){
    $data = [
     'status' => 422,
     'errors' => "Data no valid",
    ];
    return $this->response($data, 422);
   }

  }

  /**
   * @param PostRepository $postRepository
   * @param $id
   * @return JsonResponse
   * @Route("/posts/{id}", name="posts_get", methods={"GET"})
   */
  public function getPost(PostRepository $postRepository, $id){
   $post = $postRepository->find($id);

   if (!$post){
    $data = [
     'status' => 404,
     'errors' => "Post not found",
    ];
    return $this->response($data, 404);
   }
   return $this->response($post);
  }

  /**
   * @param Request $request
   * @param EntityManagerInterface $entityManager
   * @param PostRepository $postRepository
   * @param $id
   * @return JsonResponse
   * @Route("/posts/{id}", name="posts_put", methods={"PUT"})
   */
  public function updatePost(Request $request, EntityManagerInterface $entityManager, PostRepository $postRepository, $id){

   try{
    $post = $postRepository->find($id);

    if (!$post){
     $data = [
      'status' => 404,
      'errors' => "Post not found",
     ];
     return $this->response($data, 404);
    }

    $request = $this->transformJsonBody($request);

    if (!$request || !$request->get('name') || !$request->request->get('description')){
     throw new Exception();
    }

    $post->setName($request->get('name'));
    $post->setDescription($request->get('description'));
    $entityManager->flush();

    $data = [
     'status' => 200,
     'errors' => "Post updated successfully",
    ];
    return $this->response($data);

   }catch (Exception $e){
    $data = [
     'status' => 422,
     'errors' => "Data no valid",
    ];
    return $this->response($data, 422);
   }

  }

  /**
   * @param PostRepository $postRepository
   * @param $id
   * @return JsonResponse
   * @Route("/posts/{id}", name="posts_delete", methods={"DELETE"})
   */
  public function deletePost(EntityManagerInterface $entityManager, PostRepository $postRepository, $id){
   $post = $postRepository->find($id);

   if (!$post){
    $data = [
     'status' => 404,
     'errors' => "Post not found",
    ];
    return $this->response($data, 404);
   }

   $entityManager->remove($post);
   $entityManager->flush();
   $data = [
    'status' => 200,
    'errors' => "Post deleted successfully",
   ];
   return $this->response($data);
  }

  /**
   * Returns a JSON response
   *
   * @param array $data
   * @param $status
   * @param array $headers
   * @return JsonResponse
   */
  public function response($data, $status = 200, $headers = [])
  {
   return new JsonResponse($data, $status, $headers);
  }

  protected function transformJsonBody(SymfonyComponentHttpFoundationRequest $request)
  {
   $data = json_decode($request->getContent(), true);

   if ($data === null) {
    return $request;
   }

   $request->request->replace($data);

   return $request;
  }

 }

Here we identified five routes:

● GET / api / posts: will return a list of posts.


api receiving all posts

● POST / api / posts: will create a new post.


api add new post

● GET / api / posts / id: will return a post corresponding to a specific identifier,


getting a specific post

● PUT / api / posts / id: update the post.


post update

This is the result after the update:


post after update

● DELETE / api / posts / id: delete the post.


post removal

This is the result of getting all posts after deleting the post with id 3:


all posts after deletion

Source code can be found. here

Conclusion

So, now we understand what REST and Restful are. Restful API must be stateless. We know how to create a Restful application using HTTP verbs. In general, now we understand REST well and are ready to professionally create Restful-applications.

In the next article, we will look at how to secure API security using JWT authentication.


Learn more about the Symfony Framework


Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *