<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: HideyukiMORI</title>
    <description>The latest articles on DEV Community by HideyukiMORI (@hideyukimori).</description>
    <link>https://dev.to/hideyukimori</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3995621%2Fd22faba8-a5e8-4ded-a514-1558d81cc5db.jpg</url>
      <title>DEV Community: HideyukiMORI</title>
      <link>https://dev.to/hideyukimori</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hideyukimori"/>
    <language>en</language>
    <item>
      <title>Click a link, get a throwaway tenant: a zero-signup demo for a self-hosted app</title>
      <dc:creator>HideyukiMORI</dc:creator>
      <pubDate>Wed, 08 Jul 2026 16:56:30 +0000</pubDate>
      <link>https://dev.to/hideyukimori/click-a-link-get-a-throwaway-tenant-a-zero-signup-demo-for-a-self-hosted-app-1naj</link>
      <guid>https://dev.to/hideyukimori/click-a-link-get-a-throwaway-tenant-a-zero-signup-demo-for-a-self-hosted-app-1naj</guid>
      <description>&lt;p&gt;I maintain &lt;strong&gt;&lt;a href="https://github.com/hideyukiMORI/nene-invoice" rel="noopener noreferrer"&gt;NeNe Invoice&lt;/a&gt;&lt;/strong&gt;, a self-hosted invoicing OSS (PHP + SPA). Recently I needed to show it to people who are not engineers — and nothing kills a demo faster than "first, create an account."&lt;/p&gt;

