PHP Backend For Your React App. Cheap. Quick.
A story of a developer who won a job interview by staying away from the mainstream.
Lisa glanced around one more time. She was happy and satisfied with what she saw — expensive gadgets, modern paintings and luxury decoration. She liked the company. But she knew one thing: getting a job here wouldn’t come easy.
The second stage of the interview was about to begin. Lisa shifted her chair slightly to face the interviewers. “Time to discuss your test task, Lisa,” said Mandy, one of the interviewers. Three people interviewed her on that day. Mandy was a project manager, Anton was a senior developer, and Advik represented the Human Resources department.
“We’re currently reviewing a few other candidates, but you, Lisa, was the first person to complete the tasks successfully. So, you’re one of our top candidates. Well done,” continued Mandy, “Anton, would you like to continue from where I stopped?”
“Yes, thanks, Mandy. I was quite impressed with your code Lisa, but I have one question for you. Out of all the backend technologies, why did you choose PHP,” Anton wondered.
“Hm”, Lisa seemed confused, “the task description said we could use whatever backend we liked, and you also confirmed there were no preferences,” replied Lisa.
“Honestly, there’s nothing wrong with your choice. I’m just wondering why you didn’t go with AWS, Azure, or something Node-related.”
“Well, I was looking for a simpler and cheaper option. I had limited time for the task, and I didn’t want to spend much time configuring the server and resolving unexpected issues. Plus, I just graduated, and I got a limited budget,” explained Lisa with a smile.
“AWS and Azure offer free accounts, don’t they?”
“I’ve done a task with AWS for another position. In the task, I spent too much time configuring IAM, roles, and networks. Azure Cosmos DB, in my opinion, is my favourite because it requires minimal configuration. Besides, a free account is limited as I can only get 400ru/s, which is just a few reads/writes per second. And the performance of the project was a critical acceptance criterion if I’m not confusing,” explained Lisa.
“Yes, that’s correct. Performance is critical. If I may ask, have you done any PHP projects before?” asked Anton.
“Not really, but I knew some basics, and I had quite a cheap hosting,” responded Lisa.
“Ha-ha, that sounds savvy! Could you guide me through your backend code because I’m actually not a PHP expert? Let’s start with your project structure,” said Anton.
“Definitely. I structured my project using the KISS principle, which means Keep It Simple, Stupid. I also used the MVC approach. As you can see, each endpoint has its own folder. All the controllers, which I also call handlers, are placed in the named folder. I also got a config folder and another folder for constants and strings,” explained Lisa.
Anton nodded.
“Here’s my DB config file, which not only stores the configuration settings but also provides DB connection for helpers,” continued Lisa.
<?php
class DBClass {
private $host = "<your_host>";
private $username = "<your_user_name>";
private $password = "<your_passowrd>";
private $database = "<your_db_name>";
public $connection;
// get the database connection
public function getConnection(){
$this->connection = null;
try{
$this->connection = new PDO("mysql:host=" . $this->host . ";dbname=" . $this->database, $this->username, $this->password);
$this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->connection->exec("set names utf8");
}catch(PDOException $exception){
echo "Error: " . $exception->getMessage();
}
return $this->connection;
}
}
?>
“Initially, I wanted to put all the database requests here as separate functions, but it looked a bit messy to me,” added Lisa.
“Did you use any framework to simplify API developing,” asked Anton.
“It’s challenging to set up composer [package manager for PHP] on a public web hosting, so I decided to use vanilla PHP,” replied Lisa.
“Ok,” Anton nodded. “Now, let’s go through the backend tasks. The first task was to create a user endpoint, right?”
“Yep, this is the helper for user endpoint to create a user and trigger other required actions.”
<?php
/**
* This is a short snapshot to show an idea. The complete project is tored here:
* https://github.com/wildroo/medium/blob/main/react_php_task
*/
class UserHelper{
private $usersTable = "users";
private $profilesTable = "profiles";
public function createUser($connection, $input){
//check inputs
if(!isset($input['email']) || !isset($input['password']))
{
return $json = array("success" => false, "Info" => "Invalid Inputs");
}
//sanitise inputs
$email = htmlspecialchars(strip_tags($input['email']));
$password = htmlspecialchars(strip_tags(base64_decode($input['password'])));
//check if valid email
if($this->validEmail($email)===false)
{
return $json = array("success" => false, "Info" => "Invalid Email");
}
//check email exists START
$sql = "SELECT * FROM $this->profilesTable WHERE email =:email";
$stmt = $connection->prepare($sql);
$stmt->bindParam(":email",$email);
$stmt->execute();
$profile = $stmt->fetch();
if(isset($profile[0]))
{
return $json = array("success" => false, "Info" => "Email Already Exists");
}
//SET @VAR $UID
$uid = md5(time().rand());
$public_name = $this->splitEmail($email);
//The rest of the function is here:
//https://github.com/wildroo/medium/blob/main/react_php_task/phpbackend/helpers/userHelper.php
}
/**
* The complete project with all other functions and components is here:
* https://github.com/wildroo/medium/blob/main/react_php_task
*/
}
“And here’s the user’s endpoint,” Lisa added.
领英推荐
<?php
header("Content-Type: application/json; charset=UTF-8");
include_once '../config/dbconfig.php';
include_once '../helpers/userHelper.php';
$db = new DBClass();
$userHelper = new UserHelper();
//$data = $connection->query('SELECT NOW()');
//print_r($data);
//main function
//catch action
//@var $request_method
$request_method = isset($_SERVER['REQUEST_METHOD'])?$_SERVER['REQUEST_METHOD']:'';
$data = json_decode(file_get_contents("php://input"),true);
if($request_method == "POST"){
//Get action method
$action = $_REQUEST['action'];
$connection = $db->getConnection();
switch ($action) {
case "createUser":
$json = $userHelper->createUser($connection, $data);
break;
case "resendActivationLink":
$json = $userHelper->resendActivationLink($connection, $data);
break;
case "activateUser":
$json = $userHelper->activateUser($connection, $data);
break;
case "changePassword":
$json = $userHelper->changePassword($connection, $data);
break;
case "validateUser":
$json = $userHelper->validateUser($connection, $data);
break;
default:
$json = array("success" => false, "Info" => "Request method not available!");
}
$connection = null;
echo json_encode($json);
}else{
$json = array("success" => false, "Info" => "Request method not accepted!");
}
?>
“I like the fact that you were concerned about security, and you bound the variables into SQL rather than contacting the strings,” commented Anton.
“Thank you, Anton. I was trying to avoid basic SQL injections,” responded Lisa.
“And I assume all other endpoints follow the same pattern?”
“Yes, that’s correct, except for file uploader and authentication,” responded Lisa.
“Authentication? I think there was no such requirement,” Anton was surprised.
“I had extra time and decided to add some basic security. It’s very simple even though it adds the least level of protection,” replied Lisa.
“Nice,” Anton nodded, “Let’s move to the frontend and let’s start with the project structure.”
“Sounds good,” Lisa navigated to another folder in her VS Code. “Pages folder contains all the components rendering the web pages. All other components are in the related folder, and they are split into subfolders according to their logic. And api_client folder contains the functionality to communicate with endpoints. Here’s how the Create User page looks like,”
import React, { useState } from 'react';
import getToken from '../api_client/auth';
import { uploadImage } from '../api_client/files';
import { createUser } from '../api_client/user';
import '../App.css';
const CreateUserPage = () => {
const [publicName, setPublicName] = useState("");
const [password, setPassword] = useState("");
const [email, setEmail] = useState("");
const [fileUrl, setFileUrl] = useState("");
const [error, setError] = useState("");
const dismissError = () => {
setError("");
}
const handleSubmit = async (event) => {
event.preventDefault();
if (!email) {
setError("Email is required");
}
else if (!publicName) {
setError("Please provide your public name");
}
else if (!password) {
setError("Please enter the password");
}
else {
setError("");
await submitDetailsToBackend();
}
}
const onFileChange = event => {
setFileUrl(event.target.files[0]);
};
const submitDetailsToBackend = async () => {
const uid = await createUser(email, password, publicName);
if(!uid){
setError("Couldn't create a new user.");
return;
}
if(fileUrl){
const token = await getToken(uid);
await uploadImage(token, fileUrl);
}
}
return (
<div className="CreateUserForm">
<form onSubmit={handleSubmit}>
{
error &&
<h3 data-test="error" onClick={dismissError}>
<button onClick={dismissError}>?</button>
{error}
</h3>
}
<label>Email</label>
<input type="text" data-test="username" value={email} onChange={(e) => setEmail(e.target.value)} />
<label>Public Name</label>
<input type="text" data-test="username" value={publicName} onChange={(e) => setPublicName(e.target.value)} />
<label>Password</label>
<input type="password" data-test="password" value={password} onChange={(e) => setPassword(e.target.value)} />
<label>Your Picture</label>
<input type="file" onChange={onFileChange} />
<input type="submit" value="Log In" data-test="submit" />
</form>
</div>
);
}
export default CreateUserPage;
“And here’s an API request, as an example,” continued Lisa.
import Axios from "axios";
import { API_URL } from "../models/constants";
export const uploadImage = async (token, imageUrl) => {
const fileBlob = await urlToObject(imageUrl);
var bodyFormData = new FormData();
bodyFormData.append('file', fileBlob);
await Axios({
method: 'post',
url: API_URL + 'files/index.php',
data: bodyFormData,
headers: {
'Content-Type': 'multipart/form-data',
'Authorization' : token,
'Crossorigin' : 'true'
}
})
.then(function (response) {
console.log(response);
})
.catch(function (response) {
console.log(response);
});
}
//If you need to convert an URL with a pic to file object
const urlToObject = async (imageUrl) => {
const response = await fetch(imageUrl);
const blob = await response.blob();
const file = new File([blob], 'image.jpg', { type: blob.type });
return file;
}
Lisa and Anton discussed the frontend task further, followed by a few more technical and behavioural questions from Advik. In the end, Lisa impressed the interviewers with her knowledge and passion for coding.
Eleven months later, Lisa left the IT company in pursuit of another adventure. But this should be another story. All we need to know is that the eleven-month period was packed with challenging tasks and unbelievable opportunities for Lisa to learn more about software development.
The takeaway
Please note:?The code provided in this story is an extremely simplified snapshot and was provided only as a guide. I would personally go with TypeScript and styled components like Material UI. I’d probably keep vanilla PHP, though. This is because many developers on the market can code it. The security here is primitive, and it’s not suitable for a commercial project. However, it may work for a PoC (proof of concept).
The complete project is stored here:
The motivation
Today, many developers are struggling to choose their backend technology. The reason for this is understandable. Until now, software developers had never had so many great options for their backends. Yet having a lot of backend technologies to choose from does not translate into work productivity. In fact, experience has shown that having too many backend tools often leads to a situation in which a simple project becomes expensive and overcomplicated.
In this story, Lisa showed that following the mainstream and buzzers is not always the most efficient way to work on a smaller project. Hence, it would help if you strived to define your goals and build your solution around them for any projects you undertake.
What’s more, you can now get a $2-a-year PHP hosting service, which comes with a domain name, database, file storage and all the required security perks you need.
Tip 1:?If you need to install a package for PHP or execute a command on the shared server, check?shell_exec?command.
Tip 2:?Axios makes two requests to the endpoints. First, it sends OPTIONS and then the command you sent. Hence, don’t forget to handle OPTIONS in your endpoints.
APSCo Certified Recruitment Professional
3 年Ryan Behague