Chat with us, powered by LiveChat

This website uses cookies

Our website, platform and/or any sub domains use cookies to understand how you use our services, and to improve both your experience and our marketing relevance.

Build a Symfony ecommerce application using React and Cloudinary

September 29, 2018

14 Min Read
symfony ecommerce app
Reading Time: 14 minutes

The availability of modern web technology tools has made it extremely easy to build highly scalable web applications. With the access to frontend library like React, you can easily craft a dynamic and reasonable user interface irrespective of the complexity, architecture and scale of your product.

In an earlier article on getting started with React in Symfony using Webpack Encore, we discussed how to easily integrate React into an existing or a new Symfony application. Feel free to go through the article for comprehensive details.

This time, we’ll explore the stunning combination of Symfony and React further by building a product listing or Symfony ecommerce application.

What We Will Build?

The demo is a product listing application. Adding more features, will make it like a mini Symfony ecommerce store. For now, we will reduce the complexity and once done, will have a working application as shown below:

Here, in this demo, you can specify the name, description, price, and an image of your product. Once you click on the Add Product button, the React frontend application will make an HTTP call to the backend API developed in Symfony.

Furthermore, to properly manage image assets, we will employ the service of Cloudinary. Let’s start.

Prerequisites

This tutorial requires the knowledge of React, Object Oriented Programming with PHP and MVC web API patterns. To make the tutorial less difficult for newbies, we will endeavor to break down any complex implementation or logic. You will also need a Cloudinary account, kindly click here to create a free account.

Do ensure that you have Node.js and Yarn package manager installed on your system.

Structure of the Application

This application will have a backend API with a React frontend. The frontend will break into separate, reusable and independent UI components. For the backend, we will use Symfony to accept and process HTTP requests sent in by React.

Now that you have a clear picture of what we want to develop, let’s start by installing Symfony in order to set up the backend API.

Installing and Setting up Symfony for the Backend

To kick-start, use composer to quickly set up a new Symfony project. Alternatively, launch a server on Cloudways PHP hosting by signing up for free and follow the tutorial to set up a Symfony 4 project in just a few minutes.

To create a new standard Symfony project named product-listing-app and install all its dependencies, run the following command:

composer create-project symfony/website-skeleton product-listing-app

Running the Application

Start the application using the development server:

php bin/console server:run

Navigate to http://localhost:8000/ to see the welcome page:

Building the Symfony API

We will manually develop our own API from scratch. To begin with, we will generate doctrine entity and controller for the application.

Start Growing with Cloudways PHP Hosting Today

People Love us because we never compromise on hosting

Product Entity

Generate a  Product entity by running the command below:

$ php bin/console make:entity

If you are quite conversant with Symfony MakerBundle, you can optionally follow the prompt. Otherwise, locate Product.php within ./src/Entity folder and update it with the below-mentioned content:

