<?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: Loic Aron Mbassi Ewolo</title>
    <description>The latest articles on DEV Community by Loic Aron Mbassi Ewolo (@nameless0l).</description>
    <link>https://dev.to/nameless0l</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%2F4035374%2F6a718bf4-7c77-47b0-b229-3631f1fc55eb.jpg</url>
      <title>DEV Community: Loic Aron Mbassi Ewolo</title>
      <link>https://dev.to/nameless0l</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nameless0l"/>
    <language>en</language>
    <item>
      <title>A complete Laravel API with clean architecture in 30 seconds, tests included</title>
      <dc:creator>Loic Aron Mbassi Ewolo</dc:creator>
      <pubDate>Sat, 18 Jul 2026 20:23:24 +0000</pubDate>
      <link>https://dev.to/nameless0l/a-complete-laravel-api-with-clean-architecture-in-30-seconds-tests-included-1h0h</link>
      <guid>https://dev.to/nameless0l/a-complete-laravel-api-with-clean-architecture-in-30-seconds-tests-included-1h0h</guid>
      <description>&lt;p&gt;I counted the files I create every time I add a resource to a Laravel API. Model, migration, controller, a form request, a resource, a factory, a seeder, a policy. If the project cares about architecture, add a DTO and a service class. Then the tests.&lt;/p&gt;

&lt;p&gt;Twelve files before the first line of actual business logic. I got tired of typing them, so I wrote a generator. It's MIT, I maintain it solo, and v3.6 shipped this week.&lt;/p&gt;

&lt;h2&gt;
  
  
  One command
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require &lt;span class="nt"&gt;--dev&lt;/span&gt; nameless/laravel-api-generator

php artisan make:fullapi Post &lt;span class="nt"&gt;--fields&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"title:string,content:text,status:enum(draft,published),published_at:datetime"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/
├── DTO/PostDTO.php
├── Enums/Status.php
├── Http/
│   ├── Controllers/PostController.php
│   ├── Requests/PostRequest.php
│   └── Resources/PostResource.php
├── Models/Post.php
├── Policies/PostPolicy.php
└── Services/PostService.php
database/
├── factories/PostFactory.php
├── migrations/xxxx_create_posts_table.php
└── seeders/PostSeeder.php
tests/
├── Feature/PostControllerTest.php
└── Unit/PostServiceTest.php
routes/api.php  ← apiResource registered
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;[GIF demo.gif (voir fichier 12) : vraie exécution en terminal, pas Claude Design]&lt;/p&gt;

&lt;h2&gt;
  
  
  The VS Code extension: same engine, no terminal needed
&lt;/h2&gt;