&lt;p&gt;What I wanted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A URL you click. No signup, no login screen, no instructions.&lt;/li&gt;
&lt;li&gt;Realistic data already inside — invoices, payments, overdue items.&lt;/li&gt;
&lt;li&gt;No way for one visitor to see another visitor's mess.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is the demo, live (heads-up: the UI is Japanese — the product targets Japanese small businesses):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://invoice.ayane.co.jp/demo/kensetsu" rel="noopener noreferrer"&gt;https://invoice.ayane.co.jp/demo/kensetsu&lt;/a&gt; (construction company)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://invoice.ayane.co.jp/demo/bldmainte" rel="noopener noreferrer"&gt;https://invoice.ayane.co.jp/demo/bldmainte&lt;/a&gt; (building maintenance, recurring invoices)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://invoice.ayane.co.jp/demo/seisaku" rel="noopener noreferrer"&gt;https://invoice.ayane.co.jp/demo/seisaku&lt;/a&gt; (creative agency, withholding tax)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every click provisions a &lt;strong&gt;brand-new disposable tenant&lt;/strong&gt; and drops you on its dashboard. Three hours later, a cron job deletes it. This post is about how that works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not the usual demo setups
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A shared demo account&lt;/strong&gt; is the easy option, and it's bad. Every visitor sees the previous visitor's test data. You end up writing a reset cron, and then someone gets reset &lt;em&gt;mid-click&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A read-only mode&lt;/strong&gt; protects the data by removing the product. "Create an invoice, register a payment, watch it reconcile" is the whole pitch — a demo where the write buttons are disabled demos nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disposable tenants&lt;/strong&gt; solve both, &lt;em&gt;if&lt;/em&gt; you already have multi-tenancy. That's the real precondition. My app already isolated everything by organization (each row carries an &lt;code&gt;organization_id&lt;/code&gt;, every use case is org-scoped), so a demo mode became a thin layer of glue: about 900 lines including the seeder and the cleanup script, with &lt;strong&gt;zero changes to the auth core&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The flow
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /demo/{template}
  → create org with a random slug        (normal CreateOrganization use case)
  → seed industry data into it           (DemoDataSeeder)
  → issue session cookies, path-scoped   (normal RefreshTokenIssuer)
  → 302 to /{slug}/dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing in that list is demo-specific infrastructure. Creating an org, creating its admin, issuing a refresh token — those are the same production use cases a real signup would call. The demo handler just calls them in a row and returns a redirect.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pieces worth stealing
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Gate it behind an env var, and 404
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DEMO_MODE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'0'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="s1"&gt;'1'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;problemDetails&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'not-found'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'Not Found'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Demo mode is not enabled on this instance.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;/demo/*&lt;/code&gt; is a public, unauthenticated route, so on any instance that isn't the demo instance it shouldn't just be forbidden — it should not exist. A 404 (not a 403) means production installs don't even advertise that a demo feature is in the codebase.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Random slug, and the prefix is the contract
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$slug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'demo-'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;bin2hex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;random_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;   &lt;span class="c1"&gt;// demo-3f9a1c02&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;demo-&lt;/code&gt; prefix does double duty: it namespaces the tenant &lt;em&gt;and&lt;/em&gt; marks it as garbage-collectable. The cleanup script selects targets by this prefix alone, so it is structurally incapable of touching a real organization.&lt;/p&gt;

&lt;p&gt;The admin password is random and never shown to anyone. There is deliberately no way to log into a demo org through the front door.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Seed data that never goes stale
&lt;/h3&gt;

&lt;p&gt;Demo credibility lives and dies on seed data, and seed data has a classic failure mode: hardcoded dates. Six months after you write "invoice dated 2026-01-15", your demo shows an ancient dashboard.&lt;/p&gt;

&lt;p&gt;All seeded dates are &lt;strong&gt;relative to today&lt;/strong&gt;: a few invoices issued this month, one overdue, a couple paid last week. Whenever you click the link, the dashboard looks like a business that is alive right now.&lt;/p&gt;

&lt;p&gt;The seeder also plants one deliberately messy case per template — a bank transfer whose payer name doesn't quite match the client name, waiting to be reconciled. Anyone who has done accounts receivable recognizes that pain instantly. One realistic wart sells the product better than ten clean records.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Session handoff without touching the auth core
&lt;/h3&gt;

&lt;p&gt;The tricky part: after creating the org, the visitor must land &lt;em&gt;inside&lt;/em&gt; it, authenticated, without seeing a login screen.&lt;/p&gt;

&lt;p&gt;The handler issues the exact same refresh + CSRF cookies a real login would — but &lt;strong&gt;path-scoped to the new tenant's slug&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP/2 302
set-cookie: ni_refresh=...; Path=/demo-5b067975/auth; Secure; HttpOnly; SameSite=Strict
set-cookie: ni_csrf=...;    Path=/demo-5b067975/;     Secure; SameSite=Strict
location: /demo-5b067975/dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(That's a real response from the live demo.) The SPA loads, does its normal silent-refresh against &lt;code&gt;/{slug}/auth/refresh&lt;/code&gt;, gets an access token, and renders. Token rotation, cookie contents, auth middleware: all untouched.&lt;/p&gt;

&lt;p&gt;One trade-off accepted: the session is one-shot. Reload and you're back at the login screen. Fixing it would mean modifying the auth core for the demo's sake, so instead the demo leans into it — &lt;strong&gt;hitting the URL again gives you a fresh tenant, which &lt;em&gt;is&lt;/em&gt; the "reset demo" button&lt;/strong&gt;. With disposable tenants, reset isn't a feature you build; it comes free.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Cleanup that is allowed to be crude
&lt;/h3&gt;

&lt;p&gt;A cron job runs hourly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;delete &lt;code&gt;demo-&lt;/code&gt; orgs older than &lt;code&gt;DEMO_TTL_HOURS&lt;/code&gt; (default 3)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;also&lt;/strong&gt; delete the oldest ones beyond &lt;code&gt;DEMO_MAX_ORGS&lt;/code&gt; (default 200)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The second rule matters. TTL alone is fragile — if the cron stalls or someone scripts a thousand clicks, you want a hard cap on how much garbage can exist. With the cap, a bot hammering the demo link just churns its own tenants.&lt;/p&gt;

&lt;p&gt;Child rows (invoices, line items, payments…) are bulk-deleted by &lt;code&gt;organization_id&lt;/code&gt;, exceptions swallowed. Normally that's sloppy; here it's fine, and that's the point — &lt;strong&gt;when data is disposable by construction, the cleanup code gets to be simple&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Guardrails, summarized
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concern&lt;/th&gt;
&lt;th&gt;Answer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Runs on production installs?&lt;/td&gt;
&lt;td&gt;No — &lt;code&gt;DEMO_MODE=1&lt;/code&gt; gate, 404 otherwise&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Touching real tenants?&lt;/td&gt;
&lt;td&gt;Impossible by construction (&lt;code&gt;demo-&lt;/code&gt; prefix)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Login route into demo orgs?&lt;/td&gt;
&lt;td&gt;None — random password, never disclosed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data lifetime&lt;/td&gt;
&lt;td&gt;3h TTL, hourly sweep&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Abuse / DoS&lt;/td&gt;
&lt;td&gt;Hard cap on org count, oldest evicted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Reset the demo"&lt;/td&gt;
&lt;td&gt;Not built — click the link again&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;The demo feature took a day. The reason it took a day is that years of boring discipline — tenant isolation on every row, org creation as a proper use case, token issuance behind an interface — had already paid for it. The glue layer got to be thin because the boundaries underneath were real.&lt;/p&gt;

&lt;p&gt;If your app is multi-tenant, "click a link, get a throwaway tenant" is probably cheaper to build than the reset cron you were about to write for a shared demo account.&lt;/p&gt;

&lt;p&gt;Code: &lt;a href="https://github.com/hideyukiMORI/nene-invoice" rel="noopener noreferrer"&gt;&lt;code&gt;src/Demo/&lt;/code&gt;&lt;/a&gt; and &lt;code&gt;tools/sweep-demo.php&lt;/code&gt; in NeNe Invoice (MIT).&lt;/p&gt;

</description>
      <category>php</category>
      <category>saas</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Building a typed CMS for business data with PHP, OpenAPI, and MCP</title>
      <dc:creator>HideyukiMORI</dc:creator>
      <pubDate>Thu, 02 Jul 2026 15:34:57 +0000</pubDate>
      <link>https://dev.to/hideyukimori/building-a-typed-cms-for-business-data-with-php-openapi-and-mcp-7g1</link>
      <guid>https://dev.to/hideyukimori/building-a-typed-cms-for-business-data-with-php-openapi-and-mcp-7g1</guid>
      <description>&lt;p&gt;In my previous article, I introduced the &lt;strong&gt;NeNe&lt;/strong&gt; series: a family of small, self-hosted business tools for teams operating in Japan.&lt;/p&gt;

&lt;p&gt;Previous article:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/hideyukimori/i-am-building-self-hosted-business-tools-for-small-teams-in-japan-4i26"&gt;https://dev.to/hideyukimori/i-am-building-self-hosted-business-tools-for-small-teams-in-japan-4i26&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This article is a deeper look at one of those tools:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NeNe Records&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Repository:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/hideyukiMORI/nene-records" rel="noopener noreferrer"&gt;https://github.com/hideyukiMORI/nene-records&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;NeNe Records is an API-first typed CMS and flexible entity platform built on &lt;strong&gt;NENE2&lt;/strong&gt;, my small PHP framework for AI-readable business APIs.&lt;/p&gt;

&lt;p&gt;It is not trying to be a WordPress clone.&lt;/p&gt;

&lt;p&gt;It is not trying to run WordPress plugins or themes.&lt;/p&gt;

&lt;p&gt;The goal is different:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;manage business data and public content through typed schemas, documented APIs, and clear AI tool boundaries.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just use WordPress?
&lt;/h2&gt;

&lt;p&gt;WordPress is useful.&lt;/p&gt;

&lt;p&gt;It has proven that a generic content model can support blogs, pages, shops, media, and many small business workflows.&lt;/p&gt;

&lt;p&gt;But when I think about business data, a few problems keep coming back:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;untyped metadata&lt;/li&gt;
&lt;li&gt;plugin-specific data shapes&lt;/li&gt;
&lt;li&gt;hooks and filters that can change behavior from many places&lt;/li&gt;
&lt;li&gt;API contracts that depend heavily on plugins&lt;/li&gt;
&lt;li&gt;AI integrations that may not know where the real application boundary is&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;postmeta&lt;/code&gt; model is flexible, but it often becomes stringly typed storage.&lt;/p&gt;

&lt;p&gt;That is fine for many websites.&lt;/p&gt;

&lt;p&gt;But for business data, I usually want the API to know more:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;this field is text&lt;/li&gt;
&lt;li&gt;this field is an enum&lt;/li&gt;
&lt;li&gt;this field is an image&lt;/li&gt;
&lt;li&gt;this field is a relation&lt;/li&gt;
&lt;li&gt;this field is required&lt;/li&gt;
&lt;li&gt;this record belongs to this organization&lt;/li&gt;
&lt;li&gt;this operation requires this role&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the space where NeNe Records lives.&lt;/p&gt;

&lt;h2&gt;
  
  
  Typed records instead of metadata chaos
&lt;/h2&gt;

&lt;p&gt;The core model is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Entity Type
  -&amp;gt; Field Definition
  -&amp;gt; Record
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An &lt;strong&gt;Entity Type&lt;/strong&gt; defines what kind of data exists.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;posts&lt;/li&gt;
&lt;li&gt;pages&lt;/li&gt;
&lt;li&gt;products&lt;/li&gt;
&lt;li&gt;events&lt;/li&gt;
&lt;li&gt;internal documents&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A &lt;strong&gt;Field Definition&lt;/strong&gt; describes the shape of a field:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;text&lt;/li&gt;
&lt;li&gt;markdown&lt;/li&gt;
&lt;li&gt;html&lt;/li&gt;
&lt;li&gt;blocks&lt;/li&gt;
&lt;li&gt;int&lt;/li&gt;
&lt;li&gt;enum&lt;/li&gt;
&lt;li&gt;bool&lt;/li&gt;
&lt;li&gt;datetime&lt;/li&gt;
&lt;li&gt;image&lt;/li&gt;
&lt;li&gt;file&lt;/li&gt;
&lt;li&gt;relation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A &lt;strong&gt;Record&lt;/strong&gt; is the actual data.&lt;/p&gt;

&lt;p&gt;The important part is that the schema is not only a frontend concern.&lt;/p&gt;

&lt;p&gt;The API validates writes against the registered field definitions.&lt;/p&gt;

&lt;p&gt;The admin UI can provide a flexible editing experience, but the API remains the boundary that enforces the data shape.&lt;/p&gt;

&lt;p&gt;That is the main difference I care about:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;flexible editing, typed persistence.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Current status
&lt;/h2&gt;

&lt;p&gt;NeNe Records is already more than a schema experiment.&lt;/p&gt;

&lt;p&gt;It has a React admin UI, typed entity definitions, record management, media handling, public pages, OpenAPI documentation, and an OpenAPI-derived MCP tool catalog.&lt;/p&gt;

&lt;p&gt;It also supports multi-tenant organization scope, with ongoing work around WordPress migration and production deployment.&lt;/p&gt;

&lt;p&gt;It is still a work in progress, but the core shape is already running.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;

&lt;p&gt;NeNe Records follows the same general shape as the rest of the NeNe series:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;React Admin UI
Public pages
AI / MCP clients
        |
        v
Documented HTTP API
        |
        v
Use cases
        |
        v
Repositories
        |
        v
Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The backend is PHP 8.4 on NENE2.&lt;/p&gt;

&lt;p&gt;The admin UI is React and TypeScript.&lt;/p&gt;

&lt;p&gt;The API contract is OpenAPI 3.1.&lt;/p&gt;

&lt;p&gt;MCP tools are derived from the documented API surface.&lt;/p&gt;

&lt;p&gt;The important rule is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MCP tools should call the application API, not the database.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is the same rule I wrote about in my previous article:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/hideyukimori/mcp-should-not-mean-letting-ai-touch-your-database-57p1"&gt;https://dev.to/hideyukimori/mcp-should-not-mean-letting-ai-touch-your-database-57p1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For a CMS-like product, this matters a lot.&lt;/p&gt;

&lt;p&gt;Content and business data are not just rows.&lt;/p&gt;

&lt;p&gt;They have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;validation rules&lt;/li&gt;
&lt;li&gt;permissions&lt;/li&gt;
&lt;li&gt;organization scope&lt;/li&gt;
&lt;li&gt;publication state&lt;/li&gt;
&lt;li&gt;redirects&lt;/li&gt;
&lt;li&gt;SEO metadata&lt;/li&gt;
&lt;li&gt;media references&lt;/li&gt;
&lt;li&gt;audit and migration concerns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those rules belong in the application boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  API-first admin
&lt;/h2&gt;

&lt;p&gt;The admin screen is not the source of truth.&lt;/p&gt;

&lt;p&gt;It is a client.&lt;/p&gt;

&lt;p&gt;It edits entity types, fields, records, media, users, and settings through the same API shape that other clients can understand.&lt;/p&gt;

&lt;p&gt;This makes the system easier to reason about.&lt;/p&gt;

&lt;p&gt;If a future tool needs to create a record, it should not need a hidden internal shortcut.&lt;/p&gt;

&lt;p&gt;It should call the same API that the admin UI calls.&lt;/p&gt;

&lt;p&gt;That is also why OpenAPI matters.&lt;/p&gt;

&lt;p&gt;The API contract is not only documentation for humans.&lt;/p&gt;

&lt;p&gt;It is also a map for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;generated clients&lt;/li&gt;
&lt;li&gt;contract tests&lt;/li&gt;
&lt;li&gt;MCP tool catalogs&lt;/li&gt;
&lt;li&gt;future automation&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Public pages without a separate Node SSR server
&lt;/h2&gt;

&lt;p&gt;NeNe Records is not only a headless CMS.&lt;/p&gt;

&lt;p&gt;It also supports public pages.&lt;/p&gt;

&lt;p&gt;The direction is a single-origin PHP application that can return crawlable HTML for public content, with a SPA shell on top.&lt;/p&gt;

&lt;p&gt;That includes work around:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;real permalinks&lt;/li&gt;
&lt;li&gt;canonical URLs&lt;/li&gt;
&lt;li&gt;OGP&lt;/li&gt;
&lt;li&gt;Twitter cards&lt;/li&gt;
&lt;li&gt;JSON-LD&lt;/li&gt;
&lt;li&gt;breadcrumbs&lt;/li&gt;
&lt;li&gt;sitemap.xml&lt;/li&gt;
&lt;li&gt;robots.txt&lt;/li&gt;
&lt;li&gt;redirect maps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I do not want every small business deployment to require a separate Node SSR service just to make public pages crawlable.&lt;/p&gt;

&lt;p&gt;For this project, the PHP application should be able to serve useful public HTML directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  WordPress migration, not WordPress compatibility
&lt;/h2&gt;

&lt;p&gt;NeNe Records is not WordPress-compatible.&lt;/p&gt;

&lt;p&gt;That is intentional.&lt;/p&gt;

&lt;p&gt;But migration still matters.&lt;/p&gt;

&lt;p&gt;Many real teams already have content in WordPress.&lt;/p&gt;

&lt;p&gt;So NeNe Records has been growing a WXR import path:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;posts and pages&lt;/li&gt;
&lt;li&gt;tags&lt;/li&gt;
&lt;li&gt;media attachments&lt;/li&gt;
&lt;li&gt;body image URL replacement&lt;/li&gt;
&lt;li&gt;301 redirect maps&lt;/li&gt;
&lt;li&gt;SEO metadata from common plugins&lt;/li&gt;
&lt;li&gt;import plan preview&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is not:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;run WordPress themes and plugins.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The goal is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;move content, URLs, media, and SEO intent into a typed API-first platform.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That distinction is important.&lt;/p&gt;

&lt;p&gt;Compatibility with the WordPress runtime would pull the product back toward the same plugin/theme coupling I am trying to avoid.&lt;/p&gt;

&lt;p&gt;Migration is the useful part.&lt;/p&gt;

&lt;p&gt;Runtime compatibility is not the goal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-data testing with Aozora Bunko
&lt;/h2&gt;

&lt;p&gt;One thing I have been doing recently is testing NeNe Records with real public-domain text data.&lt;/p&gt;

&lt;p&gt;For that, I prepared an importer using &lt;strong&gt;Aozora Bunko&lt;/strong&gt; text.&lt;/p&gt;

&lt;p&gt;In a test tenant, I imported works by Natsume Soseki as chapter-based records.&lt;/p&gt;

&lt;p&gt;The goal is not just to prove that records can be inserted.&lt;/p&gt;

&lt;p&gt;The goal is to see whether the system still feels coherent when the data is not a tiny demo.&lt;/p&gt;

&lt;p&gt;Things I want to observe:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;list performance with many records&lt;/li&gt;
&lt;li&gt;permalink tree behavior&lt;/li&gt;
&lt;li&gt;chapter navigation&lt;/li&gt;
&lt;li&gt;public page rendering&lt;/li&gt;
&lt;li&gt;sitemap behavior&lt;/li&gt;
&lt;li&gt;search and directory browsing&lt;/li&gt;
&lt;li&gt;admin UI usability with larger content sets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The public test tenant is here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aozora.nene-records.com/" rel="noopener noreferrer"&gt;https://aozora.nene-records.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In a browser, it is intended to show the public shell.&lt;/p&gt;

&lt;p&gt;If you call it with &lt;code&gt;curl&lt;/code&gt; and the default &lt;code&gt;Accept: */*&lt;/code&gt;, you may see an API-style JSON response instead. Normal browser access should resolve as HTML.&lt;/p&gt;

