Byg en REST API med PHP
Fra routing til autentificering — sådan bygger du en professionel API fra bunden.
Foto: Domaintechnik Ledl.net / Unsplash
Hvad er en REST API?
En REST API (Representational State Transfer) er en webservice der lader klienter (frontend-apps, mobile apps, andre servere) kommunikere med din server via HTTP-requests. Klienten sender en request med en HTTP-metode (GET, POST, PUT, DELETE), og serveren svarer med data — typisk JSON.
Projektstruktur
En ren API-struktur holder routing, logik og data adskilt. Her er en minimal struktur til en PHP REST API uden framework:
api/
├── public/
│ └── index.php # Entry point - alle requests starter her
├── src/
│ ├── Router.php # URL routing
│ ├── Request.php # HTTP request wrapper
│ ├── Response.php # JSON response helper
│ └── Controllers/
│ └── UserController.php
├── config/
│ └── database.php
└── composer.jsonSimpel Router
Routeren matcher URL-paths til controller-metoder. Alle requests sendes til index.php via en .htaccess rewrite-regel.
<?php
// src/Router.php
class Router
{
private array $routes = [];
public function get(string $path, callable $handler): void
{
$this->routes['GET'][$path] = $handler;
}
public function post(string $path, callable $handler): void
{
$this->routes['POST'][$path] = $handler;
}
public function put(string $path, callable $handler): void
{
$this->routes['PUT'][$path] = $handler;
}
public function delete(string $path, callable $handler): void
{
$this->routes['DELETE'][$path] = $handler;
}
public function resolve(string $method, string $uri): mixed
{
// Fjern query string
$path = parse_url($uri, PHP_URL_PATH);
// Find matchende route
$handler = $this->routes[$method][$path] ?? null;
if ($handler === null) {
http_response_code(404);
return json_encode(['error' => 'Route not found']);
}
return $handler();
}
}JSON Responses
En API skal altid returnere konsistente JSON-svar med korrekte HTTP status-koder. Her er en simpel Response-klasse:
<?php
// src/Response.php
class Response
{
public static function json(
mixed $data,
int $status = 200
): never {
http_response_code($status);
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
echo json_encode($data, JSON_UNESCAPED_UNICODE);
exit;
}
public static function error(
string $message,
int $status = 400
): never {
self::json(['error' => $message], $status);
}
}
// Brug i en controller:
// Response::json(['users' => $users]);
// Response::error('User not found', 404);CRUD Endpoints
Her er et komplet eksempel på en UserController med alle fire CRUD-operationer. Bemærk brugen af OOP og type declarations:
<?php
// src/Controllers/UserController.php
class UserController
{
public function __construct(
private readonly PDO $db
) {}
// GET /api/users
public function index(): void
{
$stmt = $this->db->query(
'SELECT id, name, email FROM users ORDER BY id'
);
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
Response::json(['users' => $users]);
}
// POST /api/users
public function store(): void
{
$input = json_decode(
file_get_contents('php://input'),
true
);
// Validering
if (empty($input['name']) || empty($input['email'])) {
Response::error('Name and email are required', 422);
}
// Prepared statement (sikkerhed mod SQL injection)
$stmt = $this->db->prepare(
'INSERT INTO users (name, email) VALUES (:name, :email)'
);
$stmt->execute([
'name' => $input['name'],
'email' => $input['email'],
]);
Response::json(
['id' => $this->db->lastInsertId()],
201
);
}
// DELETE /api/users/{id}
public function destroy(int $id): void
{
$stmt = $this->db->prepare(
'DELETE FROM users WHERE id = :id'
);
$stmt->execute(['id' => $id]);
if ($stmt->rowCount() === 0) {
Response::error('User not found', 404);
}
Response::json(['deleted' => true]);
}
}Sikkerhed
Brug altid prepared statements til database-queries. Læs mere om SQL Injection forebyggelse.
JWT Autentificering
JSON Web Tokens (JWT) er den mest udbredte metode til API-autentificering. Klienten sender et token i Authorization headeren, og serveren verificerer det:
<?php
// Simpel JWT-verificering (brug firebase/php-jwt i produktion)
function authenticateRequest(): array
{
$header = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
if (!str_starts_with($header, 'Bearer ')) {
Response::error('Missing authorization token', 401);
}
$token = substr($header, 7);
try {
// Dekod og verificér token
$payload = Firebase\JWT\JWT::decode(
$token,
new Firebase\JWT\Key(
getenv('JWT_SECRET'),
'HS256'
)
);
return (array) $payload;
} catch (\Exception $e) {
Response::error('Invalid token', 401);
}
}
// Brug i route:
// $user = authenticateRequest();
// UserController::index(); // Kun tilgængelig med gyldigt tokenInstallér JWT-biblioteket med Composer: composer require firebase/php-jwt
Error Handling
En professionel API returnerer strukturerede fejlsvar med korrekte HTTP-statuskoder. Sæt en global error handler i din index.php:
<?php
// public/index.php
// Fang alle uventede fejl
set_exception_handler(function (Throwable $e) {
$status = $e->getCode() >= 400 && $e->getCode() < 600
? $e->getCode()
: 500;
// Log fejlen (vis ALDRIG stack traces i produktion)
error_log($e->getMessage());
Response::json([
'error' => 'Internal server error',
'message' => getenv('APP_DEBUG') === 'true'
? $e->getMessage()
: 'Something went wrong',
], $status);
});Læs mere om PHP Error Handling og sikkerhedsguides.