PHPackages                             nih/app-skeleton - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [HTTP &amp; Networking](/categories/http)
4. /
5. nih/app-skeleton

ActiveProject[HTTP &amp; Networking](/categories/http)

nih/app-skeleton
================

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

0.1.2(3mo ago)30MITPHP 8.4 - 8.5

Since Apr 11Compare

[ Source](https://github.com/nih-soft/app-skeleton)[ Packagist](https://packagist.org/packages/nih/app-skeleton)[ RSS](/packages/nih-app-skeleton/feed)WikiDiscussions Synced 3w ago

READMEChangelogDependencies (7)Versions (4)Used By (0)

`nih/app-skeleton`
==================

[](#nihapp-skeleton)

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](docs/package-pains.md). It explains what problem each core NIH package is meant to solve and when it tends to become useful.

Contents
--------

[](#contents)

- [Why this skeleton exists](#why-this-skeleton-exists)
- [Package rationale: why each core package exists](docs/package-pains.md)
- [Requirements](#requirements)
- [Quick start](#quick-start)
- [How a request flows](#how-a-request-flows)
- [First change in 2 minutes](#first-change-in-2-minutes)
- [When routes start growing](#when-routes-start-growing)
- [What you get out of the box](#what-you-get-out-of-the-box)
- [What this skeleton intentionally does not include](#what-this-skeleton-intentionally-does-not-include)
- [Core NIH packages](#core-nih-packages)
- [What this stack makes easier](#what-this-stack-makes-easier)
- [Standards and ecosystem reuse](#standards-and-ecosystem-reuse)
- [Application model](#application-model)
- [When this skeleton is a good fit](#when-this-skeleton-is-a-good-fit)
- [When it is not a good fit](#when-it-is-not-a-good-fit)
- [Optional external integrations](#optional-external-integrations)
- [Development](#development)

Why this skeleton exists
------------------------

[](#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](docs/package-pains.md).

Requirements
------------

[](#requirements)

- PHP `8.4` or `8.5`
- Composer

Quick start
-----------

[](#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
-------------------

[](#how-a-request-flows)

The default mental model is deliberately small:

1. [`public/index.php`](public/index.php) loads Composer and reads the ordered bootstrap list from [`config/app.php`](config/app.php).
2. Bootstrap classes configure services, routes, the main pipeline, and the error pipeline.
3. The default main pipeline is `RouteMatchMiddleware` -&gt; `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` -&gt; `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
-------------------------

[](#first-change-in-2-minutes)

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

- edit [`src/Action/IndexAction.php`](src/Action/IndexAction.php) to change the response of `/`
- edit [`src/Bootstrap/AppBootstrap.php`](src/Bootstrap/AppBootstrap.php) to add routes or global middleware
- create more bootstrap classes when configuration becomes too large for one file

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

[](#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/` -&gt; `App\Action\Forums\Get`
- `GET /forums/view` -&gt; `App\Action\Forums\ViewGet`

What you get out of the box
---------------------------

[](#what-you-get-out-of-the-box)

- a thin public entry point in [`public/index.php`](public/index.php)
- ordered bootstraps in [`config/app.php`](config/app.php)
- app-specific wiring in [`src/Bootstrap/`](src/Bootstrap)
- demo actions in [`src/Action/`](src/Action)
- a default main pipeline: `RouteMatchMiddleware` -&gt; `RouteDispatchMiddleware` -&gt; `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
-------------------------------------------------

[](#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
-----------------

[](#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](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`](https://github.com/nih-soft/http-kernel#readme): minimal application kernel and bootstrap lifecycle for PSR-7, PSR-11, PSR-15, and PSR-17 based applications
- [`nih/container`](https://github.com/nih-soft/container#readme): PSR-11 container with autowiring for explicit application composition
- [`nih/router`](https://github.com/nih-soft/router#readme): tree-based router with runtime-built configuration, site-aware matching, URL generation, and PSR-15 middleware integration
- [`nih/middleware-dispatcher`](https://github.com/nih-soft/middleware-dispatcher#readme): 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
----------------------------

[](#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
-----------------------------

[](#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
-----------------

[](#application-model)

README stays focused on onboarding. The detailed application model lives in [docs/application-model.md](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
--------------------------------

[](#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
-------------------------

[](#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
------------------------------

[](#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`](https://github.com/nih-soft/legacy-gateway#readme) explicitly and include `NIH\LegacyGateway\LegacyGatewayBootstrap::class` in `config/app.php`.

Development
-----------

[](#development)

- `composer test`

###  Health Score

35

—

LowBetter than 77% of packages

Maintenance80

Actively maintained with recent releases

Popularity4

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity44

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

3

Last Release

104d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/64707?v=4)[nih](/maintainers/nih)[@nih](https://github.com/nih)

---

Top Contributors

[![nih-soft](https://avatars.githubusercontent.com/u/264681698?v=4)](https://github.com/nih-soft "nih-soft (6 commits)")

---

Tags

httppsr-7middlewarepsr-17routingpsr-15Skeletonstarter

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/nih-app-skeleton/health.svg)

```
[![Health](https://phpackages.com/badges/nih-app-skeleton/health.svg)](https://phpackages.com/packages/nih-app-skeleton)
```

###  Alternatives

[guzzlehttp/psr7

PSR-7 message implementation that also provides common utility methods

7.9k1.1B4.1k](/packages/guzzlehttp-psr7)[tempest/framework

The PHP framework that gets out of your way.

2.2k34.4k16](/packages/tempest-framework)[sunrise/http-router

A powerful solution as the foundation of your project.

17451.8k11](/packages/sunrise-http-router)[mezzio/mezzio

PSR-15 Middleware Microframework

3923.8M126](/packages/mezzio-mezzio)[phpro/http-tools

HTTP tools for developing more consistent HTTP implementations.

28150.5k](/packages/phpro-http-tools)[flow-php/flow

PHP ETL - Extract Transform Load - Data processing framework

85036.3k](/packages/flow-php-flow)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
