Skip to main content Accessibility Feedback

How to create your own API endpoints with PHP

On Monday, I wrote about serverless and functions-as-a-service.

But I’m going to let you in on a little secret: I think serverless is kind of overrated, and rarely use it for my own projects.

Don’t get me wrong! I love small little functions and microservices. And as serverless vendors go, I think Cloudflare is the best and simplest option.

But I also think it’s a lot easier to just drop a PHP file on my server and run with it. And today, I wanted to share with you exactly how I do that.

Let’s dig in!

How it works

At a high level, it works like this…

  1. I create a PHP file for my API endpoint. For example, an endpoint to join a newsletter might be newsletter.php. I keep all of these in a /api directory on my server.
  2. I make a call to the PHP file using the JavaScript fetch() method. This causes the file to run, do some stuff, and return a JSON response. I could use a server-side language to do this, too.

That’s the high level, but let’s look at some of the implementation details.

PHP utility methods

I use a small handful of PHP utility methods to make getting data and responding to API requests a little bit easier.

First the get_method() function tells me which HTTP method was used for the request: GET, POSt, and so on.

<?php

/**
 * Get the API method
 * @return String The API method
 */
function get_method () {
	return $_SERVER['REQUEST_METHOD'];
}

The get_request_data() method returns an array (in JavaScript it would be an object of named key/value pairs) with all of the request data.

This includes query parameters on the endpoint itself, POST parameters, and any stringified objects or FormData object values in the request body.

<?php

/**
 * Get data object from API data
 * @return Object The data object
 */
function get_request_data () {
	return array_merge(empty($_POST) ? array() : $_POST, (array) json_decode(file_get_contents('php://input'), true), $_GET);
}

The send_response() method set the HTTP response code (defaulting to a success code of 200), returns a stringified JSON response, and ends the function.

<?php

/**
 * Send an API response
 * @param  *       $response The API response
 * @param  integer $code     The response code
 * @param  boolean $encode   If true, encode response
 */
function send_response ($response, $code = 200) {
	http_response_code($code);
	die(json_encode($response));
}

In my PHP endpoints, I prefer to restrict access to Ajax requests (as-in, not let people directly access the file). The is_not_ajax() function runs that check for me.

<?php

/**
 * Check if request is not Ajax
 * @return Boolean If true, is not Ajax
 */
function is_not_ajax () {
	return empty($_SERVER['HTTP_X_REQUESTED_WITH']) || strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) !== 'xmlhttprequest';
}

If you want to also call the file directly (with old-school form submissions, for example), you might either skip this function, or use it to conditionally control how the send_response() function works.

For example, you might choose to echo some text if the file is directly accessed.

An example API

I keep all of these utility functions in a helpers.php file that I include in each of my API endpoint files, but for a single API endpoint, you can put them in the file directly.

At the top of the API file, the first thing I do is check if the request is_not_ajax(). If it’s not, I redirect the user back to wherever they came from.

<?php

// Bail if not an Ajax request
if (is_not_ajax()) {
	header('Location: ' . $_SERVER['HTTP_REFERER']);
	return;
}

I also get the request $method and extract any $data from the request.

<?php

// Bail if not an Ajax request
if (is_not_ajax()) {
	header('Location: ' . $_SERVER['HTTP_REFERER']);
	return;
}

// Get the API method
$method = get_method();

// Get any data sent with the request
// Includes query parameters, post data, and body content
$data = get_request_data();

Handling HTTP methods

I handle responses conditionally based on the request $method used to make the call.

For example, if the $method equals GET, I might grab some data from my server or make an authenticated API call to another API. Then, I use the send_response() method to respond with an array (equivalent to an object in JavaScript) of data.

<?php

// GET request
// Get some data and respond with it
if ($method === 'GET') {

	// You'd normally do stuff here...
	// Let's just send back a success message
	send_response([
		'status' => 'success',
		'message' => 'You did it, dude!',
	]);

}

For a POST request, I might check to make sure the $data object contains all of the required information first. If not, I use the send_response() method to return an error response, passing in an object and error code as arguments.

If everything’s fine, after saving my data and taking any server actions, I’ll send back a successful response instead.

<?php
// POST request
// Store some data or something
if ($method === 'POST') {

	// You'd normally do stuff here...

	// Example: Check that all required data was provided
	if (empty($data['favorite'])) {
		send_response([
			'status' => 'failed',
			'message' => 'Please provide a favorite movie.',
		], 400);
	}

	// If there are no issues, save your data or something...

	// Then, respond with a success
	send_response([
		'status' => 'success',
		'message' => 'This movie was saved to your favorites!',
	]);

}

At the very end, I add a catchall response for any unsupported HTTP methods.

<?php

// All other request methods
send_response(array(
	'code' => 405,
	'status' => 'failed',
	'message' => 'Method not allowed'
), 405);

Calling your endpoint

You can call your endpoint with the fetch() method in JavaScript.

Use the path to your PHP file as the endpoint, and pass in an object with your method, body, and so on. If you’re restricting your API to Ajax requests, make sure to include an X-Requested-With header, with XMLHttpRequest as its value.

fetch('/api/api.php', {
	method: 'POST',
	headers: {
		'X-Requested-With': 'XMLHttpRequest',
	},
	body: JSON.stringify({
		favorite: 'WALL-E'
	})
});

For GET requests, include your parameters as query string parameters on the endpoint instead.

fetch('/api/api.php?favorite=WALL-E', {
	headers: {
		'X-Requested-With': 'XMLHttpRequest',
	}
});

A sample API endpoint

If you want to play around with this yourself, here’s a sample API file you can download.

Drop it somewhere on a server (or run it locally). Then call it with some JavaScript using the fetch() method.