&lt;p&gt;[VISUEL 11 (fichier 13) : VRAI screenshot de l'extension VS Code, habillé ensuite dans Claude Design]&lt;/p&gt;

&lt;p&gt;If you'd rather click than type, the free &lt;a href="https://marketplace.visualstudio.com/items?itemName=Nameless0l.laravel-api-generator" rel="noopener noreferrer"&gt;VS Code extension&lt;/a&gt; drives the same &lt;code&gt;make:fullapi&lt;/code&gt; command, so the files are identical. You describe the entity in a form (fields, enums, relations, options) and a live preview shows the exact code before anything is written. Or you skip the form and import from your existing database, a YAML schema, a Mermaid diagram, or an OpenAPI/Swagger spec.&lt;/p&gt;

&lt;p&gt;The button I always demo first is &lt;strong&gt;Open API Docs&lt;/strong&gt;: it checks that Scramble is installed (and offers to install it if not), finds a running Laravel server or starts &lt;code&gt;php artisan serve&lt;/code&gt; itself, detects the port, and opens the interactive docs of your new API in the browser. Migrations and seeding are one-click too, and if &lt;code&gt;.env&lt;/code&gt; is missing the extension offers to create it from &lt;code&gt;.env.example&lt;/code&gt;. You can go from an empty Laravel project to a browsable, documented, tested API without opening a terminal once.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwsl2r44almbl3aqu5nfv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwsl2r44almbl3aqu5nfv.png" alt="scramble image" width="799" height="364"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I actually care about: the architecture
&lt;/h2&gt;

&lt;p&gt;Plenty of tools can spit out a model and a migration. What I wanted was the layering I end up building by hand on every serious project. The generated controller is thin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;PostRequest&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$dto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PostDTO&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;fromRequest&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="nv"&gt;$post&lt;/span&gt; &lt;span class="o"&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="n"&gt;service&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;$dto&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PostResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$post&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;Validation lives in the form request, business logic in &lt;code&gt;PostService&lt;/code&gt;, and data crosses layers as a typed readonly &lt;code&gt;PostDTO&lt;/code&gt;. When the project grows, the place where new logic should go already exists. That's the whole point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Models your IDE can read
&lt;/h2&gt;

&lt;p&gt;Each generated model comes with a real PHPDoc block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cd"&gt;/**
 * @property int $id
 * @property string $title
 * @property string $content
 * @property Status $status
 * @property \Illuminate\Support\Carbon|null $published_at
 * @property-read \Illuminate\Database\Eloquent\Collection&amp;lt;int, Comment&amp;gt; $comments
 */&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So &lt;code&gt;$post-&amp;gt;title&lt;/code&gt; autocompletes in VS Code and PhpStorm without installing ide-helper. The generator already knows every field and relation at generation time; writing the docblock costs it nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What &lt;code&gt;enum(draft,published)&lt;/code&gt; produced
&lt;/h2&gt;

&lt;p&gt;That one field definition created five coherent pieces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;App\Enums\Status&lt;/code&gt;, a native backed enum&lt;/li&gt;
&lt;li&gt;the cast on the model&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Rule::enum(Status::class)&lt;/code&gt; in both form requests&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fake()-&amp;gt;randomElement(Status::cases())&lt;/code&gt; in the factory&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;$table-&amp;gt;enum('status', [...])&lt;/code&gt; in the migration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before v3.6 the package just mapped enums to strings. Wiring the whole chain took longer than I expected, mostly because of the factory.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tests are written, not scaffolded
&lt;/h2&gt;

&lt;p&gt;This was my line in the sand. Most generators leave you empty test classes. Here &lt;code&gt;php artisan test&lt;/code&gt; is green right after generation: index, store, validation errors, update, delete are all covered with real assertions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'creates a post'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;raw&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;postJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/api/posts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertCreated&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;assertDatabaseHas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'posts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'title'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'title'&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;That's the Pest style, from the &lt;code&gt;--pest&lt;/code&gt; flag. Without it you get PHPUnit classes with the same coverage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Docs, Postman, auth
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;--postman&lt;/code&gt; writes a ready-to-import collection at the project root. The controllers are written so &lt;a href="https://scramble.dedoc.co" rel="noopener noreferrer"&gt;Scramble&lt;/a&gt; can build OpenAPI docs from them without annotations. &lt;code&gt;--auth&lt;/code&gt; scaffolds Sanctum (register, login, logout, middleware on your resources).&lt;/p&gt;

&lt;p&gt;There is also a &lt;code&gt;--from-database&lt;/code&gt; mode that reverse-engineers an existing database into full APIs, but that deserves its own article.&lt;/p&gt;

&lt;h2&gt;
  
  
  Zero lock-in
&lt;/h2&gt;

&lt;p&gt;The package installs with &lt;code&gt;--dev&lt;/code&gt; and the generated code has no dependency on it. No base classes, no runtime helpers, nothing to &lt;code&gt;use&lt;/code&gt;. You can remove the generator after generating and the app doesn't notice. If you don't like the generated style, publish the 24 stubs (&lt;code&gt;php artisan vendor:publish --tag=api-generator-stubs&lt;/code&gt;) and edit whichever ones you want.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it stops
&lt;/h2&gt;

&lt;p&gt;It scaffolds a clean REST baseline and then it's out of the picture. Your domain logic, your weird queries, your edge cases: still your job. The difference is that you write them inside a service layer that already exists, with baseline tests already passing.&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require &lt;span class="nt"&gt;--dev&lt;/span&gt; nameless/laravel-api-generator
php artisan make:fullapi Product &lt;span class="nt"&gt;--fields&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"name:string,price:decimal,stock:integer"&lt;/span&gt; &lt;span class="nt"&gt;--pest&lt;/span&gt;
php artisan &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Docs: &lt;a href="https://nameless0l.github.io/laravel-api-generator/" rel="noopener noreferrer"&gt;https://nameless0l.github.io/laravel-api-generator/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/Nameless0l/laravel-api-generator" rel="noopener noreferrer"&gt;https://github.com/Nameless0l/laravel-api-generator&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;VS Code extension: &lt;a href="https://marketplace.visualstudio.com/items?itemName=Nameless0l.laravel-api-generator" rel="noopener noreferrer"&gt;https://marketplace.visualstudio.com/items?itemName=Nameless0l.laravel-api-generator&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you try it and something breaks, open an issue. Schema edge cases reported by users have driven most of the recent releases, and I'd rather hear about yours than guess.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
