Client Project Start Guide
This guide explains how to adapt NENE2 into a small client-style API project.
It is intentionally practical and manual. The goal is to make the first project handoff credible before adding generators or broad framework convenience layers.
Starting Point
Use this guide when a project needs:
- a running local JSON API
- OpenAPI documentation that can be shared early
- a small set of tested endpoints
- optional React starter integration
- safe local MCP inspection through documented API boundaries
- basic machine-client authentication
- a Docker-based database verification path
NENE2 is still a 0.x foundation. Treat public contracts as useful but still forming.
Public field trial reference sandbox (optional)
After the first local milestone, it can help to inspect a completed public demo that stayed on the documented scaffold path:
- Repository:
hideyukiMORI/sakura-exhibition-nene2-field-trial(based on NENE2v0.1.1). - Contents: read-only exhibition-shaped JSON APIs, OpenAPI, PHPUnit, local MCP tools, and Markdown field-trial notes.
It is not an official product repository and does not imply endorsement of any real exhibition. Fictional sandbox data — read that project’s README.md and SECURITY.md before treating names or years as facts.
Starting from composer require
If you are starting a new project from scratch rather than forking the NENE2 repository, install NENE2 as a Composer dependency:
bash
mkdir my-project && cd my-project
composer init --name="vendor/my-project" --no-interaction
composer require hideyukimori/nene2:^0.3Then create the minimum files manually:
.env
dotenv
APP_ENV=local
APP_DEBUG=true
APP_NAME="My Project"
DB_ADAPTER=sqlitepublic/index.php — front controller using the built-in container:
php
<?php
declare(strict_types=1);
use Nene2\Http\ResponseEmitter;
use Nene2\Http\RuntimeContainerFactory;
use Nyholm\Psr7\Factory\Psr17Factory;
use Nyholm\Psr7Server\ServerRequestCreator;
use Psr\Http\Server\RequestHandlerInterface;
require dirname(__DIR__) . '/vendor/autoload.php';
$container = (new RuntimeContainerFactory(dirname(__DIR__)))->create();
$psr17 = $container->get(Psr17Factory::class);
$request = (new ServerRequestCreator($psr17, $psr17, $psr17, $psr17))->fromGlobals();
$response = $container->get(RequestHandlerInterface::class)->handle($request);
$container->get(ResponseEmitter::class)->emit($response);Note:
RuntimeContainerFactorywires the full NENE2 runtime including the Note CRUD example routes (/examples/notes/*). These are harmless but visible. To use only your own routes, wire the stack manually (see below).
Serve locally with PHP's built-in server:
bash
php -S localhost:8080 -t publicAdding custom routes
Pass custom routes via $routeRegistrars — an optional list<callable(Router): void> accepted by RuntimeApplicationFactory:
php
use Nene2\Http\JsonResponseFactory;
use Nene2\Http\RuntimeApplicationFactory;
use Nene2\Routing\Router;
use Nyholm\Psr7\Factory\Psr17Factory;
use Psr\Http\Message\ServerRequestInterface;
$psr17 = new Psr17Factory();
$json = new JsonResponseFactory($psr17, $psr17);
$app = (new RuntimeApplicationFactory(
$psr17,
$psr17,
routeRegistrars: [
static function (Router $router) use ($json): void {
$router->get('/items/{id}', static function (ServerRequestInterface $req) use ($json) {
// Path parameters are stored under Router::PARAMETERS_ATTRIBUTE, not as individual attributes.
$params = $req->getAttribute(Router::PARAMETERS_ATTRIBUTE, []);
return $json->create(['id' => (int) ($params['id'] ?? 0)]);
});
},
],
))->create();Route registrars run after the built-in framework routes are registered. For path parameter access, always read from Router::PARAMETERS_ATTRIBUTE. See docs/development/endpoint-scaffold.md for details.
First Local Setup
Start from a clean checkout:
bash
docker compose build
docker compose run --rm app composer install
docker compose run --rm app composer check
docker compose up -d appConfirm the local API and docs:
bash
curl -i http://localhost:8080/health
curl -i http://localhost:8080/examples/pingUseful browser URLs:
- OpenAPI:
http://localhost:8080/openapi.php - Swagger UI:
http://localhost:8080/docs/
Rename the Project Boundary
Before adding application behavior, decide what is project-specific and what stays framework foundation.
Update project-facing metadata first:
README.mdproject descriptioncomposer.jsonpackage name and description when the project will not remain the NENE2 framework package- OpenAPI
info.title,info.description, andinfo.version - default examples that should no longer describe NENE2 itself
Keep these unchanged unless the application truly owns them:
- low-level framework namespaces in
src/ - Problem Details shape
- request id behavior
- endpoint scaffold workflow
- test and static analysis commands
Add the First Application Endpoint
Use docs/development/endpoint-scaffold.md for every shipped JSON endpoint.
For the first client endpoint:
- Create or reuse a focused GitHub Issue.
- Add the route in the smallest clear runtime boundary.
- Add the OpenAPI path,
operationId, schema, and examples. - Add runtime tests close to the endpoint behavior.
- Let
tests/OpenApi/RuntimeContractTest.phpverify documented success examples. - Run a local HTTP smoke check through Docker.
Keep early endpoints thin. Move behavior toward use cases or small services once a route grows beyond simple runtime demonstration. See docs/development/domain-layer.md for the UseCase → RepositoryInterface → PDO adapter convention and container wiring pattern.
Keep OpenAPI as the Handoff Contract
OpenAPI should be updated in the same PR as endpoint behavior.
Each endpoint should include:
- stable
operationId - concise summary and description
- success response schema and example
- relevant Problem Details responses
- security requirements only when matching middleware exists
Before sending the handoff, run:
bash
docker compose run --rm app composer openapi
docker compose run --rm app composer checkAdd MCP Only Through API Boundaries
Start with public or read-only API behavior first. Add MCP metadata only after the endpoint exists in OpenAPI.
If an endpoint should become a local MCP tool:
- Add or confirm the OpenAPI operation.
- Add a read-only entry to
docs/mcp/tools.json. - Run
docker compose run --rm app composer mcp. - Smoke test the local MCP server only against local APIs.
Local MCP guidance lives in docs/integrations/local-mcp-server.md.
Do not expose direct database, filesystem, or production-only behavior through MCP tools.
Protect Machine-Client Paths
Use the existing X-NENE2-API-Key direction for machine-client paths only.
For local testing:
bash
NENE2_MACHINE_API_KEY=local-dev-key docker compose up -d app
curl -i -H 'X-NENE2-API-Key: local-dev-key' http://localhost:8080/machine/healthDo not commit real API keys, generated secrets, or local .env files.
Authentication policy lives in docs/development/authentication-boundary.md. The local protected route smoke workflow lives in docs/development/machine-client-smoke.md.
Set Up a Dockerfile for the Client Project
If the client project runs in Docker using php:8.4-cli as the base image, you will need to install Composer and the required PHP extensions manually, as they are not included in the -cli image.
dockerfile
FROM php:8.4-cli
RUN apt-get update && apt-get install -y \
libsqlite3-dev libonig-dev curl unzip \
&& docker-php-ext-install pdo pdo_sqlite pdo_mysql \
&& curl -sS https://getcomposer.org/installer \
| php -- --install-dir=/usr/local/bin --filename=composer \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --no-dev --optimize-autoloader
COPY . .Extension notes:
| Extension | Required when |
|---|---|
pdo | Always (PDO base) |
pdo_sqlite | DB_ADAPTER=sqlite |
pdo_mysql | DB_ADAPTER=mysql |
libsqlite3-dev | Build dependency for pdo_sqlite |
libonig-dev | Build dependency for mbstring (used by some NENE2 deps) |
For a full Docker Compose setup with a MySQL service, see compose.yaml in the NENE2 repository as a reference.
Set Up Quality Tools in the Client Project
When setting up PHP-CS-Fixer with declare_strict_types in .php-cs-fixer.php, the fixer is classified as risky and requires the --allow-risky=yes flag. Add it to your composer.json scripts to avoid forgetting it:
json
{
"scripts": {
"cs": "php-cs-fixer check --diff --allow-risky=yes",
"cs:fix": "php-cs-fixer fix --allow-risky=yes"
}
}Without --allow-risky=yes, the check command exits with an error even when there are no actual style violations.
Verify Database Behavior
Default database adapter tests use SQLite and run through the standard check path.
Use MySQL verification only when behavior should be checked against a service database:
bash
docker compose up -d mysql
docker compose run --rm app composer test:database:mysqlDatabase strategy lives in docs/development/test-database-strategy.md.
Handoff Checklist
Before handing off a client-style project, confirm:
README.mddescribes the project, not just the starter.docs/openapi/openapi.yamlmatches shipped JSON behavior.- Swagger UI loads locally.
- New endpoints have runtime tests and OpenAPI examples.
- Protected routes document required credentials without exposing secret values.
- MCP tools, if any, call documented API boundaries only.
docker compose run --rm app composer checkpasses.- Frontend checks pass when the React starter is part of the handoff.
- Deferred work is visible in
docs/todo/current.mdor a project-specific tracker.
Useful Next Documents
- Domain layer policy:
docs/development/domain-layer.md - Endpoint scaffold workflow:
docs/development/endpoint-scaffold.md - Local MCP server guidance:
docs/integrations/local-mcp-server.md - Local MCP client configuration:
docs/integrations/local-mcp-client-configuration.md - MCP tool policy:
docs/integrations/mcp-tools.md - Authentication boundary:
docs/development/authentication-boundary.md - Machine-client smoke workflow:
docs/development/machine-client-smoke.md - Database test strategy:
docs/development/test-database-strategy.md - Release policy:
docs/development/release-v0.1.x-policy.md - Current milestone:
docs/milestones/2026-05-client-delivery-hardening.md - GitHub Issue:
#150