nih/app-skeleton

Lightweight starter project for PSR-7/PSR-15 HTTP applications.

Maintainers

Package info

github.com/nih-soft/app-skeleton

Documentation

Type:project

pkg:composer/nih/app-skeleton

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

0.1.2 2026-04-11 22:08 UTC

This package is auto-updated.

Last update: 2026-04-11 22:10:31 UTC


README

Lightweight starter project for PSR-based HTTP applications built on top of nih/http-kernel.

It is intended for developers who want a ready-to-run application structure without committing to a large full-stack framework and without getting boxed into a tiny microframework.

If your first question is "why would I add this package at all?", start with docs/package-pains.md. It explains what problem each core NIH package is meant to solve and when it tends to become useful.

Contents

Why this skeleton exists

PSR-7, PSR-15, PSR-17, and PSR-11 are extremely useful because they created a broad ecosystem of reusable HTTP messages, middleware, handlers, factories, and containers.

What those standards do not define is the shape of a complete HTTP application. In practice that often leaves two common choices:

  • large frameworks that provide everything, but also bring a lot of structure, conventions, and moving parts
  • microframeworks that start fast, but become awkward once the application needs richer routing, explicit composition, multiple pipelines, or host-aware rules

nih/app-skeleton targets the middle ground:

  • fast start for a small HTTP application
  • ability to start writing business logic immediately instead of first assembling framework plumbing
  • explicit and lightweight project structure
  • PSR-compatible HTTP stack from day one
  • room to grow into more advanced routing and middleware composition

If you want the package-by-package view of which concrete pain each part of the stack is solving, see docs/package-pains.md.

Requirements

  • PHP 8.4 or 8.5
  • Composer

Quick start

For a new application, use Composer project creation:

composer create-project nih/app-skeleton my-app
cd my-app
composer test
composer serve

Then open:

  • http://127.0.0.1:8080/
  • http://127.0.0.1:8080/health

The goal of the skeleton is that after installation you can move straight to application behavior: add routes, handlers, middleware, and domain code without first building a custom public entry point, container wiring strategy, or bootstrap lifecycle from scratch.

If you are working on the skeleton itself rather than creating a new app from it:

git clone https://github.com/nih-soft/app-skeleton.git
cd app-skeleton
composer install
composer test

How a request flows

The default mental model is deliberately small:

  1. public/index.php loads Composer and reads the ordered bootstrap list from config/app.php.
  2. Bootstrap classes configure services, routes, the main pipeline, and the error pipeline.
  3. The default main pipeline is RouteMatchMiddleware -> RouteDispatchMiddleware, with NotFoundHandler as the fallback final handler.
  4. A matched route resolves the action class from the container and executes it.
  5. Unhandled exceptions go through the separate error pipeline: ErrorLoggingMiddleware -> ErrorFormatMiddleware, with PlainTextErrorHandler as the fallback final handler.

That is enough to understand where to make most first changes: routes and services live in bootstraps, request behavior lives in middleware and actions.

First change in 2 minutes

Once the app is running, the fastest way to touch real behavior is:

Typical example:

$app->routes->path('/about')
    ->action('', App\Action\AboutAction::class, '__invoke', ['GET']);

$app->pipeline->prepend(App\Http\Middleware\RequestIdMiddleware::class);

What this means in practice:

  • path('/about') creates the /about branch in the route tree
  • action('', ...) attaches the action to that exact branch root rather than to a child path segment
  • App\Action\AboutAction::class and App\Http\Middleware\RequestIdMiddleware::class are class strings; the container resolves them when the request is dispatched
  • prepend() makes the middleware run before routing, which is the usual choice for request-wide behavior such as request IDs, auth context, or body parsing

If you use plain append() in AppBootstrap, that middleware is added after RouteDispatchMiddleware and usually acts as fallback behavior for NOT_FOUND pass-through requests rather than for successfully dispatched routes.

Bootstrap classes define application configuration. Actions and middleware handle requests.

When routes start growing

If a whole route subtree follows naming conventions, one branch rule can cover many actions without listing them one by one:

use NIH\Router\Strategy\PathToClassStrategy;

$app->routes->path('/forums/')
    ->strategy(PathToClassStrategy::class, [
        'namespace' => 'App\\Action\\Forums',
    ]);

Typical mappings for that subtree:

  • GET /forums/ -> App\Action\Forums\Get
  • GET /forums/view -> App\Action\Forums\ViewGet