&lt;p&gt;This kind of real-data testing is valuable because CMS demos can be misleading.&lt;/p&gt;

&lt;p&gt;A CMS with five records can look finished.&lt;/p&gt;

&lt;p&gt;A CMS with hundreds or thousands of records starts to reveal where the model, navigation, and public presentation are weak.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is business software, not only a CMS experiment
&lt;/h2&gt;

&lt;p&gt;I call NeNe Records a typed CMS, but the broader idea is a flexible entity platform.&lt;/p&gt;

&lt;p&gt;The same primitives can describe more than blog posts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;product catalogs&lt;/li&gt;
&lt;li&gt;internal knowledge records&lt;/li&gt;
&lt;li&gt;simple CRM data&lt;/li&gt;
&lt;li&gt;public directories&lt;/li&gt;
&lt;li&gt;structured landing pages&lt;/li&gt;
&lt;li&gt;operational records&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The difference from an application framework is that Records keeps the schema editable from the product surface.&lt;/p&gt;

&lt;p&gt;The difference from a traditional CMS is that the API and field definitions are not treated as incidental plugin behavior.&lt;/p&gt;

&lt;p&gt;They are the center.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is still unfinished
&lt;/h2&gt;

&lt;p&gt;I do not want to overstate the maturity.&lt;/p&gt;

&lt;p&gt;NeNe Records is still being refined.&lt;/p&gt;

&lt;p&gt;Important areas are still evolving:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;settings UI&lt;/li&gt;
&lt;li&gt;finer-grained permissions&lt;/li&gt;
&lt;li&gt;two-factor authentication&lt;/li&gt;
&lt;li&gt;production deployment paths&lt;/li&gt;
&lt;li&gt;hosted / managed options&lt;/li&gt;
&lt;li&gt;more public-site polish&lt;/li&gt;
&lt;li&gt;large-data soak testing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The project is open source and usable for experimentation, but I do not want to present it as a finished CMS replacement.&lt;/p&gt;