// ./src/Entity/Product.php

 <?php

 

 namespace App\Entity;

 use Doctrine\ORM\Mapping as ORM;

 

 /**

  * @ORM\Entity(repositoryClass="App\Repository\ProductRepository")

  */

 class Product

 {

     /**

      * @ORM\Id()

      * @ORM\GeneratedValue()

      * @ORM\Column(type="integer")

      */

     private $id;

 

     /**

      * @ORM\Column(name="product", type="string", length=255)

      */

     private $product;

 

     /**

      * @ORM\Column(name="description", type="string", length=255)

      */

     private $description;

 

     /**

      * @ORM\Column(name="favorite_count", type="integer", nullable=true)

      */

     private $favoriteCount;

 

     /**

      * @ORM\Column(name="price", type="string", length=255, nullable=true)

      */

     private $price;

 

     /**

      * @ORM\Column(name="image_url", type="string", length=255, nullable=true)

      */

     private $imageUrl;

 

     /**

      * @return mixed

      */

     public function getId()

     {

         return $this->id;

     }

 

 

     /**

      * @return mixed

      */

     public function getProduct()

     {

         return $this->product;

     }

 

     /**

      * @param mixed $product

      */

     public function setProduct($product): void

     {

         $this->product = $product;

     }

 

 

     /**

      * @return mixed

      */

     public function getDescription()

     {

         return $this->description;

     }

 

     /**

      * @param mixed $description

      */

     public function setDescription($description): void

     {

         $this->description = $description;

     }

 

 

     /**

      * @return mixed

      */

     public function getFavoriteCount()

     {

         return $this->favoriteCount;

     }

 

     /**

      * @param mixed $favoriteCount

      */

     public function setFavoriteCount($favoriteCount): void

     {

         $this->favoriteCount = $favoriteCount;

     }

 

     /**

      * @return mixed

      */

     public function getPrice()

     {

         return $this->price;

     }

 

     /**

      * @param mixed $price

      */

     public function setPrice($price): void

     {

         $this->price = $price;

     }

 

     /**

      * @return mixed

      */

     public function getImageUrl()

     {

         return $this->imageUrl;

     }

 

     /**

      * @param mixed $imageUrl

      */

     public function setImageUrl($imageUrl): void

     {

         $this->imageUrl = $imageUrl;

     }

 }

Nothing strange here, we have only specified the required fields for our database.

Setup Product Controller

In the terminal, run the command mentioned below to create the ProductController

$ php bin/console make:controller ProductController

Open the newly created file and replace the index() method with the content below:

/ ./src/

   /**

    * @Route("/", name="default")

    */

   public function index()

   {

       return $this->render('product/index.html.twig');

   }

Create new product endpoint

Next, to the index() method, add a function to create products:

/**

  * @Route("/products/create", methods="POST")

  */

 public function createProducts(Request $request)

 {

     $product = new Product;

     $product->setProduct($request->get('product'));

     $product->setFavoriteCount(0);

     $product->setDescription($request->get('description'));

     $product->setPrice($request->get('price'));

     $product->setImageUrl($this->imageUploader->uploadImageToCloudinary($request->files->get('image')));

     $this->updateDatabase($product);

 

     return new JsonResponse($this->productRepository->modify($product));

 }

The method above will receive a POST HTTP request from the frontend and will save the details of the newly created products.

Points to note:

You must have noticed the usage of few custom functions like uploadImageToCloudinary(), and updateDatabase(). Let’s try to understand their purpose:

  • uploadImageToCloudinary(): It is meant to easily upload product’s images directly to Cloudinary. It was made and abstracted into a separate class as a service. We will brief this later in this tutorial.
  • updateDatabase(): a function to persist and flush data

JSON Response to List all Products

Next, we will create a function to fetch and return the list of all added products. Add the function below to the ProductController for that purpose:

/**

  * @Route("/products", name="products", methods="GET")

  * @return \Symfony\Component\HttpFoundation\Response

  */

 public function products()

 {

     $products = $this->productRepository->modifyAllProduct();

 

     return $this->response($products);

 }

The function above will return a JSON response that we will loop through by using React and will display it appropriately for all the users.

Ensure that the top of your controller has all the required declared class as shown below:

 namespace App\Controller;

 

 use App\Entity\Product;

 use App\Service\ImageUploader;

 use Doctrine\ORM\EntityManagerInterface;

 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;

 use Symfony\Component\HttpFoundation\JsonResponse;

 use Symfony\Component\HttpFoundation\Request;

 use Symfony\Component\Routing\Annotation\Route;

Within the ProductController we have been referring to a repository named ProductRepository. We will create it later in the tutorial. For now, just before the index(), include the following private properties and add a constructor method:

protected $statusCode = 200;

 

 /**

  * @var EntityManagerInterface

  */

 private $entityManager;

 

 /**

  * @var \App\Repository\ProductRepository|\Doctrine\Common\Persistence\ObjectRepository

  */

 private $productRepository;

 

 /**

  * @var ImageUploader

  */

 private $imageUploader;

 

 /**

  * ProductController constructor.

  * @param EntityManagerInterface $entityManager

  * @param ImageUploader $imageUploader

  */

 public function __construct(EntityManagerInterface $entityManager, ImageUploader $imageUploader)

 {

     $this->entityManager = $entityManager;

     $this->productRepository = $entityManager->getRepository('App:Product');

     $this->imageUploader = $imageUploader;

 }

