Een API bouwen in PHP is voor veel backend-developers een logische volgende stap. Je maakt je applicatie toegankelijk voor mobiele apps, single-page applicaties en externe diensten. In deze handleiding leer je stap voor stap hoe je een werkende REST API in PHP opzet, compleet met routing, JSON-responses, authenticatie en nette foutafhandeling.
We bouwen een kleine boeken-API als voorbeeld. Onderweg bespreken we best practices die je direct in productie kunt toepassen.
Wat is een API precies?
Een API (Application Programming Interface) is een contract tussen software. In de webwereld betekent dat meestal: een HTTP-endpoint dat data teruggeeft in JSON-formaat. Een client, bijvoorbeeld een React-app of een andere server, stuurt een request, jouw PHP-code verwerkt die en stuurt een response terug.
De meest gebruikte stijl is REST. REST gebruikt HTTP-methodes (GET, POST, PUT, DELETE) om acties op resources uit te drukken. Een resource is iets als een boek, gebruiker of bestelling.
Een typisch voorbeeld:
GET /books, lijst alle boekenGET /books/42, haal boek 42 opPOST /books, maak een nieuw boek aanPUT /books/42, update boek 42DELETE /books/42, verwijder boek 42
Stap 1: Projectstructuur opzetten
Begin met een schone mappenstructuur. Voor een API hoef je geen views te renderen, dus je kunt het simpel houden:
api/
├── public/
│ └── index.php
├── src/
│ ├── Controllers/
│ ├── Models/
│ └── Router.php
├── config/
│ └── database.php
└── composer.json
De public/index.php is je enige entrypoint. Alle requests komen daar binnen en worden doorgestuurd naar de juiste controller.
Zorg dat je Composer hebt geïnstalleerd. Lees onze gids over Composer en dependency management als je daar nog niet mee werkt.
Initialiseer het project:
composer init
composer require vlucas/phpdotenv
Stap 2: Requests afvangen met een front controller
Configureer je webserver zodat alle requests naar public/index.php gaan. In Apache doe je dat met een .htaccess:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
In index.php bepaal je welke route de client aanroept:
<?php
require __DIR__ . '/../vendor/autoload.php';
header('Content-Type: application/json; charset=utf-8');
$method = $_SERVER['REQUEST_METHOD'];
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$router = new App\Router();
$router->dispatch($method, $uri);
Deze front controller is het hart van je API. Elk request passeert hier.
Stap 3: Een simpele router bouwen
Een router koppelt HTTP-methode en pad aan een controller-actie. Voor uitgebreide uitleg over dit patroon, bekijk onze post over middleware en routing.
<?php
namespace App;
class Router
{
private array $routes = [];
public function add(string $method, string $pattern, callable $handler): void
{
$this->routes[] = compact('method', 'pattern', 'handler');
}
public function dispatch(string $method, string $uri): void
{
foreach ($this->routes as $route) {
if ($route['method'] !== $method) continue;
$pattern = '#^' . preg_replace('#\{(\w+)\}#', '(?P<$1>[^/]+)', $route['pattern']) . '$#';
if (preg_match($pattern, $uri, $matches)) {
$params = array_filter($matches, 'is_string', ARRAY_FILTER_USE_KEY);
($route['handler'])($params);
return;
}
}
http_response_code(404);
echo json_encode(['error' => 'Not found']);
}
}
Registreer je routes in index.php:
$router->add('GET', '/books', [new BookController(), 'index']);
$router->add('GET', '/books/{id}', [new BookController(), 'show']);
$router->add('POST', '/books', [new BookController(), 'store']);
Stap 4: Database koppelen met PDO
Gebruik PDO voor databasetoegang. Dat is veilig, database-agnostisch en ondersteunt prepared statements. Als je nog niet vertrouwd bent met PDO, lees dan eerst werken met databases met PDO.
<?php
namespace App;
use PDO;
class Database
{
private static ?PDO $pdo = null;
public static function connection(): PDO
{
if (self::$pdo === null) {
self::$pdo = new PDO(
'mysql:host=localhost;dbname=api;charset=utf8mb4',
$_ENV['DB_USER'],
$_ENV['DB_PASS'],
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);
}
return self::$pdo;
}
}
Gebruik environment variables voor credentials, commit nooit wachtwoorden in Git.
Stap 5: Een controller schrijven
Controllers bevatten de logica per endpoint. Houd ze dun: ze ontvangen input, delegeren naar een model en sturen JSON terug.
<?php
namespace App\Controllers;
use App\Database;
class BookController
{
public function index(): void
{
$stmt = Database::connection()->query('SELECT * FROM books');
$books = $stmt->fetchAll(\PDO::FETCH_ASSOC);
echo json_encode($books);
}
public function show(array $params): void
{
$stmt = Database::connection()->prepare('SELECT * FROM books WHERE id = ?');
$stmt->execute([$params['id']]);
$book = $stmt->fetch(\PDO::FETCH_ASSOC);
if (!$book) {
http_response_code(404);
echo json_encode(['error' => 'Book not found']);
return;
}
echo json_encode($book);
}
public function store(): void
{
$data = json_decode(file_get_contents('php://input'), true);
if (empty($data['title']) || empty($data['author'])) {
http_response_code(422);
echo json_encode(['error' => 'Title and author are required']);
return;
}
$stmt = Database::connection()->prepare(
'INSERT INTO books (title, author) VALUES (?, ?)'
);
$stmt->execute([$data['title'], $data['author']]);
http_response_code(201);
echo json_encode(['id' => Database::connection()->lastInsertId()]);
}
}
Let op file_get_contents('php://input'). JSON-payloads komen niet in $_POST binnen, je moet de raw body uitlezen en decoderen.
Stap 6: Authenticatie toevoegen
De simpelste vorm is een API-token in de Authorization-header. Voor productie raden we JSON Web Tokens (JWT) aan.
function authenticate(): void
{
$headers = getallheaders();
$token = $headers['Authorization'] ?? '';
if (!str_starts_with($token, 'Bearer ')) {
http_response_code(401);
echo json_encode(['error' => 'Unauthorized']);
exit;
}
$token = substr($token, 7);
// Valideer token tegen database of JWT-signature
}
Roep deze functie aan in middleware of aan het begin van beschermde controller-acties. Voor een diepere duik in JWT raden we de officiële specificatie op jwt.io aan.
Stap 7: Consistente foutafhandeling
Een goede API stuurt voorspelbare foutresponses. Bouw een centrale error handler:
set_exception_handler(function (\Throwable $e) {
http_response_code(500);
echo json_encode([
'error' => 'Server error',
'message' => $_ENV['APP_DEBUG'] ? $e->getMessage() : 'Something went wrong'
]);
});
Toon nooit stack traces in productie, dat is een beveiligingsrisico. Log ze in plaats daarvan naar een bestand of monitoringdienst.
Stap 8: Valideren en versioneren
Input valideren voorkomt bugs en aanvallen. Voor complexe validatie kun je packages zoals respect/validation via Composer installeren. Bouw je het zelf, check dan altijd types, lengtes en verplichte velden.
Versioneer je API via de URL of een header:
GET /v1/books
GET /v2/books
Zo kun je breaking changes invoeren zonder bestaande clients te breken.
Wanneer kies je voor een framework?
Voor prototypes of kleine interne APIs is pure PHP prima. Zodra je applicatie groeit, met authenticatie, caching, rate limiting, jobs, wordt een framework aantrekkelijk. Lees onze Laravel introductie om te zien wat zo'n framework oplost.
Laravel biedt out of the box API-resources, Sanctum voor tokens, Eloquent voor database-abstractie en een volwassen ecosysteem. Slim is een lichtere optie als je alleen routing en middleware nodig hebt.
Voor de architectuur achter robuuste APIs is het MVC-pattern vrijwel universeel. Controllers blijven dun, models bevatten de businesslogica en views zijn in een API simpelweg de JSON-serialisatie.
Testen met curl of Postman
Test je endpoints snel vanaf de command line:
curl -X GET http://localhost:8000/books
curl -X POST http://localhost:8000/books \
-H "Content-Type: application/json" \
-d '{"title":"PHP Advanced","author":"Jane Doe"}'
Tools zoals Postman of Insomnia bieden een GUI en maken het makkelijker om complexe requests te delen met je team.
Veelgestelde vragen
Heb ik een framework nodig om een API te bouwen in PHP?
Nee, je kunt prima een API bouwen in pure PHP. Voor grotere projecten bieden frameworks zoals Laravel of Slim echter routing, validatie en middleware out of the box, waardoor je sneller productief bent.
Wat is het verschil tussen REST en GraphQL?
REST gebruikt meerdere endpoints met HTTP-methodes zoals GET en POST, terwijl GraphQL één endpoint heeft waar je exact opvraagt wat je nodig hebt. REST is eenvoudiger te implementeren, GraphQL flexibeler voor complexe data.
Hoe beveilig ik mijn PHP API?
Gebruik HTTPS, authenticeer via API-tokens of JWT, valideer alle input, voorkom SQL-injectie met prepared statements en beperk rate limits. Log verdachte requests en exposeer nooit interne foutmeldingen.
Welke HTTP-statuscodes moet ik gebruiken?
Gebruik 200 voor succes, 201 voor aangemaakte resources, 400 voor foute input, 401 voor ontbrekende authenticatie, 404 voor niet gevonden en 500 voor serverfouten. Consistente statuscodes maken je API voorspelbaar.
Hoe documenteer ik een PHP API?
Gebruik OpenAPI (Swagger) om je endpoints, parameters en responses te beschrijven. Tools zoals Swagger UI genereren automatisch interactieve documentatie die developers direct kunnen uitproberen.