&lt;p&gt;The honest position is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;the shape is working, and the product is being hardened through real implementation.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned while building it
&lt;/h2&gt;

&lt;p&gt;The biggest lesson is that "simple CMS" is not actually simple.&lt;/p&gt;

&lt;p&gt;Once you care about typed data, SEO, public pages, migration, multi-tenancy, media, admin UX, and AI boundaries, the system becomes a real product very quickly.&lt;/p&gt;

&lt;p&gt;That is why the architecture needs to stay boring.&lt;/p&gt;

&lt;p&gt;The boring parts are the parts I want to trust:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTTP APIs&lt;/li&gt;
&lt;li&gt;OpenAPI contracts&lt;/li&gt;
&lt;li&gt;use cases&lt;/li&gt;
&lt;li&gt;repositories&lt;/li&gt;
&lt;li&gt;typed field definitions&lt;/li&gt;
&lt;li&gt;explicit permissions&lt;/li&gt;
&lt;li&gt;MCP behind the API boundary&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The product can become flexible only if the boundaries stay strict.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;NeNe Records: &lt;a href="https://github.com/hideyukiMORI/nene-records" rel="noopener noreferrer"&gt;https://github.com/hideyukiMORI/nene-records&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;NENE2: &lt;a href="https://github.com/hideyukiMORI/NENE2" rel="noopener noreferrer"&gt;https://github.com/hideyukiMORI/NENE2&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Aozora test tenant: &lt;a href="https://aozora.nene-records.com/" rel="noopener noreferrer"&gt;https://aozora.nene-records.com/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Previous NeNe series overview: &lt;a href="https://dev.to/hideyukimori/i-am-building-self-hosted-business-tools-for-small-teams-in-japan-4i26"&gt;https://dev.to/hideyukimori/i-am-building-self-hosted-business-tools-for-small-teams-in-japan-4i26&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;MCP boundary article: &lt;a href="https://dev.to/hideyukimori/mcp-should-not-mean-letting-ai-touch-your-database-57p1"&gt;https://dev.to/hideyukimori/mcp-should-not-mean-letting-ai-touch-your-database-57p1&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;NeNe Records is still a work in progress.&lt;/p&gt;

&lt;p&gt;But the direction is clear:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;typed business data, public pages, OpenAPI contracts, and AI tools that stay behind the application boundary.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>cms</category>
      <category>api</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I am building self-hosted business tools for small teams in Japan</title>
      <dc:creator>HideyukiMORI</dc:creator>
      <pubDate>Tue, 30 Jun 2026 13:51:32 +0000</pubDate>
      <link>https://dev.to/hideyukimori/i-am-building-self-hosted-business-tools-for-small-teams-in-japan-4i26</link>
      <guid>https://dev.to/hideyukimori/i-am-building-self-hosted-business-tools-for-small-teams-in-japan-4i26</guid>
      <description>&lt;p&gt;I have been building a small family of open source business tools called the &lt;strong&gt;NeNe&lt;/strong&gt; series.&lt;/p&gt;

&lt;p&gt;The idea is simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;small teams should be able to run useful business software on their own infrastructure, with clear APIs and readable boundaries.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most of the tools are still being refined, but the direction is already visible:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;self-hosted&lt;/li&gt;
&lt;li&gt;API-first&lt;/li&gt;
&lt;li&gt;bilingual where Japan operations need it&lt;/li&gt;
&lt;li&gt;OpenAPI documented&lt;/li&gt;
&lt;li&gt;MCP-ready for AI agents&lt;/li&gt;
&lt;li&gt;small enough for one developer or a small team to understand&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The foundation is &lt;strong&gt;NENE2&lt;/strong&gt;, my PHP 8.4 API-first micro-framework.&lt;/p&gt;

&lt;p&gt;Repository:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/hideyukiMORI/NENE2" rel="noopener noreferrer"&gt;https://github.com/hideyukiMORI/NENE2&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GitHub profile:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/hideyukiMORI" rel="noopener noreferrer"&gt;https://github.com/hideyukiMORI&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Japan-specific business tools?
&lt;/h2&gt;

&lt;p&gt;Many teams operating in Japan have a mixed workflow.&lt;/p&gt;

&lt;p&gt;The technical team may be comfortable with English.&lt;/p&gt;

&lt;p&gt;The business workflow is still deeply local:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;qualified invoices&lt;/li&gt;
&lt;li&gt;received-document retention&lt;/li&gt;
&lt;li&gt;payment reconciliation&lt;/li&gt;
&lt;li&gt;Japanese bank CSV formats&lt;/li&gt;
&lt;li&gt;bilingual admin screens&lt;/li&gt;
&lt;li&gt;small company operations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Generic tools can work.&lt;/p&gt;

&lt;p&gt;But sometimes the local workflow leaks through the cracks.&lt;/p&gt;

&lt;p&gt;That is why the NeNe series is not just “another CRUD app collection”.&lt;/p&gt;

&lt;p&gt;It is an attempt to build small, self-hosted tools that understand specific business boundaries.&lt;/p&gt;

&lt;h2&gt;
  
  
  The products
&lt;/h2&gt;

&lt;p&gt;The series includes several focused tools.&lt;/p&gt;

&lt;h3&gt;
  
  
  NeNe Invoice
&lt;/h3&gt;

&lt;p&gt;Self-hosted quote and invoice management for Japan small businesses.&lt;/p&gt;

&lt;p&gt;It focuses on qualified invoices, quotes, invoices, payments, PDF output, and admin UI workflows.&lt;/p&gt;

&lt;p&gt;Repository:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/hideyukiMORI/nene-invoice" rel="noopener noreferrer"&gt;https://github.com/hideyukiMORI/nene-invoice&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Japanese hands-on article:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://qiita.com/xioncc/items/7439c9832a6795b4d48e" rel="noopener noreferrer"&gt;https://qiita.com/xioncc/items/7439c9832a6795b4d48e&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  NeNe Vault
&lt;/h3&gt;

&lt;p&gt;Received-document archive for PDFs and business documents.&lt;/p&gt;

&lt;p&gt;It focuses on storing received invoices, receipts, and contracts with search, audit history, and retention-oriented workflows.&lt;/p&gt;

&lt;p&gt;Repository:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/hideyukiMORI/nene-vault" rel="noopener noreferrer"&gt;https://github.com/hideyukiMORI/nene-vault&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Japanese hands-on article:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://qiita.com/xioncc/items/2f3726347e4504363534" rel="noopener noreferrer"&gt;https://qiita.com/xioncc/items/2f3726347e4504363534&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  NeNe Deal
&lt;/h3&gt;

&lt;p&gt;Lightweight B2B deal pipeline management.&lt;/p&gt;

&lt;p&gt;It handles opportunities, stages, simple forecasts, and handoff to NeNe Invoice as draft client / quote data.&lt;/p&gt;

&lt;p&gt;Repository:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/hideyukiMORI/nene-deal" rel="noopener noreferrer"&gt;https://github.com/hideyukiMORI/nene-deal&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Japanese hands-on article:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://qiita.com/xioncc/items/5f7f0e3687d77f07e00e" rel="noopener noreferrer"&gt;https://qiita.com/xioncc/items/5f7f0e3687d77f07e00e&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  NeNe Contact
&lt;/h3&gt;

&lt;p&gt;Embeddable contact forms and an operator inbox.&lt;/p&gt;

&lt;p&gt;It lets operators build forms, embed them with a script tag, receive submissions, manage statuses, and route notifications.&lt;/p&gt;

&lt;p&gt;Repository:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/hideyukiMORI/nene-contact" rel="noopener noreferrer"&gt;https://github.com/hideyukiMORI/nene-contact&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Japanese hands-on article:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://qiita.com/xioncc/items/580750a119ffaa1641af" rel="noopener noreferrer"&gt;https://qiita.com/xioncc/items/580750a119ffaa1641af&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  NeNe Records
&lt;/h3&gt;

