The REST architectural style is designed for network-based applications, specifically client-server applications. But more than that, it is designed for Internet-scale usage, so the coupling between the user agent (client) and the origin server must be as lightweight (loose) as possible to facilitate large-scale adoption. This is achieved by creating a layer of abstraction on the server by defining resources that encapsulate entities (e.g. files) on the server and so hiding the underlying implementation details (file server, database, etc.).

Clients can only access resources using URIs. In other words, the client requests a resource using a URI and the server responds with a representation of the resource.

Wikipedia

In other words, REST is a conception of interaction between independent objects using the HTTP protocol. It includes a set of principles, recommendations for client-server applications on how to interact. Usually, the answer receives in JSON format.

API is an interface of interaction with objects. It includes a set of interaction rules covering creating, reading, updating and deleting operations.

REST API allows the app to interact with objects using REST conceptions.

The task was to develop a filter tool for the website. The filter tool must filter products depending on the user’s answers. The buyer should answer five questions to pick the best product for him.

I chose the technologies and languages to realize the project: PHP, JavaScript, Vue, Vuex, axios.

File structure of the tool:

.
├── app
│   ├── config
│   │   └── database.php
│   ├── objects
│   │   └── product.php
│   └── products
│   └── read.php
├── dist
│   └── js
│   └── app.js
├── index.php
├── mix-manifest.json
├── package.json
├── package-lock.json
├── src
│   ├── app.js
│   ├── components
│   │   └── App.vue
│   └── store
│   ├── index.js
│   ├── modules
│   │   └── answers.js
│   └── mutations.js
└── webpack.mix.js


Backend

app/config/database.php

Database connection file using singleton pattern.

<?php

//app/database.php

require_once __DIR__ . "../../..//bootstrap/init.php";

class Database {

public $conn;

private function __construct() {
$this->conn = null;

try {
$this->conn = new PDO(
"mysql:host=" . $_SERVER['APP_HOST'] .
";dbname=" . $_SERVER['APP_DBNAME'], $_SERVER['APP_USERNAME'], $_SERVER['APP_PASSWORD']);
$this->conn->exec("set names utf8mb4");
} catch(PDOException $exception){
echo "Connection error: " . $exception->getMessage();
}
}

private static $instance = null;

public static function getInstance(){

if(is_null(self::$instance)) {
self::$instance = new self;
}

return self::$instance;

}

private function __clone() {}
private function __wakeup() {}

}

app/objects/product.php

Database queries depend on the user’s answers. We require all the products if the user hasn’t answered any questions.

<?php

//app/objects/product.php

class Product {
private $conn;
private $table_name = "products";

public $answer_one;
public $answer_two;
public $answer_three;
public $answer_four;
public $answer_five;

public function __construct($db){
$this->conn = $db;
}

public function read() {

//Select all the products if the user hasn’t answered any questions
if(
$this->answer_one == 0 &&
$this->answer_two == 0 &&
$this->answer_three == 0 &&
$this->answer_four == 0 &&
$this->answer_five == 0

) {

$query = "SELECT id, name, description, price,
category_id, sub_category_id,
created_at, image_path
FROM " . $this->table_name;

} else {

$sqlReq = '';
$answers = [];

//Get answers array
foreach ($this as $key => $value) {
if (!in_array($key, ['conn', 'table_name']) ) {
if($value) {
array_push($answers, $key);
}
}
}

//Create the SQL request to the database
$i = 0;
foreach ($answers as $answer) {

if ($i == 0) {
$sqlReq = $answer . ' = 1';
} else {
$sqlReq = $sqlReq . ' AND ' . $answer . ' = 1';
}

$i++;
}

$query = "SELECT id, name, description, price,
category_id, sub_category_id,
created_at, image_path
FROM " . $this->table_name . " WHERE " . $sqlReq;

}

//Prepare query
$req = $this->conn->prepare($query);

//Ececute query
$req->execute();

return $req;

}

}

app/products/read.php

REST API endpoint. We send requests to this file.

<?php

//app/products/read.php

header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: access");
header("Access-Control-Allow-Methods: GET");
header("Access-Control-Allow-Credentials: true");
header("Content-Type: application/json");


// Include files
include_once '../config/database.php';
include_once '../objects/product.php';

// Get DB connection
$database = Database::getInstance();
$db = $database->conn;

// Init Product Object
$product = new Product($db);

$data = json_decode(file_get_contents("php://input"));

$product->answer_one = ($data->answer_one->answer) ? $data->answer_one->answer : 0;
$product->answer_two = ($data->answer_two->answer) ? $data->answer_two->answer : 0;
$product->answer_three = ($data->answer_three->answer) ? $data->answer_three->answer : 0;
$product->answer_four = ($data->answer_four->answer) ? $data->answer_four->answer : 0;
$product->answer_five = ($data->answer_five->answer) ? $data->answer_five->answer : 0;

// Request products
$stmt = $product->read();

$num = $stmt->rowCount();