What you get out of the box

  • a thin public entry point in public/index.php
  • ordered bootstraps in config/app.php
  • app-specific wiring in src/Bootstrap/
  • demo actions in src/Action/
  • a default main pipeline: RouteMatchMiddleware -> RouteDispatchMiddleware -> NotFoundHandler
  • explicit error pipeline wiring with content-negotiated error rendering and logging
  • host-agnostic starter routing by default
  • a lightweight PSR-based stack that can keep growing with the application

Those files are examples of a minimal layout, not mandatory framework rules. You may keep everything in public/index.php, move bootstrap lists elsewhere, or organize the project in any other way that fits the application.

What this skeleton intentionally does not include

This is a minimal skeleton, not a batteries-included framework distribution.

Out of the box it does not include:

  • a production logging stack beyond the minimal app-local logger used for error logging examples
  • an ORM
  • database access or migrations
  • higher-level framework subsystems such as auth, forms, templating, or admin tooling

You are expected to add the libraries that fit your application and keep the project structure that fits your team. The skeleton is intentionally open in that regard.

Core NIH packages

If your first question is "why would I add this package and what pain does it solve?", start with docs/package-pains.md.

If you already know why you need a package and want the package-level API and runtime documentation, start with the README of each core package:

  • nih/http-kernel: minimal application kernel and bootstrap lifecycle for PSR-7, PSR-11, PSR-15, and PSR-17 based applications
  • nih/container: PSR-11 container with autowiring for explicit application composition
  • nih/router: tree-based router with runtime-built configuration, site-aware matching, URL generation, and PSR-15 middleware integration
  • nih/middleware-dispatcher: middleware dispatcher designed for lazy resolution and large or dynamic PSR-15 pipelines

nih/http-kernel is the top-level runtime package. The skeleton also declares nih/container and nih/router directly because application code and bootstrap configuration use their APIs explicitly. nih/middleware-dispatcher remains a transitive dependency pulled in by the kernel and router packages.

What this stack makes easier

  • keeping boot-time configuration explicit in PHP instead of spreading it across hidden framework conventions
  • separating normal request flow from error logging and negotiated error rendering
  • starting with a few explicit routes and growing into larger route trees without changing routing model
  • placing middleware before routing, on route branches, or in fallback positions instead of forcing everything into one flat global stack
  • resolving actions and middleware lazily from the container through class strings
  • growing the application without adding a build step for container compilation or route compilation
  • reusing PSR-compatible middleware, handlers, loggers, and supporting packages without wrapping everything in framework-specific HTTP abstractions
  • integrating new subsystems gradually instead of committing to a batteries-included framework from day one

Standards and ecosystem reuse

The skeleton is built around standard PSR contracts instead of framework-specific HTTP abstractions:

  • psr/http-message for PSR-7 request and response objects
  • psr/http-factory for PSR-17 factories
  • PSR-15 request handlers and middleware through the HTTP stack
  • psr/log for logging contracts

That matters because it lets the application reuse a wide range of existing PSR-compatible middleware, handlers, and supporting packages instead of forcing everything through a custom framework API.

Application model

README stays focused on onboarding. The detailed application model lives in docs/application-model.md.

That document covers:

  • project structure and the role of public/index.php, config/app.php, bootstrap classes, actions, and tests
  • startup flow and bootstrap execution order
  • the bootstrap contract and code-based configuration model
  • the default main pipeline and error pipeline
  • current demo behavior, including starter routes and host-agnostic matching defaults

When this skeleton is a good fit

  • small or medium HTTP applications that should stay explicit and lightweight
  • projects where developers want to start with business logic quickly instead of spending early time on framework assembly
  • projects that want to reuse existing PSR middleware and handlers
  • teams that want a clear startup model instead of hidden framework magic
  • applications that may outgrow a microframework but do not need a full-stack framework

When it is not a good fit

  • applications that want a batteries-included full-stack framework from day one
  • teams that want the framework itself to prescribe most application architecture and built-in subsystems
  • projects that expect built-in ORM, forms, templating, auth, admin tools, and similar higher-level features in the base package

Optional external integrations

The current starter model does not include a built-in local modules/ system. Optional integrations should be separate packages or other explicitly listed bootstrap classes.

For example, if an application needs to fall through to an existing legacy entry point when the new routes do not match, it can add nih/legacy-gateway explicitly and include NIH\LegacyGateway\LegacyGatewayBootstrap::class in config/app.php.

Development

  • composer test