&lt;p&gt;API-first typed CMS / flexible entity platform.&lt;/p&gt;

&lt;p&gt;It lets users define entity types, typed fields, records, public pages, and OpenAPI/MCP-facing operations.&lt;/p&gt;

&lt;p&gt;Repository:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/hideyukiMORI/nene-records" rel="noopener noreferrer"&gt;https://github.com/hideyukiMORI/nene-records&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What ties them together?
&lt;/h2&gt;

&lt;p&gt;The products are different, but they share the same direction.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. API-first boundaries
&lt;/h3&gt;

&lt;p&gt;The API is the contract.&lt;/p&gt;

&lt;p&gt;Admin screens, public pages, integrations, and AI tools should go through documented HTTP APIs.&lt;/p&gt;

&lt;p&gt;That makes behavior easier to test, review, and expose safely.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. OpenAPI as a shared language
&lt;/h3&gt;

&lt;p&gt;Each product aims to document its public API with OpenAPI.&lt;/p&gt;

&lt;p&gt;That matters for human developers.&lt;/p&gt;

&lt;p&gt;It also matters for AI agents.&lt;/p&gt;

&lt;p&gt;If the API is explicit, a tool can map to the operation without guessing database structure.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. MCP should call the app
&lt;/h3&gt;

&lt;p&gt;I wrote about this in my previous article:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/hideyukimori/mcp-should-not-mean-letting-ai-touch-your-database-57p1"&gt;https://dev.to/hideyukimori/mcp-should-not-mean-letting-ai-touch-your-database-57p1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My rule of thumb is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MCP tools should wrap application capabilities, not bypass the application.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That means:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI Agent
  -&amp;gt; MCP Tool
  -&amp;gt; documented HTTP API
  -&amp;gt; handler
  -&amp;gt; use case
  -&amp;gt; repository / transaction boundary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI Agent
  -&amp;gt; MCP Tool
  -&amp;gt; direct SQL query
  -&amp;gt; production database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Boring architecture on purpose
&lt;/h3&gt;

&lt;p&gt;The stack is intentionally not magical.&lt;/p&gt;

&lt;p&gt;Most products follow a shape like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Handler
  -&amp;gt; Use Case
  -&amp;gt; Repository
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The frontend is a client.&lt;/p&gt;

&lt;p&gt;The database stays behind repositories.&lt;/p&gt;

&lt;p&gt;The MCP layer stays behind HTTP APIs.&lt;/p&gt;

&lt;p&gt;This is boring.&lt;/p&gt;

&lt;p&gt;That is a feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why PHP?
&lt;/h2&gt;

&lt;p&gt;Because PHP still matters for small business software.&lt;/p&gt;

&lt;p&gt;It is widely deployable.&lt;/p&gt;

&lt;p&gt;It is easy to host.&lt;/p&gt;

&lt;p&gt;It is familiar to many developers who work on business systems.&lt;/p&gt;

&lt;p&gt;Modern PHP can be strict, typed, tested, and API-first.&lt;/p&gt;

&lt;p&gt;NENE2 is my attempt to keep PHP small and explicit instead of recreating a large full-stack framework.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is NENE2?
&lt;/h2&gt;

&lt;p&gt;NENE2 is the small framework underneath many of these tools.&lt;/p&gt;

&lt;p&gt;It provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PSR-style HTTP runtime&lt;/li&gt;
&lt;li&gt;explicit routing&lt;/li&gt;
&lt;li&gt;Problem Details error responses&lt;/li&gt;
&lt;li&gt;OpenAPI contracts&lt;/li&gt;
&lt;li&gt;database boundaries&lt;/li&gt;
&lt;li&gt;auth middleware&lt;/li&gt;
&lt;li&gt;optional frontend starter patterns&lt;/li&gt;
&lt;li&gt;local MCP server support&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is not trying to replace Laravel.&lt;/p&gt;

&lt;p&gt;It is a small foundation for building business APIs that humans and AI agents can both understand.&lt;/p&gt;

&lt;p&gt;First DEV article:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/hideyukimori/i-built-a-tiny-php-framework-for-ai-readable-business-apis-48eo"&gt;https://dev.to/hideyukimori/i-built-a-tiny-php-framework-for-ai-readable-business-apis-48eo&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  This is still a work in progress
&lt;/h2&gt;

&lt;p&gt;I do not want to overstate the maturity of the portfolio.&lt;/p&gt;

&lt;p&gt;Some tools are close to formal release.&lt;/p&gt;

&lt;p&gt;Some are still being refined.&lt;/p&gt;

&lt;p&gt;Some deployment paths are Docker-first today, with shared-hosting or managed paths still evolving.&lt;/p&gt;

&lt;p&gt;The goal of the articles I am publishing now is not to claim that every product is finished.&lt;/p&gt;

&lt;p&gt;The goal is to make the architecture, direction, and working software visible.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I am looking for
&lt;/h2&gt;

&lt;p&gt;I am especially interested in feedback from people who care about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;self-hosted business software&lt;/li&gt;
&lt;li&gt;API-first PHP&lt;/li&gt;
&lt;li&gt;OpenAPI contracts&lt;/li&gt;
&lt;li&gt;MCP and AI agent boundaries&lt;/li&gt;
&lt;li&gt;small tools for real operational workflows&lt;/li&gt;
&lt;li&gt;software for teams operating in Japan&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are building similar tools, or thinking about how AI agents should interact with business APIs, I would be happy to hear your perspective.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;GitHub profile: &lt;a href="https://github.com/hideyukiMORI" rel="noopener noreferrer"&gt;https://github.com/hideyukiMORI&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;NENE2: &lt;a href="https://github.com/hideyukiMORI/NENE2" rel="noopener noreferrer"&gt;https://github.com/hideyukiMORI/NENE2&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;NeNe Invoice: &lt;a href="https://github.com/hideyukiMORI/nene-invoice" rel="noopener noreferrer"&gt;https://github.com/hideyukiMORI/nene-invoice&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;NeNe Vault: &lt;a href="https://github.com/hideyukiMORI/nene-vault" rel="noopener noreferrer"&gt;https://github.com/hideyukiMORI/nene-vault&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;NeNe Deal: &lt;a href="https://github.com/hideyukiMORI/nene-deal" rel="noopener noreferrer"&gt;https://github.com/hideyukiMORI/nene-deal&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;NeNe Contact: &lt;a href="https://github.com/hideyukiMORI/nene-contact" rel="noopener noreferrer"&gt;https://github.com/hideyukiMORI/nene-contact&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;NeNe Records: &lt;a href="https://github.com/hideyukiMORI/nene-records" rel="noopener noreferrer"&gt;https://github.com/hideyukiMORI/nene-records&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;nene-mcp: &lt;a href="https://github.com/hideyukiMORI/nene-mcp" rel="noopener noreferrer"&gt;https://github.com/hideyukiMORI/nene-mcp&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I am still refining the NeNe series and NENE2.&lt;/p&gt;

&lt;p&gt;But the shape is becoming clearer:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;small, self-hosted, API-first business tools with AI-readable boundaries.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>php</category>
      <category>api</category>
      <category>ai</category>
    </item>
    <item>
      <title>MCP should not mean letting AI touch your database</title>
      <dc:creator>HideyukiMORI</dc:creator>
      <pubDate>Thu, 25 Jun 2026 13:18:14 +0000</pubDate>
      <link>https://dev.to/hideyukimori/mcp-should-not-mean-letting-ai-touch-your-database-57p1</link>
      <guid>https://dev.to/hideyukimori/mcp-should-not-mean-letting-ai-touch-your-database-57p1</guid>
      <description>&lt;p&gt;After publishing my first article about &lt;strong&gt;NENE2&lt;/strong&gt;, a small PHP framework for AI-readable business APIs, one comment stood out:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;consistent JSON envelopes matter more than ever when agents are parsing responses.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I agree.&lt;/p&gt;

