Server rack med blinkende lys
← Tilbage til GuidesMellem - Avanceret

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.

GET
Hent data
POST
Opret ny ressource
PUT
Opdater ressource
DELETE
Slet ressource

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.json

Simpel 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 token

Installé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.

Best Practices

+Brug altid HTTPS i produktion - aldrig plain HTTP for API-trafik
+Validér ALT input - stol aldrig på data fra klienten
+Brug prepared statements til alle database-queries
+Returnér korrekte HTTP-statuskoder (201 for created, 404 for not found, etc.)
+Versionér din API (f.eks. /api/v1/users) for bagudkompatibilitet
+Implementér rate limiting for at beskytte mod misbrug
+Log alle fejl men vis aldrig stack traces i produktion
+Brug et framework som Laravel til komplekse API-projekter