Create Favorite Count Endpoint

Similar to what is obtainable on most product listing or e-commerce websites, we will add a favorite button to the frontend part of our application. While in order to save the number of likes any of our listed products receive, we will create an API for that by adding the method below to the ProductController after the CreateProducts() method:

/**
  * @Route("/products/{id}/count", methods="POST")
  */
 public function increaseFavoriteCount($id)
 {
     $product = $this->productRepository->find($id);
     if (! $product) {
         return new JsonResponse("Not found!", 404);
     }
     $product->setFavoriteCount($product->getFavoriteCount() + 1);
     $this->updateDatabase($product);
     
     return $this->response($product->getFavoriteCount());
 }

What we have done here is to find a particular product using the id, increase the favoriteCount by one (1) and finally persist with the numberOfCount for the selected product.

Add other important functions within this controller:

/**
  * @param $data
  * @return JsonResponse
  */
 function response($data) {
 
     return new JsonResponse($data, $this->statusCode);
 }
 
 /**
  * @param $errors
  * @return JsonResponse
  */
 function responseWithError($errors) {
     $errorMsg = [
         'errors' => $errors
     ];
     return new JsonResponse($errorMsg, 422);
 }
 
 /**
 * Accept JSON payload
  * @param Request $request
  * @return null|Request
  */
 function acceptJsonPayload(Request $request)
 {
     $data = json_decode($request->getContent(), true);
 
     if (json_last_error() !== JSON_ERROR_NONE) {
         return null;
     }
 
     if ($data === null) {
         return $request;
     }
 
     $request->request->replace($data);
 
     return $request;
 }
 
 
 /**
  * Persist and flush
  * @param $object
  */
 function updateDatabase($object) {
     $this->entityManager->persist($object);
     $this->entityManager->flush();

In case you missed any of the steps, find the complete ProductController() file here on GitHub

Creating the Service to Upload Images to Cloudinary

Set up Cloudinary Account

Cloudinary will manage the image assets of our application. Cloudinary is a media management platform for images and videos. In case you haven’t created an account yet, click here to start:

Once you are done, log in into your dashboard to obtain your account details:

Proceed to your Cloudinary dashboard and take note of your Cloud name, API Key and API Secret:

Create ImageUploader Service

To upload files, Cloudinary provides a PHP-SDK to facilitate the process within any PHP application. We will install this SDK via composer by running the command mentioned below:

$ composer require cloudinary/cloudinary_php

Once the installation process is complete, create a new folder within the src folder and name it Service. Within this newly created folder, create a file named imageUploader.php. This will be a PHP class where the process of uploading images to Cloudinary will be configured. Open  the newly created file and update the content with:

// ./src/Service/ImageUploader.php

 <?php
 namespace App\Service;
 
 use Symfony\Component\HttpFoundation\File\UploadedFile;
 
 class ImageUploader
 {
     public function uploadImageToCloudinary(UploadedFile $file)
     {
         $fileName = $file->getRealPath();
 
         \Cloudinary::config([
             "cloud_name" => getenv('CLOUD_NAME'),
             'api_key' => getenv('API_KEY'),
             "api_secret" =>  getenv('API_SECRET')
         ]);
 
         $imageUploaded = \Cloudinary\Uploader::upload($fileName, [
             'folder' => 'symfony-listing',
             'width' => 200,
             'height' => 200
         ]);
 
         return $imageUploaded['secure_url'];
     }

To avoid unnecessary exposure of our Cloudinary credentials we will save in the default .env file.

Update .env file with your Cloudinary credentials

Open ./env and add the details mentioned below to the bottom

# ./env
...
#Cloudinary Details
CLOUD_NAME=YOUR_CLOUD_NAME
API_KEY=YOUR_API_KEY
API_SECRET=YOUR_API_SECRET

Don’t forget to replace the YUOR_CLOUD_NAME, YOUR_API_KEY and YOUR_API_SECRET with the appropriate details as obtained from the Cloudinary dashboard.

Create Product Repository

Now, we will create the product repository that we included earlier within our ProductController. One of the major benefits of using repository in any Symfony application is to isolate complex queries from our application controller. It is the best practice because it allows you to separate and abstract business logic into a different file entirely.

If you actually generated your controller using the makerBundle as we have done earlier, it would have created the ProductRepository.php file for you. You can find it in ./src/Repository folder. Open it and use the content below:

 

// ./src/Repository/ProductRepository.php
 <?php
 namespace App\Repository;
 
 use App\Entity\Product;
 use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
 use Symfony\Bridge\Doctrine\RegistryInterface;
 
 /**
  * @method Product|null find($id, $lockMode = null, $lockVersion = null)
  * @method Product|null findOneBy(array $criteria, array $orderBy = null)
  * @method Product[]    findAll()
  * @method Product[]    findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
  */
 class ProductRepository extends ServiceEntityRepository
 {
     public function __construct(RegistryInterface $registry)
     {
         parent::__construct($registry, Product::class);
     }
 
     public function modify(Product $product) {
         return [
             'id' => (int) $product->getId(),
             'product' =>  (string) $product->getProduct(),
             'description' => (string) $product->getDescription(),
             'favoriteCount' =>  (int) $product->getFavoriteCount(),
             'price' =>  (string) $product->getPrice(),
             'image' => (string) $product->getImageUrl()
         ];
     }
 
     public function modifyAllProduct()
     {
         $products = $this->findAll();
         $productsArray = [];
         foreach ($products as $product) {
             $productsArray [] = $this->modify($product);
         }
 
         return $productsArray;
     }

Set up and Configure Database

Open up the .env file within your project root directory. You should see something familiar to the following:

###> doctrine/doctrine-bundle ###

# Configure your db driver and server_version in config/packages/doctrine.yaml
DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/db_name

###< doctrine/doctrine-bundle ###

Update the values with the credentials of your MySQL database by replacing:

  1. db_user with your database username
  2. db_password with your database password
  3. db_name with your preferred database name. I have named mine product-listing

Once configured properly, you can use the command below to create your database:

php bin/console doctrine:database:create

Run Migration

Create a migration with:

php bin/console make:migration

And finally:

php bin/console doctrine:migrations:migrate

Building the React Frontend

We have now completed the implementation of logic for the backend API of our application, now let’s proceed with the frontend.

Install React and other Dependencies

We will use Webpack Encore to set up React. Run the commands below sequentially:

# Create package.json, webpack.config.js and add node_modules to .gitignore
composer require symfony/webpack-encore-pack

# Add react dependencies
yarn add --dev react react-dom prop-types babel-preset-react

In addition, install the dependencies below as well:

yarn add axios reactstrap --save

Configure Symfony Encore

Here, we navigate to ./webpack.config.js to configure Webpack Encore and enable React in our application. Edit the file as shown below:

// ./webpack.config.js
var Encore = require('@symfony/webpack-encore');

 

 Encore

     .setOutputPath('public/build/')

     .setPublicPath('/build')

 

     .enableReactPreset()

     .addEntry('js/app', './assets/js/app.js')

     

     .cleanupOutputBeforeBuild()

     .enableBuildNotifications()

     .enableSourceMaps(!Encore.isProduction())

     .enableVersioning(Encore.isProduction())

 ;

 

 module.exports = Encore.getWebpackConfig();

Finally in this section, create a subdirectory named js within the assets folder and then add an app.js file inside of it.

Creating the Application Components

We will create a folder to house all the components for our application. To start, create a subdirectory named components within the ./assets/js/ folder.

Add Product

Create the a component named ProductForm.js within the components folder and add the following content within:

// ./assets/js/components/ProductForm.js

 

 import React, { Component } from 'react';

 import { Button, Form, FormGroup, Label, Alert, Input } from 'reactstrap';

 import { APP } from "../util";

 import axios from 'axios';

 

 

 class ProductForm extends Component {

     constructor (props) {

         super(props);

         this.state = {

             product: null,

             description: null,

             image: null,

             price: null,

             errorMessage:null,

             error: false,

             isLoading: false

         };

         this.fileChangeHandler = this.fileChangeHandler.bind(this);

         this.submitForm = this.submitForm.bind(this);

     }

 

     fileChangeHandler(e) {

         this.setState({

             image: e.target.files[0]

         });

     };

 

 

     submitForm(e) {

         e.preventDefault();

         this.setState({

             isLoading: true,

             error: false,

             errorMessage: ''

         });

 

         const body = new FormData(Form);

         body.append("product", e.target.product.value);

         body.append("description", e.target.description.value);

         body.append("price", e.target.price.value);

         body.append("image", this.state.image);

     }

 

     render() {

         return (

             <Form onSubmit={this.submitForm}>

                 <FormGroup>

                     <label>Product</label>

                     <Input type={'text'} name={'product'} required placeholder='Enter the product name' className={'form-control'}/>

                 </FormGroup>

 

                 <FormGroup>

                     <label>Description</label>

                     <Input type={'text'} name={'description'} required placeholder='Enter product description' className={'form-control'} />

                 </FormGroup>

 

                 <FormGroup>

                     <label>Price</label>

                     <Input type={'text'} name={'price'} required placeholder='Price' className={'form-control'}/>

                 </FormGroup>

 

                 <FormGroup>

                     <Label for="imageFile">Image</Label>

                     <Input type="file" name="file" id="imageFile" onChange={this.fileChangeHandler}/>

                 </FormGroup>

 

                 { this.state.error &&

                 <Alert color="danger">

                     {this.state.errorMessage}

                 </Alert>

                 }

                 <Button type='submit' outline color="success">Add Product</Button>

                 { this.state.isLoading && <Alert color="primary">

                     Loading ....

                 </Alert>}

 

             </Form>

         )

     }

 }

 

 export default ProductForm;

This file is an example of a typical React component. What we have done here, is to import the required module from react and reactstrap. ReactStrap is a Stateless React component for Bootstrap 4. We also imported axios to make API HTTP calls within our component and a custom utility class that contains the BASE_URL for our app.

Next, we set the initial state for our form variables and declare two important methods which are:

  • fileChangeHandler(): We will use it to set the state of the image file for each product
  • submitForm(): will handle the form submission and pass the submitted data to the server.

Finally, we used React render() method to display the product form.

Upload Form Data to the Server

Let’s upload the form to the server. Within the submitForm(), add the method given below just before the closing curly bracket:

...

class ProductForm extends Component {

...

   submitForm(e) {

      ...

       this._uploadToServer(body); // add this line

   }

}



export default ProductForm;

Next, add the _uploadToServer() method below and place it before the render() method of our ProductForm component:

...

class ProductForm extends Component {

  ...

   _uploadToServer(body) {

       axios.post(`${APP.BASE_URL}/${APP.CREATE_PRODUCT_URL}`, body)

           .then(response => {

               this.setState({

                   product: '',

                   isLoading: false,

                   error: false,

                   errorMessage: ''

               });

               this.props.addProduct(response.data)

           }).catch(err => {

           this.setState({

               isLoading: false,

               error: true,

               errorMessage: err.errors

           });

       });

   }



   render() {

   ...

   }

}



export default ProductForm;

Display the list of Products

To display the list of products to users, create another component file within the components folder and name it Products.js. Paste in the content below:

import React, { Component } from 'react';

 import ProductForm from './ProductForm';

 import { Card, CardImg, CardText, CardBody,

     CardTitle, Container, Button, Alert,Row, Col, Badge  } from 'reactstrap';

 import { APP } from '../util'

 import Favorite from './Favorite';

 import axios from 'axios';
class Products extends Component {

 

     constructor(props) {

         super(props);

         this.state = {

             products: null,

             isLoading: null

         };

         this.addProduct = this.addProduct.bind(this);

         this.favoriteIncrease = this.favoriteIncrease.bind(this);

         this.getProducts = this.getProducts.bind(this);

     }

 

     favoriteIncrease(data, id) {

         let products = this.state.products;

         let product = products.find(product => product.id === id);

         product.count = data.count;

         this.setState({

             products: products

         })

     }

 

     componentDidMount() {

         this.getProducts();

     }

 

     getProducts() {

         if (!this.state.products) {

             this.setState({ isLoading: true });

             axios.get(`${APP.BASE_URL}/${APP.PRODUCTS_URL}`).then(response => {

                 this.setState({ products: response.data, isLoading: false });

             }).catch(err => { this.setState({ isLoading: false}); console.log(err) })

         }

     }

 

     addProduct(product) {

         this.setState({

             products: [...this.state.products, product]

         })

     }

 

     render() {

         return (

             <div>

                 {this.state.isLoading &&  <Alert color="primary">

                     Loading ....

                 </Alert>}

                 {this.state.products &&

                 <div>

                     <Container>

                         <Row>

                             <Col xs="3">

                                 <ProductForm addProduct={this.addProduct} />

                             </Col>

                             <Col xl="9">

                                 <Row>

                                     {this.state.products.map(

                                         product =>

                                             <Col xs="4" id={product.id} key={product.id}>

                                                 <Card>

                                                     <CardImg top width="100%"

                                                              src={product.image}

                                                              alt="Card image cap" />

                                                     <CardBody>

                                                         <CardTitle>{product.product}</CardTitle>

                                                         <h4><Badge color="info" pill>{product.price}</Badge></h4>

                                                         <CardText>{product.description}</CardText>

 

                                                         <Favorite favoriteIncrease={this.favoriteIncrease} productId={product.id} favoriteCount={product.favoriteCount}/>

                                                     </CardBody>

                                                 </Card>

                                             </Col>

                                     )}

                                 </Row>

                             </Col>

                         </Row>

                     </Container>

                 </div>

                 }

             </div>

         );

     }

 }

 

 export default Products;

This component will not only display the list of products, but will also house both the ProductForm component and a favorite button component.

We created three different methods named addProduct(), favoriteIncrease() and getProducts() to bind the details of the product added, increase the count of favorite for each product and to fetch all products on component mount respectively.

Creating the Increase Component to Select Product as Favorite

Next, is the Favorite.js component. This component has already been rendered within the Product component. To set it up, create a new file inside components folder and use the content below:

// ./assets/js/components/Favorite.js

 import React, { Component } from 'react';

 import { Form, Button,Badge } from 'reactstrap'

 import { APP } from '../util'

 import axios from 'axios';

 

 class Favorite extends Component {

 

     constructor (props) {

         super(props);

         this.state = {

             id: props.productId,

             count: props.favoriteCount,

         };

         this.onSubmit = this.onSubmit.bind(this);

     }

 

     onSubmit(e) {

         e.preventDefault();

         this.state.count++;

 

         axios.post(`${APP.BASE_URL}/${APP.PRODUCTS_URL}/${this.state.id}/count`).then(res => {

             this.props.favoriteIncrease(res.data, this.state.id)

         });

     }

 

     render() {

         return (

             <Form onSubmit={this.onSubmit}>

                 <Button type='submit' color="primary" outline>

                     Favorite <Badge color="secondary">{ this.state.count }</Badge>

                 </Button>

             </Form>

         )

     }

 }

 

 export default Favorite;

Once clicking the favorite button, we will send a POST HTTP request to the server and increase the favorite count for the particular product.

Create Navigation Bar

We will keep things simple and create a file named Navbar.js as the Navigation bar component. Once you are done, add the following content:

import React, { Component } from 'react';

 

 class Navbar extends Component {

     render(){

         return (

             <nav className="navbar navbar-expand-lg navbar-dark bg-dark">

             <a className="navbar-brand" href="#"> Symfony React Product Listing</a>

         </nav>

     )

     }

 }

 export default Navbar;

Lastly create a file named util.js within the ./assets/js folder. This will house the endpoint of BASE_URL, CREATE_PRODUCT_URL, and PRODUCTS_URL of our application respectively. With this in place, we will have a central location for managing all the existing URLs and adding new ones if required.  Open the newly created file and add the following code:

export const APP = {
   BASE_URL: window.location.origin,
   CREATE_PRODUCT_URL: 'products/create',
   PRODUCTS_URL: 'products'
};

Bringing it all Together by Updating the Main Component

Update the App component within the js folder as shown here :

// ./assets/js/app.js

 

 import React from 'react';
 import ReactDOM from 'react-dom';
 import { Container } from 'reactstrap';
 import Navbar from './components/Navbar';
 import Products from './components/Products';
 
 
 class App extends React.Component {
     render() {
         return (
             <div>
                 <Navbar/>
                 <Container >
                     <Products/>
                 </Container>
             </div>
         )
     }
 }
 
 ReactDOM.render(<App />, document.getElementById('root'));

We imported both the Navbar and Products component and attached it to render within an HTML element with an id named root. We will create this in a bit and use it to mount our React application.

Render the Main Component within Twig

To render the main component, open product homepage and paste the code below in it:

 {% extends 'base.html.twig' %}

 {% block title %} Symfony React Product Listing {% endblock %}

 {% block body %}

   <div id="root"></div>

 {% endblock %}

This template file was automatically generated when we ran a command to create the ProductController earlier. It is found inside ./template/product/index.html

Update the Base Template

Finally update the base template file by including a link to the CDN file for Bootstrap. Also add the compiled JavaScript file by Webpack Encore as shown below:

{# ./templates/base.html.twig #}

 <!DOCTYPE html>
 <html>
 <head>
     <meta charset="UTF-8">
     <title>{% block title %}Welcome!{% endblock %}</title>
     {% block stylesheets %}{% endblock %}
     <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css">
 </head>
 <body>
 {% block body %}{% endblock %}
 {% block javascripts %}
 
     <!-- Include the compiled script-->
     <script type="text/javascript" src="{{ asset('build/js/app.js') }}"></script>
 
 {% endblock %}
 </body>
 </html>

Try the Application

It’s time to test the application. If you had started the server earlier, the application should work fine for you. But if otherwise, open your terminal within the project directory and run the command below to start the development server:

php bin/console server:run

Once the process mentioned above is complete, open another terminal and compile the React application using Webpack Encore by running:

yarn run dev

Now, navigate to http://localhost:8000/ from your favorite browser and start by adding products.

Conclusion

This article serves as a guide to build an awesome application using Symfony as a backend and React to power the frontend logic. Obviously, more features, like Symfony authentication, add to cart feature, and others can be added to the demo – that we have built in this post – in order to make it a full blown mini Symfony ecommerce website.

In addition, for a commercial application, you might want to use API platform instead of crafting your own API from scratch, as it can be daunting and time consuming.

We hope that you will find this product listing application helpful and can easily implement the logic learnt on your existing or new applications. Feel free to download the complete source here on GitHub and add more features as you deem fit.

And lastly, you can share your thoughts and other issues in the comment section below.

Share your opinion in the comment section. COMMENT NOW

Share This Article

Launch PHP websites without the worry of Server Management.

Pre-Installed Optimized Stack with Git, Composer & SSH

Olususi k Oluyemi

A tech enthusiast, programming freak and a web development junkie who loves to embrace new technology.

Get Our Newsletter
Be the first to get the latest updates and tutorials.

Do you like what you read?

Get the Latest Updates

Share Your Feedback

Please insert Content

Thank you for your feedback!