&lt;p&gt;But predictable JSON is only one part of the story.&lt;/p&gt;

&lt;p&gt;The bigger question is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What should an AI agent be allowed to touch?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My answer is simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An MCP tool should call your application boundary, not your production database.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP is not a license to bypass the app
&lt;/h2&gt;

&lt;p&gt;MCP is useful because it gives AI tools a structured way to call capabilities.&lt;/p&gt;

&lt;p&gt;But if we use it carelessly, it can become a very polished shortcut around the application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI Agent
  -&amp;gt; MCP Tool
  -&amp;gt; direct SQL query
  -&amp;gt; production database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That may feel convenient during a demo.&lt;/p&gt;

&lt;p&gt;It is also exactly the kind of thing I do not want in business software.&lt;/p&gt;

&lt;p&gt;The database does not know the full application rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;validation&lt;/li&gt;
&lt;li&gt;authorization&lt;/li&gt;
&lt;li&gt;tenant isolation&lt;/li&gt;
&lt;li&gt;domain invariants&lt;/li&gt;
&lt;li&gt;audit logging&lt;/li&gt;
&lt;li&gt;rate limits&lt;/li&gt;
&lt;li&gt;Problem Details error behavior&lt;/li&gt;
&lt;li&gt;request IDs&lt;/li&gt;
&lt;li&gt;business workflow state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your application knows those things.&lt;/p&gt;

&lt;p&gt;So the agent should go through the application.&lt;/p&gt;

&lt;h2&gt;
  
  
  The safer shape
&lt;/h2&gt;

&lt;p&gt;The direction I use in NENE2 is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI Agent
  -&amp;gt; MCP Tool
  -&amp;gt; documented HTTP API
  -&amp;gt; handler
  -&amp;gt; use case
  -&amp;gt; repository / transaction boundary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is less magical.&lt;/p&gt;

&lt;p&gt;That is the point.&lt;/p&gt;

&lt;p&gt;If an AI agent asks for the status of a record, the tool should call the same read API that a normal client would call.&lt;/p&gt;

&lt;p&gt;If an AI agent creates or updates something, the tool should call the same write API that already validates the request, checks credentials, logs the request, and returns a predictable error shape.&lt;/p&gt;

&lt;p&gt;MCP should expose application capabilities.&lt;/p&gt;

&lt;p&gt;It should not invent a second, hidden application.&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenAPI as the tool contract
&lt;/h2&gt;

&lt;p&gt;In NENE2, OpenAPI is the public API contract.&lt;/p&gt;

&lt;p&gt;That makes it a natural starting point for MCP tool definitions.&lt;/p&gt;

&lt;p&gt;If the HTTP API says:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /examples/notes/{id}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then an MCP tool can map to that operation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;getExampleNoteById
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tool does not need to know where the note is stored.&lt;br&gt;
It does not need SQL.&lt;br&gt;
It does not need to load a &lt;code&gt;.env&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;It needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a tool name&lt;/li&gt;
&lt;li&gt;a JSON input schema&lt;/li&gt;
&lt;li&gt;an HTTP method and path&lt;/li&gt;
&lt;li&gt;the API base URL&lt;/li&gt;
&lt;li&gt;credentials when required&lt;/li&gt;
&lt;li&gt;a way to preserve errors and request metadata&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The API remains the contract.&lt;/p&gt;
&lt;h2&gt;
  
  
  Read tools first
&lt;/h2&gt;

&lt;p&gt;I think most MCP integrations should start with read tools.&lt;/p&gt;

&lt;p&gt;Read tools are easier to reason about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;health checks&lt;/li&gt;
&lt;li&gt;list records&lt;/li&gt;
&lt;li&gt;get one record&lt;/li&gt;
&lt;li&gt;inspect history&lt;/li&gt;
&lt;li&gt;export a manifest&lt;/li&gt;
&lt;li&gt;summarize a dashboard&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They still need authorization when the data is private, but they do not mutate state.&lt;/p&gt;

&lt;p&gt;That makes them a good first milestone for teams learning how agents interact with their product.&lt;/p&gt;

&lt;p&gt;In NENE2, MCP tools have safety levels such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;read&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;write&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;admin&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;destructive&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first tools should usually be &lt;code&gt;read&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Write tools need more design.&lt;/p&gt;

&lt;p&gt;Admin and destructive tools need even more.&lt;/p&gt;
&lt;h2&gt;
  
  
  Write tools need authentication and audit
&lt;/h2&gt;

&lt;p&gt;Write tools are not forbidden.&lt;/p&gt;

&lt;p&gt;But they should not be treated as a weekend shortcut.&lt;/p&gt;

&lt;p&gt;A write tool needs the same kind of design you would expect from any sensitive API:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;authentication&lt;/li&gt;
&lt;li&gt;authorization&lt;/li&gt;
&lt;li&gt;request validation&lt;/li&gt;
&lt;li&gt;audit fields&lt;/li&gt;
&lt;li&gt;request ID propagation&lt;/li&gt;
&lt;li&gt;useful error responses&lt;/li&gt;
&lt;li&gt;tests for failure and permission boundaries&lt;/li&gt;
&lt;li&gt;confirmation behavior for destructive actions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In local NENE2 development, write tools can call documented write endpoints.&lt;/p&gt;

&lt;p&gt;When &lt;code&gt;NENE2_LOCAL_JWT_SECRET&lt;/code&gt; is configured, write tools require a valid Bearer JWT.&lt;/p&gt;

&lt;p&gt;For production tools, the bar should be higher:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;owner&lt;/li&gt;
&lt;li&gt;allowed environments&lt;/li&gt;
&lt;li&gt;required scopes&lt;/li&gt;
&lt;li&gt;rate limits&lt;/li&gt;
&lt;li&gt;logging policy&lt;/li&gt;
&lt;li&gt;rollback or remediation behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Production MCP tools are product features.&lt;/p&gt;

&lt;p&gt;They are not debugging shortcuts.&lt;/p&gt;
&lt;h2&gt;
  
  
  Problem Details matter
&lt;/h2&gt;

&lt;p&gt;When an MCP tool calls an HTTP API, it should preserve the API's error behavior.&lt;/p&gt;

&lt;p&gt;For NENE2, that means &lt;strong&gt;RFC 9457 Problem Details&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If a request is invalid, the agent should see a structured error.&lt;/p&gt;

&lt;p&gt;If authentication fails, the agent should see a structured 401.&lt;/p&gt;

&lt;p&gt;If a domain object is not found, the agent should see a structured 404.&lt;/p&gt;

&lt;p&gt;The tool should not collapse everything into:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Something went wrong.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agents need predictable failure shapes too.&lt;/p&gt;

&lt;p&gt;Good error contracts are part of AI-readability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Credentials should stay out of the repository
&lt;/h2&gt;

&lt;p&gt;This sounds obvious, but it matters.&lt;/p&gt;

&lt;p&gt;MCP configuration often lives close to developer tooling.&lt;/p&gt;

&lt;p&gt;That makes it tempting to commit values that should never be committed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API keys&lt;/li&gt;
&lt;li&gt;bearer tokens&lt;/li&gt;
&lt;li&gt;local secrets&lt;/li&gt;
&lt;li&gt;private paths&lt;/li&gt;
&lt;li&gt;database URLs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;NENE2's guidance is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;document environment variable names&lt;/li&gt;
&lt;li&gt;do not commit secret values&lt;/li&gt;
&lt;li&gt;do not return credentials in tool metadata&lt;/li&gt;
&lt;li&gt;do not let MCP tools read raw &lt;code&gt;.env&lt;/code&gt; secrets&lt;/li&gt;
&lt;li&gt;do not print tokens in logs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A tool can say it requires a scope.&lt;/p&gt;

&lt;p&gt;It should not expose the credential.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where nene-mcp fits
&lt;/h2&gt;

&lt;p&gt;I also maintain &lt;strong&gt;nene-mcp&lt;/strong&gt;, a small PHP stdio MCP bridge.&lt;/p&gt;

&lt;p&gt;It can expose HTTP APIs as MCP tools using a committed &lt;code&gt;tools.json&lt;/code&gt; catalog.&lt;/p&gt;

&lt;p&gt;The shape is intentionally simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MCP client
  -&amp;gt; stdio JSON-RPC
  -&amp;gt; nene-mcp
  -&amp;gt; HTTP API
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is not a customer-facing API gateway.&lt;/p&gt;

&lt;p&gt;It is a sidecar for local development, staging, and small team workflows.&lt;/p&gt;

&lt;p&gt;It works best when the application already has documented HTTP endpoints and a clear tool catalog.&lt;/p&gt;

&lt;p&gt;That is the same idea again:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MCP should call the app, not bypass it.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A concrete rule of thumb
&lt;/h2&gt;

&lt;p&gt;When designing an MCP tool, I like to ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Could a normal HTTP client safely do this through the documented API?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If yes, the MCP tool is probably a good wrapper.&lt;/p&gt;

&lt;p&gt;If no, I ask another question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why does this capability exist only for the agent?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sometimes there is a good answer.&lt;/p&gt;

&lt;p&gt;Often there is not.&lt;/p&gt;

&lt;p&gt;If the capability is real business behavior, it probably deserves an application API, tests, documentation, and review.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means for "AI-readable" APIs
&lt;/h2&gt;

&lt;p&gt;An AI-readable API is not just an API with JSON.&lt;/p&gt;

&lt;p&gt;It is an API where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the routes are discoverable&lt;/li&gt;
&lt;li&gt;the schemas are predictable&lt;/li&gt;
&lt;li&gt;the errors are structured&lt;/li&gt;
&lt;li&gt;the boundaries are explicit&lt;/li&gt;
&lt;li&gt;credentials are handled deliberately&lt;/li&gt;
&lt;li&gt;tools map to documented operations&lt;/li&gt;
&lt;li&gt;business rules are not hidden in a side channel&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the design direction behind NENE2.&lt;/p&gt;

&lt;p&gt;It is also why I prefer boring architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Handler
  -&amp;gt; Use Case
  -&amp;gt; Repository
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent can understand it.&lt;/p&gt;

&lt;p&gt;The developer can review it.&lt;/p&gt;

&lt;p&gt;The tests can cover it.&lt;/p&gt;

&lt;p&gt;The database stays behind the application boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;NENE2: &lt;a href="https://github.com/hideyukiMORI/NENE2" rel="noopener noreferrer"&gt;https://github.com/hideyukiMORI/NENE2&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;nene-mcp: &lt;a href="https://github.com/hideyukiMORI/nene-mcp" rel="noopener noreferrer"&gt;https://github.com/hideyukiMORI/nene-mcp&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;First article: &lt;a href="https://dev.to/hideyukimori/i-built-a-tiny-php-framework-for-ai-readable-business-apis-48eo"&gt;https://dev.to/hideyukimori/i-built-a-tiny-php-framework-for-ai-readable-business-apis-48eo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub profile: &lt;a href="https://github.com/hideyukiMORI" rel="noopener noreferrer"&gt;https://github.com/hideyukiMORI&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I am still refining this approach through NENE2 and the surrounding NeNe OSS series.&lt;/p&gt;

&lt;p&gt;If you are building MCP tools for business APIs, I would be interested to hear where you draw the line between useful automation and unsafe shortcuts.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>opensource</category>
      <category>mcp</category>
    </item>
    <item>
      <title>I built a tiny PHP framework for AI-readable business APIs</title>
      <dc:creator>HideyukiMORI</dc:creator>
      <pubDate>Mon, 22 Jun 2026 13:25:37 +0000</pubDate>
      <link>https://dev.to/hideyukimori/i-built-a-tiny-php-framework-for-ai-readable-business-apis-48eo</link>
      <guid>https://dev.to/hideyukimori/i-built-a-tiny-php-framework-for-ai-readable-business-apis-48eo</guid>
      <description>&lt;p&gt;I have been building a small PHP framework called &lt;strong&gt;NENE2&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It is not a Laravel replacement.&lt;br&gt;
It is not a full-stack framework.&lt;br&gt;
It is not trying to hide everything behind magic.&lt;/p&gt;

&lt;p&gt;NENE2 is a tiny, API-first PHP foundation for building &lt;strong&gt;AI-readable business APIs&lt;/strong&gt;: small services with explicit HTTP boundaries, OpenAPI contracts, Problem Details errors, and optional MCP tool catalogs for AI agents.&lt;/p&gt;

&lt;p&gt;Repository: &lt;a href="https://github.com/hideyukiMORI/NENE2" rel="noopener noreferrer"&gt;hideyukiMORI/NENE2&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Why build another PHP framework?
&lt;/h2&gt;

&lt;p&gt;Most business software does not start as a beautiful greenfield platform.&lt;/p&gt;

&lt;p&gt;It starts as a request like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Can we expose this workflow as an API?"&lt;/li&gt;
&lt;li&gt;"Can an internal tool call this safely?"&lt;/li&gt;
&lt;li&gt;"Can an AI agent look up the status without touching the database?"&lt;/li&gt;
&lt;li&gt;"Can we keep this small enough that a team can actually understand it?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Large frameworks are great when you need their ecosystem.&lt;/p&gt;

&lt;p&gt;But for small business APIs, I often want something quieter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;explicit routes&lt;/li&gt;
&lt;li&gt;small handlers&lt;/li&gt;
&lt;li&gt;use cases that do not know about HTTP&lt;/li&gt;
&lt;li&gt;repository interfaces&lt;/li&gt;
&lt;li&gt;OpenAPI as the public contract&lt;/li&gt;
&lt;li&gt;predictable error responses&lt;/li&gt;
&lt;li&gt;tests that prove the behavior&lt;/li&gt;
&lt;li&gt;a clean boundary for AI / MCP tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the design space where NENE2 lives.&lt;/p&gt;
&lt;h2&gt;
  
  
  The core idea: API first, AI readable
&lt;/h2&gt;

&lt;p&gt;NENE2 assumes that the API is the primary surface.&lt;/p&gt;

&lt;p&gt;Server-rendered HTML can exist, and a React frontend starter can exist, but the backend should not become frontend build glue. The stable contract is the HTTP API.&lt;/p&gt;

&lt;p&gt;The framework is organized around:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PSR-7 / PSR-15 / PSR-17&lt;/strong&gt; HTTP runtime direction&lt;/li&gt;
&lt;li&gt;explicit routing and middleware&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenAPI 3.1&lt;/strong&gt; contract documentation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RFC 9457 Problem Details&lt;/strong&gt; for errors&lt;/li&gt;
&lt;li&gt;typed configuration objects&lt;/li&gt;
&lt;li&gt;PSR-11 dependency injection&lt;/li&gt;
&lt;li&gt;PDO database adapters and transaction boundaries&lt;/li&gt;
&lt;li&gt;optional React + TypeScript frontend starter&lt;/li&gt;
&lt;li&gt;local MCP server support behind the same API boundary&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is not to make the code clever.&lt;/p&gt;

&lt;p&gt;The goal is to make the code easy to inspect by a human developer, a reviewer, or an AI coding agent.&lt;/p&gt;
&lt;h2&gt;
  
  
  A boring architecture on purpose
&lt;/h2&gt;

&lt;p&gt;The reference domain example follows a simple flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP Handler
  -&amp;gt; Use Case
  -&amp;gt; Repository Interface
  -&amp;gt; PDO Adapter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, the Note CRUD reference implementation includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;route + handler classes&lt;/li&gt;
