Getting Started with Slim

To begin, you'll need to have PHP 7.4 or higher installed, along with Composer. Install Slim using Composer:

composer create-project slim/slim-skeleton my-api

Navigate into your project directory and start the built-in PHP server:

cd my-api
php -S localhost:8080 -t public

You should now see the Slim welcome page at http://localhost:8080.

Project Structure and Dependencies

A Slim project typically includes the following key directories:

  • public/: Entry point for the application.
  • src/: Contains your application logic, routes, and services.
  • config/: Holds configuration files.
  • dependencies.php: Sets up the dependency container.

Slim uses a dependency injection container powered by PHP-DI. This allows you to define and retrieve services in a clean and testable way.

Setting Up a Dependency Container

In dependencies.php, you can register services and middleware. For example, to register a database connection or logger:

$container->set('logger', function ($c) {
    $settings = $c->get('settings')['logger'];
    $logger = new Monolog\Logger($settings['name']);
    $logger->pushHandler(new Monolog\Handler\StreamHandler($settings['path'], $settings['level']));
    return $logger;
});

Creating a REST API with Routes

Basic Route Definitions

In routes.php, define your API endpoints:

$app->get('/api/users', \App\Action\UserListAction::class)->setName('user-list');
$app->get('/api/users/{id}', \App\Action\UserGetAction::class)->setName('user-get');
$app->post('/api/users', \App\Action\UserCreateAction::class)->setName('user-create');
$app->put('/api/users/{id}', \App\Action\UserUpdateAction::class)->setName('user-update');
$app->delete('/api/users/{id}', \App\Action\UserDeleteAction::class)->setName('user-delete');

Each route points to a class in the App\Action namespace, which should implement the corresponding HTTP method.

Example: UserGetAction

namespace App\Action;

use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Exception\HttpNotFoundException;

class UserGetAction
{
    protected $userRepository;

    public function __construct(UserRepository $userRepository)
    {
        $this->userRepository = $userRepository;
    }

    public function __invoke(Request $request, Response $response, array $args): Response
    {
        $id = $args['id'];
        $user = $this->userRepository->findById($id);

        if (!$user) {
            throw new HttpNotFoundException($request, "User not found");
        }

        $response->getBody()->write(json_encode($user));
        return $response->withHeader('Content-Type', 'application/json');
    }
}

Implementing Middleware

Middleware in Slim is used to process requests before they reach the route handler. For example, you can create a middleware to check for a valid API key:

use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

class AuthMiddleware implements MiddlewareInterface
{
    public function process(Request $request, RequestHandlerInterface $handler): Response
    {
        $apiKey = $request->getHeaderLine('X-API-KEY');

        if ($apiKey !== 'your-secret-key') {
            $response = new \Slim\Psr7\Response();
            return $response->withStatus(401)->withJson(['error' => 'Unauthorized']);
        }

        return $handler->handle($request);
    }
}

Register the middleware in dependencies.php and apply it to routes:

$app->add(new AuthMiddleware());

Or apply it to specific routes:

$app->get('/api/users/{id}', \App\Action\UserGetAction::class)
    ->add(new AuthMiddleware())
    ->setName('user-get');

Best Practices for API Development

PracticeDescription
Use PSR-7Ensures compatibility with other middleware and services
Organize routesGroup related endpoints and use consistent naming
Return proper status codesUse HTTP status codes like 200, 400, 404, 401, 500 for clear responses
Use JSONReturn data in JSON format with Content-Type: application/json
Validate inputAlways validate and sanitize input data
Use middlewareKeep business logic separate from HTTP concerns

Error Handling and Logging

Slim integrates with PSR-3 logging. You can log errors using the dependency container:

$this->container->get('logger')->error("User not found: $id");

You can also define a global error handler to catch exceptions and return a consistent error response:

use Slim\Exception\HttpException;

$app->addErrorMiddleware(true, true, true);

$app->add(function ($request, $handler) {
    try {
        return $handler->handle($request);
    } catch (HttpException $e) {
        return $e->getResponse();
    } catch (\Exception $e) {
        return (new \Slim\Psr7\Response())
            ->withStatus(500)
            ->withJson(['error' => 'Internal Server Error']);
    }
});

Learn more with useful resources