if($num > 0) {
$products_arr = [];

$products_arr['products'] = [];

while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {

extract($row);

$product_item = array(
'id' => $id,
'name' => $name,
'description' => $description,
'price' => $price,
'category_id' => $category_id,
'sub_category_id' => $sub_category_id,
'image_path' => $image_path
);

array_push($products_arr["products"], $product_item);

}

http_response_code(200);
echo json_encode($products_arr);

} else {

http_response_code(200);
echo json_encode(array("message" => "Products not found"), JSON_UNESCAPED_UNICODE);
}

Front-end

src/components/App.vue

<!--src/components/App.vue-->

<template>
<div class="container">
<div class="checkboxes-wrapper">

<div class="answerone-wrapper">
<input type="checkbox" id="answerOne" v-model="answerOne">
<label for="answerOne">1. Lorem ipsum dolor sit amet, consectetur adipisicing elit?</label>
</div>

<div class="answerone-wrapper">
<input type="checkbox" id="answerTwo" v-model="answerTwo">
<label for="answerTwo">2. Lorem ipsum dolor sit amet, consectetur adipisicing elit?</label>
</div>

<div class="answerone-wrapper">
<input type="checkbox" id="answerThree" v-model="answerThree">
<label for="answerThree">3. Lorem ipsum dolor sit amet, consectetur adipisicing elit?</label>
</div>

<div class="answerone-wrapper">
<input type="checkbox" id="answerFour" v-model="answerFour">
<label for="answerFour">4. Lorem ipsum dolor sit amet, consectetur adipisicing elit?</label>
</div>

<div class="answerone-wrapper">
<input type="checkbox" id="answerFive" v-model="answerFive">
<label for="answerFive">5. Lorem ipsum dolor sit amet, consectetur adipisicing elit?</label>
</div>

</div>

<h2>Products:</h2>

<div class="row">
<div
class="col-sm-3 mb-3"
v-for="(product, index) in products"
:key="'product-' + index">
<div class="card">
<img :src="product.image_path" class="card-img-top">
<div class="card-body">
<h5 class="card-title">{{ product.name }}</h5>
<p class="card-text">
Some quick example text to build on the card title and make up the bulk of the card's content.</p>
<a href="#" class="btn btn-outline">Add to Cart</a>
</div>
</div>
</div>
</div>

</div>
</template>

<script>
import { mapGetters, mapActions } from "vuex";
export default {
data() {
return {
test: "test"
}
},
created() {
this.getProducts();
},
computed: {
...mapGetters("userAnswers", ["products", "answers"]),
answerOne: {
get() {
return this.answers.answer_one ? this.answers.answer_one.answer : false;
},
set(answer) {
this.setAnswers({
answerNum: 'answer_one',
answer
})
}
},
answerTwo: {
get() {
return this.answers.answer_two ? this.answers.answer_two.answer : false;
},
set(answer) {
this.setAnswers({
answerNum: 'answer_two',
answer
})
}
},
answerThree: {
get() {
return this.answers.answer_three ? this.answers.answer_three.answer : false;
},
set(answer) {
this.setAnswers({
answerNum: 'answer_three',
answer
})
}
},
answerFour: {
get() {
return this.answers.answer_four ? this.answers.answer_four.answer : false;
},
set(answer) {
this.setAnswers({
answerNum: 'answer_four',
answer
})
}
},
answerFive: {
get() {
return this.answers.answer_five ? this.answers.answer_five.answer : false;
},
set(answer) {
this.setAnswers({
answerNum: 'answer_five',
answer
})
}
},

},
methods: {
...mapActions("userAnswers", ["getProducts", "setAnswers"]),
},
}
</script>


State manager Vuex

src/store.index.js

import Vue from "vue";
import Vuex from "vuex";
import userAnswers from "./modules/userAnswers";

Vue.use(Vuex);

export default new Vuex.Store({
state: {},
mutations: {},
actions: {},
modules: {
userAnswers,
}
});

src/store/modules/userAnswer.js

import mutations from "../mutations";

const axios = require('axios').default;
const { SET_ANSWERS, SET_PRODUCTS } = mutations;

const answersStore = {
namespaced: true,
state: {
answers: {},
products: []
},
getters: {
answers: ({ answers }) => answers,
products: ({ products }) => products
},
mutations: {
[SET_ANSWERS](state, {answerNum, answer}) {
state.answers[answerNum] = {answer};
},
[SET_PRODUCTS](state, products) {
state.products = products;
},
},
actions: {
async getProducts({ state, commit }) {

try {
const data = state.answers;
const response = await axios.post(`./app/products/read.php`, data);

const message = response.data.message;
if(message) {
console.log(message);
commit('SET_PRODUCTS', []);
return message;
}

const products = response.data.products;

console.log(products);

commit('SET_PRODUCTS', products);

return response;
} catch(error) {
console.log(error);
} finally {
console.log("finally");
}
},
setAnswers({ commit, dispatch }, { answerNum, answer }) {
commit("SET_ANSWERS", { answerNum, answer });

dispatch("getProducts", null, { root: false });

console.log(this.state);
}
},
};

export default answersStore;