&lt;li&gt;use case classes&lt;/li&gt;
&lt;li&gt;readonly input/output DTOs&lt;/li&gt;
&lt;li&gt;repository interface&lt;/li&gt;
&lt;li&gt;PDO repository&lt;/li&gt;
&lt;li&gt;exception-to-Problem-Details mapping&lt;/li&gt;
&lt;li&gt;OpenAPI paths and schemas&lt;/li&gt;
&lt;li&gt;unit, HTTP, and PDO integration tests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is intentionally boring.&lt;/p&gt;

&lt;p&gt;I want application behavior to be visible in the file tree instead of being implied by framework convention.&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenAPI is the handoff document
&lt;/h2&gt;

&lt;p&gt;For NENE2, OpenAPI is not an afterthought.&lt;/p&gt;

&lt;p&gt;The repository ships an OpenAPI 3.1 document, Swagger UI, and runtime contract tests for example endpoints.&lt;/p&gt;

&lt;p&gt;Locally, after starting the app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt; app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Useful URLs include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:8200/health
http://localhost:8200/examples/ping
http://localhost:8200/openapi.php
http://localhost:8200/docs/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This matters because the OpenAPI file is also the bridge to other tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;generated clients&lt;/li&gt;
&lt;li&gt;contract tests&lt;/li&gt;
&lt;li&gt;documentation&lt;/li&gt;
&lt;li&gt;MCP tool catalogs&lt;/li&gt;
&lt;li&gt;AI-assisted development workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The API contract is the shared language.&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP should not mean "let AI touch the database"
&lt;/h2&gt;

&lt;p&gt;One of the ideas behind NENE2 is that AI agents should operate through documented API boundaries.&lt;/p&gt;

&lt;p&gt;I do not want an agent to directly query or mutate production tables.&lt;/p&gt;

&lt;p&gt;Instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI Agent
  -&amp;gt; MCP Tool
  -&amp;gt; documented HTTP API
  -&amp;gt; application use case
  -&amp;gt; repository / transaction boundary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;NENE2 includes local MCP server support and guidance for mapping tools to OpenAPI-backed operations.&lt;/p&gt;

&lt;p&gt;Read tools can inspect state.&lt;br&gt;
Write tools require explicit authentication.&lt;br&gt;
The application still owns validation, authorization, logging, and error handling.&lt;/p&gt;

&lt;p&gt;That boundary is the important part.&lt;/p&gt;
&lt;h2&gt;
  
  
  Field-tested through real apps
&lt;/h2&gt;

&lt;p&gt;NENE2 is not only a framework repository.&lt;/p&gt;

&lt;p&gt;I have been using it as the foundation for a family of self-hosted business OSS projects, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;NeNe Invoice&lt;/strong&gt; — quote and invoice management for Japan qualified invoices&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NeNe Vault&lt;/strong&gt; — received-document archive for electronic record retention&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NeNe Records&lt;/strong&gt; — typed headless CMS / flexible entity platform&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NeNe Deal&lt;/strong&gt; — lightweight B2B deal pipeline&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NeNe Contact&lt;/strong&gt; — embeddable contact forms&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;nene-mcp&lt;/strong&gt; — stdio MCP bridge for OpenAPI-backed HTTP APIs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;nene2-python&lt;/strong&gt; — Python reference implementation of the same philosophy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;nene2-js&lt;/strong&gt; / &lt;strong&gt;nene2-node&lt;/strong&gt; — TypeScript ecosystem pieces&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some of these products are Japan-specific.&lt;br&gt;
That is fine.&lt;/p&gt;

&lt;p&gt;The point is that the framework is tested against real business shapes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;authentication&lt;/li&gt;
&lt;li&gt;multi-tenancy&lt;/li&gt;
&lt;li&gt;OpenAPI&lt;/li&gt;
&lt;li&gt;admin UIs&lt;/li&gt;
&lt;li&gt;Docker local stacks&lt;/li&gt;
&lt;li&gt;database migrations&lt;/li&gt;
&lt;li&gt;exports&lt;/li&gt;
&lt;li&gt;audit logs&lt;/li&gt;
&lt;li&gt;MCP catalogs&lt;/li&gt;
&lt;li&gt;AI-readable documentation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Framework design gets better when it is forced through real applications.&lt;/p&gt;
&lt;h2&gt;
  
  
  What NENE2 is not
&lt;/h2&gt;

&lt;p&gt;NENE2 is deliberately small.&lt;/p&gt;

&lt;p&gt;It is not:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a Laravel or Symfony replacement&lt;/li&gt;
&lt;li&gt;a no-code platform&lt;/li&gt;
&lt;li&gt;an ORM-first framework&lt;/li&gt;
&lt;li&gt;a CMS&lt;/li&gt;
&lt;li&gt;a frontend framework&lt;/li&gt;
&lt;li&gt;an AI agent runtime&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is a small foundation for teams or solo developers who want explicit PHP APIs and documented handoff points.&lt;/p&gt;
&lt;h2&gt;
  
  
  Quick start
&lt;/h2&gt;

&lt;p&gt;Clone the repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/hideyukiMORI/NENE2.git
&lt;span class="nb"&gt;cd &lt;/span&gt;NENE2
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
docker compose build
docker compose run &lt;span class="nt"&gt;--rm&lt;/span&gt; app composer &lt;span class="nb"&gt;install
&lt;/span&gt;docker compose run &lt;span class="nt"&gt;--rm&lt;/span&gt; app composer check
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt; app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or install it as a Composer dependency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require hideyukimori/nene2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;NENE2 targets PHP 8.4+ and uses Docker as the standard development runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "AI-readable" matters
&lt;/h2&gt;

&lt;p&gt;AI-assisted development is not only about generating code.&lt;/p&gt;

&lt;p&gt;It is also about whether the system is readable enough for an agent to help safely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can it find the route?&lt;/li&gt;
&lt;li&gt;Can it understand the request and response shape?&lt;/li&gt;
&lt;li&gt;Can it see the use case?&lt;/li&gt;
&lt;li&gt;Can it run the focused tests?&lt;/li&gt;
&lt;li&gt;Can it avoid bypassing validation?&lt;/li&gt;
&lt;li&gt;Can it call a documented tool instead of guessing a database query?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;NENE2 tries to make those answers obvious.&lt;/p&gt;

&lt;p&gt;Not by adding more magic.&lt;/p&gt;

&lt;p&gt;By removing ambiguity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;NENE2: &lt;a href="https://github.com/hideyukiMORI/NENE2" rel="noopener noreferrer"&gt;https://github.com/hideyukiMORI/NENE2&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Packagist: &lt;a href="https://packagist.org/packages/hideyukimori/nene2" rel="noopener noreferrer"&gt;https://packagist.org/packages/hideyukimori/nene2&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;nene2-python: &lt;a href="https://github.com/hideyukiMORI/nene2-python" rel="noopener noreferrer"&gt;https://github.com/hideyukiMORI/nene2-python&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;nene2-js: &lt;a href="https://github.com/hideyukiMORI/nene2-js" rel="noopener noreferrer"&gt;https://github.com/hideyukiMORI/nene2-js&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;nene-mcp: &lt;a href="https://github.com/hideyukiMORI/nene-mcp" rel="noopener noreferrer"&gt;https://github.com/hideyukiMORI/nene-mcp&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub profile: &lt;a href="https://github.com/hideyukiMORI" rel="noopener noreferrer"&gt;https://github.com/hideyukiMORI&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I am still refining the framework and the surrounding NeNe OSS series.&lt;/p&gt;

&lt;p&gt;If you are interested in API-first PHP, OpenAPI, MCP, or AI-readable business software, feedback is very welcome.&lt;/p&gt;

</description>
      <category>php</category>
      <category>opensource</category>
      <category>api</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
