Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Slim Skeleton Laravel Package

slim/slim-skeleton

Official Slim 4 skeleton application: a minimal starting point for building PHP microservices and APIs, with PSR-7 HTTP handling, routing, middleware, dependency injection, and Twig templates preconfigured.

View on GitHub
Deep Wiki
Context7

Getting Started

composer create-project slim/slim-skeleton my-api gives you a working PSR-7 application in under a minute — no Artisan, no service providers, no Eloquent. Think of it as the scaffold you'd get if Laravel stopped at "router + container + middleware" and handed the rest to you.

First three commands:

composer create-project slim/slim-skeleton my-api
cd my-api
composer start          # php -S localhost:8080 -t public public/index.php
curl localhost:8080/hello/world

Where to look first, in this order:

  1. public/index.php (or app/bootstrap.php in newer revisions) — the whole bootstrap is ~40 lines: build PHP-DI container → load settings.php → dependencies.php → repositories.php → create app → register middleware → register routes → handle request → emit response. Read it once and you understand the entire lifecycle.
  2. app/routes.php — returns a callable receiving Slim\App; three demo routes (/, /hello/{name}, /users).
  3. app/settings.php — namespaced config array (logger, db, displayErrorDetails) fed into App\Application\Settings\Settings.
  4. app/dependencies.php — where you bind services (the closest thing to a service provider).
  5. app/Application/Actions/Action.php — the base class every action extends; it gives you respondWithData(), respondWithError(), getFormData(), resolveArg().

First real use case: clone the shape of ListUsersAction / ViewUserAction and the UserRepository interface + InMemoryUserRepository implementation. That's the whole "vertical slice" idiom the skeleton is teaching: Domain interface → Infrastructure implementation → Action → Route → DI binding in repositories.php. Swap the in-memory repo for a PDO/Doctrine one and you have a real endpoint.

Implementation Patterns

Single-action controllers everywhere. Actions are invokable classes resolved by PHP-DI. Constructor-inject whatever you need and extend Action for the response helpers:

final class CreateOrderAction extends Action
{
    public function __construct(
        LoggerInterface $logger,
        private OrderRepository $orders,
    ) {
        parent::__construct($logger);
    }

    protected function action(): Response
    {
        $data = $this->getFormData();                 // merged body/query
        $order = $this->orders->create($data['sku']);

        return $this->respondWithData(['id' => $order->id], 201);
    }
}

Interface bindings live in repositories.php. PHP-DI autowires concrete classes, but any interface (your repository ports, mailers, clients) must be explicitly mapped:

return function (ContainerBuilder $containerBuilder) {
    $containerBuilder->addDefinitions([
        UserRepository::class  => \DI\autowire(InMemoryUserRepository::class),
        OrderRepository::class => \DI\autowire(PdoOrderRepository::class),
    ]);
};

Group and name routes for anything beyond toy size. RouteCollectorProxy + ->setName() + ->add() mirrors Laravel's Route::group()->middleware():

$app->group('/api/v1', function (RouteCollectorProxy $group) {
    $group->get('/users', ListUsersAction::class)->setName('users.list');
    $group->get('/users/{id:[0-9]+}', ViewUserAction::class);
    $group->post('/users', CreateUserAction::class);
})->add(AuthMiddleware::class);

$app->get('/users/{id}', ViewUserAction::class)->setName('users.view');

Middleware is PSR-15, so anything you own is portable — a JWT middleware here works unchanged in Mezzio or Laminas. Register global middleware in app/middleware.php; note the skeleton uses addBodyParsingMiddleware() so getParsedBody() returns decoded JSON/form bodies (Slim 4 no longer does this implicitly for JSON).

Config injection. Never read getenv() inside an action. Inject SettingsInterface and read a namespace:

$stripeKey = $this->settings->get('stripe')['secret'];

Testing. The skeleton's PHPUnit base sets up the app for you; test through the front controller rather than mocking:

$app     = $this->getAppInstance();
$request = $this->createRequest('GET', '/users')->withHeader('Accept', 'application/json');
$response = $app->handle($request);

$this->assertSame(200, $response->getStatusCode());
$this->assertJsonStringEqualsJsonString('{"data":[...]}', (string) $response->getBody());

Console tasks (if your revision ships bin/console) use Symfony Console — that's your Artisan substitute for cron jobs, imports, and queue drains.

Gotchas and Tips

  • This is a template, not a dependency. composer create-project copies a snapshot and drops VCS history. You will never composer update the skeleton itself; the last tagged release is from late 2022, so audit slim/slim, php-di/php-di, and phpunit versions against your PHP target (PHPUnit 9 → 10 is a config rewrite) before starting.
  • Middleware order is reversed. Slim 4 executes the last added middleware first. That's why ErrorMiddleware is registered last in public/index.php — it must be the outermost layer to catch exceptions from everything else. If you add CORS after it, your 500s lose their headers.
  • PHP-DI autowiring silently fails on interfaces. "No entry found for App\Domain\User\UserRepository" means you forgot the binding in repositories.php — nothing to do with your constructor.
  • PSR-7 responses are immutable. $response->withHeader(...) returns a new object; forgetting $response = $response->withHeader(...) is the classic silent no-op. Same for $request->withAttribute().
  • Body writes happen in place. $response->getBody()->write(...) mutates the stream; respondWithData() already handles the rewind. Rolling your own JSON response without rewind() yields empty bodies in some SAPI setups.
  • getenv() is unreliable under PHP-FPM. Skeleton settings read getenv('APP_ENV'); with FPM's default clear_env = on, those variables are invisible. Either flip clear_env or add vlucas/phpdotenv and load it at the very top of public/index.php, before settings.php is required — order matters.
  • displayErrorDetails must be false in production. It's driven by APP_ENV === 'development' and controls both whoops-style output and the ShutdownHandler.
  • getParsedBody() returns null for text/plain and for JSON if addBodyParsingMiddleware() isn't registered. Validate the body yourself; the skeleton ships no validator — bring respect/validation or hand-rolled checks.
  • resolveArg() returns strings. Route placeholders are always strings; cast before passing to a repository or you'll get subtle type-mismatch bugs with strict types.
  • No 404/405 magic without routing middleware. If you call $app->handle() manually (tests, custom entrypoint) and skip addRoutingMiddleware(), unmatched routes throw HttpNotFoundException instead of rendering a 404.
  • Docroot is public/. Never point nginx/Apache at the project root; app/ and vendor/ must stay outside it. composer start gets this right — your production vhost must too.
  • Run composer dump-autoload after adding new namespaces. PSR-4 maps App\ → app/; new top-level namespaces need a composer.json edit plus a dump.
  • Nothing is included that Laravel gives you free. No ORM, migrations, queue, mailer, auth, sessions, CSRF, or cache. Budget for each consciously: Doctrine/Migrations, symfony/mailer, firebase/php-jwt, league/container-style extras — this is where a Slim project's real cost lives.
  • Enable PHP-DI compilation in production ($containerBuilder->enableCompilation(__DIR__ . '/../var/cache')) — it's disabled by default in the skeleton's public/index.php with a if (false) stub. Clear that cache directory on every deploy.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource