Building a Basic API Server in PHP: A Journey from Concept to Implementation

Smith Kruz
4 min readJul 1, 2024

--

Introduction

In this article, I’ll guide you through the process of building a basic API server using PHP as part of my journey in the HNG Internship program. I’ll explain the project’s structure, the challenges I faced, and how I successfully implemented the solution to achieve the desired functionality.

Background: HNG Internship Task

As part of the HNG Backend Track Stage One task, I was tasked with creating a simple API server that fetches weather data based on client IP addresses. This project allowed me to apply my PHP programming skills in a real-world scenario and integrate with external APIs to enhance functionality.

Project Overview

Structure

The project is organized into the following components:

  • Controllers
  • apicontroller.php: Manages API requests for weather data based on client IP.
  • indexcontroller.php: Handles routing and main API functionality.
  • Files
  • .htaccess: Configures URL rewriting for cleaner API endpoints.
  • index.php: Main entry point for handling API requests and routing.
  • Documentation
  • readme.md: Provides project documentation and usage instructions.
Code structure

Challenges Faced

Challenge 1: Integrating WeatherAPI.com

Integrating with WeatherAPI.com posed initial challenges, including:

  • Acquiring an API key and understanding the API documentation.
  • Constructing dynamic API requests based on client IP addresses.
  • Parsing and formatting JSON responses to extract relevant weather data.
API Call

Challenge 2: Handling Client IP Addresses

Dealing with client IP addresses required:

  • Addressing differences in IP address retrieval between local and production environments.
  • Ensuring accurate transmission of IP addresses to WeatherAPI.com for precise weather data retrieval.

Challenge 3: Structuring Controllers

Organizing the PHP controllers (apicontroller.php and indexcontroller.php) involved:

  • Defining clear responsibilities for each controller to maintain a modular and scalable codebase.
  • Ensuring each controller method efficiently processed API requests and responses.
<?php

// index.php

// Load Configurations
require_once __DIR__ . '/config.php';

// Set HTTP header for JSON response
header('Content-Type: application/json');

// Define the base path for your controllers
define('CONTROLLER_PATH', __DIR__ . '/controllers/');

// Function to handle the routing
function handleRequest($url) {
// Sanitize the URL parameter to prevent directory traversal attacks
$url = preg_replace('/[^a-zA-Z0-9\/]/', '', $url);

// Remove leading slash if present
$url = ltrim($url, '/');

// Separate the URL into parts based on '/'
$parts = explode('/', $url);

// Extract controller and method
$controller = !empty($parts[0]) ? ucfirst($parts[0]) . 'Controller' : 'IndexController'; // Default to IndexController
$method = !empty($parts[1]) ? $parts[1] : 'index'; // Default method 'index' if not specified
$args = array_slice($parts, 2); // Arguments passed after method name

// Construct the file path based on the controller
$file_path = CONTROLLER_PATH . strtolower($controller) . '.php';

// Check if the controller file exists
if (file_exists($file_path)) {
// Include the controller file
require_once $file_path;

// Check if the class exists
if (class_exists($controller)) {
$controller_instance = new $controller();

// Check if the method exists in the controller
if (method_exists($controller_instance, $method)) {
// Call the method with arguments and capture the result
call_user_func_array([$controller_instance, $method], $args);

} else {
// Method not found error
http_response_code(404);
echo json_encode(['error' => 'Path not found']);
}
} else {
// Controller not found error
http_response_code(404);
echo json_encode(['error' => 'Route not found']);
}
} else {
// Endpoint not found error
http_response_code(404);
echo json_encode(['error' => 'Endpoint not found']);
}
}

// Extract the requested URL path from $_SERVER['REQUEST_URI']
$request_uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$request_uri = urldecode($request_uri);

// Remove the base directory from the request URI, if applicable
$base_dir = str_replace('\\', '/', dirname($_SERVER['SCRIPT_NAME']));
if ($base_dir !== '/') {
$request_uri = preg_replace('#^' . preg_quote($base_dir) . '#', '', $request_uri);
}

// Handle the request
handleRequest($request_uri);
?>

Implementation Details

Fetching Weather Data

The apicontroller.php was instrumental in:

  • Dynamically constructing API requests to WeatherAPI.com using client IP addresses.
  • Extracting temperature and location data from JSON responses.
  • Formatting responses as JSON for seamless integration with client applications.
<?php
// routes/apiController.php

class apiController
{
public function index()
{
echo json_encode(['status' => 200, 'message' => 'API V1.0 currently running.']);
}
public function hello()
{
$visitor_name = trim(@$_GET['visitor_name'], "\"' \t\n\r\0\x0B");
$visitor_name = empty($visitor_name) ? "Guest" : $visitor_name;
$ip = htmlspecialchars($_SERVER['REMOTE_ADDR']);

$weather_api_key = API_KEY;
$response = @json_decode(file_get_contents("https://api.weatherapi.com/v1/current.json?q=$ip&key=$weather_api_key"));

echo json_encode([
"client_ip" => $ip, // The IP address of the requester
"location" => $response->location->name, // The city of the requester
"greeting" => "Hello, $visitor_name!, the temperature is " . $response->current->temp_c . " degrees Celcius in " . $response->location->name
]);
}
}

Routing and Request Handling

The index.php and .htaccess facilitated:

  • Routing incoming API requests to appropriate controllers (apicontroller.php and indexcontroller.php).
  • Implementing URL rewriting using .htaccess for intuitive and user-friendly API endpoints.
# .htaccess file for url rewrite to enable /hello interpreted as /hello.php
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /

# Rewrite requests to index.php?url=<request>
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?url=$1 [QSA,L]
</IfModule>

Conclusion

Building this basic API server has been a valuable learning experience as part of my journey in the HNG Internship program. It has enhanced my skills in PHP development, API integration, and problem-solving. Overcoming challenges such as integrating external APIs and structuring controllers has deepened my understanding of backend development principles.

This project not only fulfills the internship task requirements but also serves as a stepping stone in my career as a backend developer. I look forward to applying these skills in future projects and continuing to grow in my journey as a software developer.

Deployed at: https://smithkruz.pxxl.space/
Github Code Repo: https://github.com/samsmithKruz/hng-stage-1
HNG Internship: https://hng.tech/
HNG Premium: https://hng.tech/checkout/hng-internship

--

--

Smith Kruz

Experienced backend developer proficient in PHP, Node.js, Express, and MySQL. Dedicated to crafting efficient and secure web solutions. Let's innovate together!