<?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>Forem: A0mineTV</title>
    <description>The latest articles on Forem by A0mineTV (@blamsa0mine).</description>
    <link>https://forem.com/blamsa0mine</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2455723%2Fdd380ab3-92f1-4067-a1b9-16b49c6b5a13.jpg</url>
      <title>Forem: A0mineTV</title>
      <link>https://forem.com/blamsa0mine</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/blamsa0mine"/>
    <language>en</language>
    <item>
      <title>How I Built a Support Ticket MCP Server in Laravel</title>
      <dc:creator>A0mineTV</dc:creator>
      <pubDate>Tue, 31 Mar 2026 18:57:32 +0000</pubDate>
      <link>https://forem.com/blamsa0mine/how-i-built-a-support-ticket-mcp-server-in-laravel-1pdk</link>
      <guid>https://forem.com/blamsa0mine/how-i-built-a-support-ticket-mcp-server-in-laravel-1pdk</guid>
      <description>&lt;p&gt;Model Context Protocol is much easier to understand when it is attached to a real workflow.&lt;/p&gt;

&lt;p&gt;In my Laravel project, I wanted a support-focused MCP server that could do three concrete things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create a support ticket&lt;/li&gt;
&lt;li&gt;read a ticket from a resource URI&lt;/li&gt;
&lt;li&gt;generate a polished summary that is ready to send to a customer or teammate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of building a generic demo, I kept the scope narrow and useful. The result is a local &lt;code&gt;support&lt;/code&gt; MCP server powered by &lt;code&gt;laravel/mcp&lt;/code&gt;, with one tool, one resource, and one prompt working together as a small support system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is a good MCP use case
&lt;/h2&gt;

&lt;p&gt;A lot of MCP examples stop at "here is a tool that returns some JSON." That is fine for learning the API, but it does not show why the protocol is interesting.&lt;/p&gt;

&lt;p&gt;Support workflows are a better fit because they combine three different kinds of interaction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an action that changes state,&lt;/li&gt;
&lt;li&gt;a resource that can be fetched again later,&lt;/li&gt;
&lt;li&gt;a prompt that turns raw operational data into something a human can actually use.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That maps cleanly to MCP:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tool&lt;/strong&gt;: create the ticket.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource&lt;/strong&gt;: fetch the ticket by identifier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompt&lt;/strong&gt;: transform the ticket into a professional summary.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This separation is what I like most about the approach. The AI client does not need custom knowledge about my Laravel app. It only needs to know which MCP capabilities are available.&lt;/p&gt;

&lt;h2&gt;
  
  
  Registering the server in Laravel
&lt;/h2&gt;

&lt;p&gt;The first step in my project was registering a local MCP server in &lt;code&gt;routes/ai.php&lt;/code&gt;:&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="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Mcp\Servers\SupportServer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Laravel\Mcp\Facades\Mcp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;Mcp&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;local&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'support'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;SupportServer&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gives the app a dedicated MCP entry point for support operations. I also have a separate CRM server in the same project, which keeps responsibilities isolated instead of pushing every AI capability into one giant surface area.&lt;/p&gt;

&lt;h2&gt;
  
  
  Defining the support server
&lt;/h2&gt;

&lt;p&gt;The server itself is intentionally small. Its job is to compose capabilities, not to hold business logic. In &lt;code&gt;app/Mcp/Servers/SupportServer.php&lt;/code&gt;, I register the three pieces that make the workflow useful:&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SupportServer&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Server&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nc"&gt;CreateTicketTool&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$resources&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nc"&gt;TicketResource&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$prompts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nc"&gt;SummarizeTicketPrompt&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&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 structure is one of the reasons Laravel feels comfortable here. The framework already encourages small classes with clear responsibilities, and &lt;code&gt;laravel/mcp&lt;/code&gt; fits that style well.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ticket creation tool
&lt;/h2&gt;

&lt;p&gt;The first capability I needed was a way for an AI client to create a support ticket safely.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;app/Mcp/Tools/CreateTicketTool.php&lt;/code&gt;, the tool validates three inputs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;title&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;description&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;priority&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It then creates the ticket and returns a structured response with the fields an agent actually needs next:&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;return&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;structured&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$ticket&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&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;$ticket&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'status'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$ticket&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'priority'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$ticket&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;priority&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;This is an important detail. I do not want the tool to return a blob of unstructured text when the next step may depend on the new ticket ID. MCP tools are much more useful when they return predictable data that another step can consume immediately.&lt;/p&gt;

&lt;p&gt;I also defined the schema explicitly. That gives the client a machine-readable contract instead of forcing it to guess the arguments:&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;schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;JsonSchema&lt;/span&gt; &lt;span class="nv"&gt;$schema&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&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;$schema&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;string&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;required&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="s1"&gt;'description'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$schema&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;string&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;required&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="s1"&gt;'priority'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$schema&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;string&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;enum&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'low'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'medium'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'high'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'medium'&lt;/span&gt;&lt;span class="p"&gt;),&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;For support workflows, this matters a lot. Validation rules are not just a backend concern anymore. They become part of the interface between your Laravel app and the AI client.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ticket resource
&lt;/h2&gt;

&lt;p&gt;Creating a ticket is only half of the story. Once the ticket exists, an agent should be able to retrieve it again without calling a mutation tool.&lt;/p&gt;

&lt;p&gt;That is where the resource comes in. In &lt;code&gt;app/Mcp/Resources/TicketResource.php&lt;/code&gt;, the resource receives a ticket identifier, loads the record, and returns a readable text representation:&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;return&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;"Ticket #&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$ticket&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;
    &lt;span class="s2"&gt;"Titre: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$ticket&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;
    &lt;span class="s2"&gt;"Statut: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$ticket&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;
    &lt;span class="s2"&gt;"Priorité: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$ticket&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;priority&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;
    &lt;span class="s2"&gt;"Description:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$ticket&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I like this pattern because it makes the ticket easy to reuse in multiple contexts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an assistant can read it before answering a user&lt;/li&gt;
&lt;li&gt;another prompt can summarize it&lt;/li&gt;
&lt;li&gt;a human can inspect the output without decoding JSON&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a small design choice, but it makes the system more composable. A tool changes state. A resource exposes state. Keeping those responsibilities separate leads to cleaner MCP surfaces.&lt;/p&gt;

&lt;h2&gt;
  
  
  The prompt that turns raw data into a ready-to-send summary
&lt;/h2&gt;

&lt;p&gt;The third piece is the one that makes the demo feel like a real support workflow rather than a CRUD exercise.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;app/Mcp/Prompts/SummarizeTicketPrompt.php&lt;/code&gt;, I created a prompt that takes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;code&gt;tone&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;the raw &lt;code&gt;ticket_text&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The prompt then instructs the model to return a structured support summary covering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the problem&lt;/li&gt;
&lt;li&gt;the impact&lt;/li&gt;
&lt;li&gt;the suggested priority&lt;/li&gt;
&lt;li&gt;the next action.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is the core idea:&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;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;"You are a support assistant. Summarize the ticket below in a &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$tone&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; tone. "&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;
        &lt;span class="s2"&gt;"Return: problem, impact, suggested priority, next action."&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;asAssistant&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ticket_text&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;This is the part many teams skip. They expose data, but they do not shape the final output. In support, the final wording matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;internal teams may want concise operational summaries&lt;/li&gt;
&lt;li&gt;customer-facing teams may want a more professional tone&lt;/li&gt;
&lt;li&gt;some situations need a friendlier message without losing the technical facts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Packaging that as an MCP prompt means the formatting logic is reusable instead of being reinvented in every client.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I like this architecture
&lt;/h2&gt;

&lt;p&gt;What I ended up with is not a huge system. It is a very small one. That is exactly why it works well.&lt;/p&gt;

&lt;p&gt;Each MCP primitive has a clear role:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the &lt;strong&gt;tool&lt;/strong&gt; creates the record,&lt;/li&gt;
&lt;li&gt;the &lt;strong&gt;resource&lt;/strong&gt; exposes the record,&lt;/li&gt;
&lt;li&gt;the &lt;strong&gt;prompt&lt;/strong&gt; transforms the record into communication.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That separation makes the project easier to extend. If I want to go further, I already know the next logical steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;add a tool to update ticket status,&lt;/li&gt;
&lt;li&gt;expose a resource for recent tickets,&lt;/li&gt;
&lt;li&gt;create prompts for escalation notes or customer replies,&lt;/li&gt;
&lt;li&gt;connect the same support server to external AI clients.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Laravel gives me the application structure, validation, and persistence patterns I already trust. MCP adds a clean interface layer on top of that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;The main lesson from this project is that MCP becomes much more compelling when you model a real workflow instead of isolated demo commands.&lt;/p&gt;

&lt;p&gt;In my Laravel app, the support server is useful because it does three connected things well:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;it creates a ticket with validated input,&lt;/li&gt;
&lt;li&gt;it lets the client read that ticket back as a resource,&lt;/li&gt;
&lt;li&gt;it provides a prompt that converts raw ticket data into a professional summary.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is enough to move from "AI can call my backend" to "AI can participate in a support flow with clear boundaries."&lt;/p&gt;

&lt;p&gt;If you are experimenting with &lt;code&gt;laravel/mcp&lt;/code&gt;, I would strongly recommend starting with a narrow domain like support, sales handoff, onboarding, or incident triage. Pick one workflow, then model it with one tool, one resource, and one prompt. You will learn much more than you would from a generic demo.&lt;/p&gt;

&lt;p&gt;If I keep iterating on this project, the next step will be turning this support server into a fuller ticket operations layer with richer resources and multi-step support actions. But even in its current form, it already shows why Laravel and MCP fit together so naturally.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>ai</category>
      <category>mcp</category>
    </item>
    <item>
      <title>PHP 8.5: The Changes That Really Matter in Everyday Development</title>
      <dc:creator>A0mineTV</dc:creator>
      <pubDate>Wed, 25 Mar 2026 19:09:06 +0000</pubDate>
      <link>https://forem.com/blamsa0mine/php-85-the-changes-that-really-matter-in-everyday-development-1pdl</link>
      <guid>https://forem.com/blamsa0mine/php-85-the-changes-that-really-matter-in-everyday-development-1pdl</guid>
      <description>&lt;p&gt;PHP 8.5 brings a solid mix of language improvements, practical extension updates, and deprecations worth cleaning up before PHP 9.0.&lt;/p&gt;

&lt;p&gt;It is not a flashy release built around one massive headline feature. Instead, it improves day-to-day developer experience in a lot of useful places: cleaner data transformations, better URL handling, nicer debugging, a few welcome standard library additions, and a clearer path away from old legacy patterns.&lt;/p&gt;

&lt;p&gt;In this article, I want to focus on what actually matters when writing and maintaining PHP code: what you can start using right away, what deserves attention during migration, and which changes are most useful in real-world projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pipe operator makes transformations easier to read
&lt;/h2&gt;

&lt;p&gt;One of the most visible additions in PHP 8.5 is the pipe operator, &lt;code&gt;|&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Its purpose is simple: pass the result of one expression into the next one from left to right. That makes chained transformations much easier to read than deeply nested function calls.&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="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="nv"&gt;$title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'  PHP 8.5 Released  '&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$slug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$title&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;strtolower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'-'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$slug&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// php-8.5-released&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is especially nice for string processing, array transformations, or small data-cleaning pipelines.&lt;/p&gt;

&lt;p&gt;The biggest benefit is readability. You read the code in the same order it runs.&lt;/p&gt;

&lt;p&gt;One thing to keep in mind: functions that need multiple arguments, like &lt;code&gt;str_replace()&lt;/code&gt;, will often require a small closure.&lt;/p&gt;

&lt;h2&gt;
  
  
  The new URI extension is one of the most useful additions
&lt;/h2&gt;

&lt;p&gt;PHP 8.5 also introduces a built-in URI extension.&lt;/p&gt;

&lt;p&gt;This is a big deal because URL handling in many projects still relies on a mix of &lt;code&gt;parse_url()&lt;/code&gt;, string concatenation, and custom helpers. That usually works until it does not.&lt;/p&gt;

&lt;p&gt;The new API gives PHP a cleaner and more structured way to work with URIs and URLs.&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="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Uri\Rfc3986\Uri&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'https://example.com/blog/php-8-5'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$updated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/blog/php-8-5/comments'&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;withQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'page=2&amp;amp;sort=recent'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$updated&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// https://example.com/blog/php-8-5/comments?page=2&amp;amp;sort=recent&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That makes a lot of sense in apps dealing with pagination, canonical URLs, crawlers, API clients, or SEO tooling.&lt;/p&gt;

&lt;p&gt;It is one of those additions that will probably save more bugs than it gets credit for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Small new functions that remove a lot of noise
&lt;/h2&gt;

&lt;p&gt;PHP 8.5 also adds several small functions and APIs that reduce boilerplate.&lt;/p&gt;

&lt;p&gt;Among the most useful are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;array_first()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;array_last()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;get_error_handler()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;get_exception_handler()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Locale::isRightToLeft()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;locale_is_right_to_left()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;IntlListFormatter&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FILTER_THROW_ON_FAILURE&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is a simple example with &lt;code&gt;array_first()&lt;/code&gt; and &lt;code&gt;array_last()&lt;/code&gt;:&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="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="nv"&gt;$items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'PHP'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Laravel'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Vue'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="nb"&gt;var_dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;array_first&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$items&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// PHP&lt;/span&gt;
&lt;span class="nb"&gt;var_dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;array_last&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$items&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;  &lt;span class="c1"&gt;// Vue&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here is one with &lt;code&gt;FILTER_THROW_ON_FAILURE&lt;/code&gt;:&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="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;filter_var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;'vincent@@example.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="no"&gt;FILTER_VALIDATE_EMAIL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="no"&gt;FILTER_THROW_ON_FAILURE&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Throwable&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'Invalid email'&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;None of these changes are huge on their own.&lt;/p&gt;

&lt;p&gt;But together, they make code a little more direct and a little less repetitive, which is exactly the kind of improvement that ages well.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;#[\NoDiscard]&lt;/code&gt; helps prevent silent mistakes
&lt;/h2&gt;

&lt;p&gt;Another interesting addition is the &lt;code&gt;#[\NoDiscard]&lt;/code&gt; attribute.&lt;/p&gt;

&lt;p&gt;It allows you to mark a function whose return value should not be ignored.&lt;/p&gt;

&lt;p&gt;That is especially useful for transformation functions where forgetting to use the returned value is usually a bug.&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="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="na"&gt;#[\NoDiscard]&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;normalizeEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$email&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;strtolower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$email&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nv"&gt;$email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;normalizeEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'  Vincent.Capek@Example.COM  '&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PHP 8.5 also introduces the &lt;code&gt;(void)&lt;/code&gt; cast so you can explicitly ignore a return value when that is intentional.&lt;/p&gt;

&lt;p&gt;This makes intent clearer and helps distinguish real mistakes from deliberate choices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fatal errors are more useful now
&lt;/h2&gt;

&lt;p&gt;PHP 8.5 improves debugging by adding stack traces to fatal errors.&lt;/p&gt;

&lt;p&gt;This sounds small, but in practice it is a very welcome quality-of-life improvement.&lt;/p&gt;

&lt;p&gt;When something crashes badly, getting more context immediately can save time, especially in larger apps or older codebases where tracking the source of a fatal error can be annoying.&lt;/p&gt;

&lt;p&gt;It is one of those improvements you do not think about until the moment you need it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical extension improvements you will actually notice
&lt;/h2&gt;

&lt;p&gt;PHP 8.5 also includes several useful updates across built-in extensions.&lt;/p&gt;

&lt;p&gt;A few stand out more than others in day-to-day work.&lt;/p&gt;

&lt;h3&gt;
  
  
  DOM gets &lt;code&gt;outerHTML&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Dom\Element::$outerHTML&lt;/code&gt; is finally available.&lt;/p&gt;

&lt;p&gt;That is great for scraping, HTML cleanup, testing, or any tool that manipulates markup.&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="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="nv"&gt;$doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Dom\HTMLDocument&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;createFromString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'&amp;lt;article class="post"&amp;gt;&amp;lt;h1&amp;gt;Hello&amp;lt;/h1&amp;gt;&amp;lt;/article&amp;gt;'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$article&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$doc&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'article.post'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$article&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;outerHTML&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;getimagesize()&lt;/code&gt; improves support for modern formats
&lt;/h3&gt;

&lt;p&gt;PHP 8.5 improves &lt;code&gt;getimagesize()&lt;/code&gt; support for HEIF, HEIC, and SVG.&lt;/p&gt;

&lt;p&gt;That is genuinely useful for uploads, media libraries, CMS features, and portfolio-like projects where modern image formats show up more often.&lt;/p&gt;

&lt;h3&gt;
  
  
  Partitioned cookies are now supported
&lt;/h3&gt;

&lt;p&gt;Cookies and sessions now support the &lt;code&gt;partitioned&lt;/code&gt; flag.&lt;/p&gt;

&lt;p&gt;This matters more in modern browser environments where third-party cookie behavior has become stricter.&lt;/p&gt;

&lt;h3&gt;
  
  
  cURL exposes more diagnostic information
&lt;/h3&gt;

&lt;p&gt;PHP 8.5 adds more details to &lt;code&gt;curl_getinfo()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That helps when debugging outgoing HTTP requests, proxies, authentication behavior, or connection timing.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;mail()&lt;/code&gt; reports failures more honestly
&lt;/h3&gt;

&lt;p&gt;If you still use &lt;code&gt;mail()&lt;/code&gt; in some environment, PHP 8.5 improves error reporting around sendmail failures.&lt;/p&gt;

&lt;p&gt;That does not modernize &lt;code&gt;mail()&lt;/code&gt;, but it does make it less opaque.&lt;/p&gt;

&lt;h2&gt;
  
  
  The deprecations are not scary, but they are worth cleaning up
&lt;/h2&gt;

&lt;p&gt;PHP 8.5 also deprecates a number of old patterns.&lt;/p&gt;

&lt;p&gt;Most of them are not difficult to fix, but they are exactly the sort of things that can sit around in a codebase for years if ignored.&lt;/p&gt;

&lt;p&gt;Some of the most important ones are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;case;&lt;/code&gt; instead of &lt;code&gt;case:&lt;/code&gt; in &lt;code&gt;switch&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;non-canonical casts like &lt;code&gt;(boolean)&lt;/code&gt;, &lt;code&gt;(integer)&lt;/code&gt;, &lt;code&gt;(double)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;backticks as an alias for &lt;code&gt;shell_exec()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;__sleep()&lt;/code&gt; and &lt;code&gt;__wakeup()&lt;/code&gt; in favor of &lt;code&gt;__serialize()&lt;/code&gt; and &lt;code&gt;__unserialize()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;using &lt;code&gt;null&lt;/code&gt; as an array offset or in &lt;code&gt;array_key_exists()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;incrementing non-numeric strings with &lt;code&gt;++&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;legacy functions like &lt;code&gt;curl_close()&lt;/code&gt;, &lt;code&gt;imagedestroy()&lt;/code&gt;, &lt;code&gt;finfo_close()&lt;/code&gt;, &lt;code&gt;xml_parser_free()&lt;/code&gt;, &lt;code&gt;mysqli_execute()&lt;/code&gt;, and &lt;code&gt;socket_set_timeout()&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is a simple before-and-after example:&lt;/p&gt;

&lt;p&gt;Before:&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="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="nv"&gt;$value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$input&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sb"&gt;`ls -la`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s1"&gt;'draft'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'draft'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;break&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;After:&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="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="nv"&gt;$value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$input&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;shell_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ls -la'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s1"&gt;'draft'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'draft'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;break&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;These are not dramatic changes, but they are good signals.&lt;/p&gt;

&lt;p&gt;They push PHP code toward more consistency and away from old habits that are either confusing, unnecessary, or easy to misuse.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would prioritize first in a real migration
&lt;/h2&gt;

&lt;p&gt;If I were upgrading a real project to PHP 8.5, I would focus on three things first.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Try the features that can simplify real code
&lt;/h3&gt;

&lt;p&gt;The pipe operator and the new URI extension are the two most interesting practical additions to experiment with right away.&lt;/p&gt;

&lt;p&gt;They can make everyday code cleaner without needing a big architectural change.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Clean up easy deprecations early
&lt;/h3&gt;

&lt;p&gt;Things like &lt;code&gt;(boolean)&lt;/code&gt;, backticks, &lt;code&gt;case;&lt;/code&gt;, or legacy close/free functions are usually simple to fix and easy to detect with a global search.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Check extension-related behavior where it matters
&lt;/h3&gt;

&lt;p&gt;If your app handles HTML, images, outgoing HTTP requests, or more advanced cookie behavior, PHP 8.5 has changes worth testing directly.&lt;/p&gt;

&lt;p&gt;That is where the most practical wins are.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;PHP 8.5 is not the kind of release that tries to reinvent the language.&lt;/p&gt;

&lt;p&gt;Instead, it improves the everyday experience of writing PHP.&lt;/p&gt;

&lt;p&gt;The pipe operator improves readability. The URI extension fills a long-standing gap. The new functions remove little bits of friction. Debugging gets better. Extension updates feel grounded in real use cases. And the deprecations help nudge codebases toward cleaner patterns.&lt;/p&gt;

&lt;p&gt;That makes PHP 8.5 a very solid release.&lt;/p&gt;

&lt;p&gt;Not because it changes everything, but because it improves the places where developers spend time every day.&lt;/p&gt;

</description>
      <category>php</category>
      <category>webdev</category>
      <category>programming</category>
      <category>backend</category>
    </item>
    <item>
      <title>Learning Buffalo by Building a Small Helpdesk App in Go</title>
      <dc:creator>A0mineTV</dc:creator>
      <pubDate>Tue, 24 Mar 2026 18:27:08 +0000</pubDate>
      <link>https://forem.com/blamsa0mine/learning-buffalo-by-building-a-small-helpdesk-app-in-go-12do</link>
      <guid>https://forem.com/blamsa0mine/learning-buffalo-by-building-a-small-helpdesk-app-in-go-12do</guid>
      <description>&lt;p&gt;When people talk about Go web development, the conversation often jumps straight to minimal stacks: &lt;code&gt;net/http&lt;/code&gt;, a router, a query builder, a templating choice, and then a lot of glue code.&lt;/p&gt;

&lt;p&gt;That approach is great when you want total control.&lt;/p&gt;

&lt;p&gt;But sometimes, you want a more complete web framework experience — something that gives you a structured project layout, routing, templates, models, migrations, forms, and a development workflow that feels closer to Laravel or Rails.&lt;/p&gt;

&lt;p&gt;That is exactly why I wanted to explore &lt;strong&gt;Buffalo&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;To make it concrete, I built a small example project called &lt;strong&gt;HelpDesk Lite&lt;/strong&gt;, a simple support app with users, tickets, and comments:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub repo:&lt;/strong&gt; &lt;code&gt;https://github.com/VincentCapek/helpdesk_lite&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In this article, I want to show how Buffalo works in practice through this project, what the developer experience feels like, and what I learned along the way.&lt;/p&gt;




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

&lt;p&gt;Buffalo is interesting because it is not just "a router plus a few helpers".&lt;/p&gt;

&lt;p&gt;It gives you a full web application structure for Go:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;actions&lt;/strong&gt; for request handling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;models&lt;/strong&gt; for database entities&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pop&lt;/strong&gt; for ORM-style database work and migrations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plush&lt;/strong&gt; for server-side templates&lt;/li&gt;
&lt;li&gt;a CLI that helps you run, build, and manage the app&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are coming from frameworks like Laravel, Rails, or even Symfony, the mental model feels familiar — but you still write Go.&lt;/p&gt;

&lt;p&gt;That mix is what makes Buffalo appealing.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Example: HelpDesk Lite
&lt;/h2&gt;

&lt;p&gt;To learn Buffalo properly, I did not want a “hello world” project. I wanted a small app with enough moving parts to exercise the framework.&lt;/p&gt;

&lt;p&gt;So I used this structure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;users&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;tickets&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;comments&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;a user can create a support ticket&lt;/li&gt;
&lt;li&gt;tickets have a title, description, status, priority, and category&lt;/li&gt;
&lt;li&gt;users can add comments to a ticket&lt;/li&gt;
&lt;li&gt;there is also an admin entry point in the app&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This kind of project is perfect for learning because it touches most of the things you expect in a real web app:&lt;br&gt;
routing, CRUD, validation, relational data, templates, and tests.&lt;/p&gt;


&lt;h2&gt;
  
  
  What a Buffalo Project Feels Like
&lt;/h2&gt;

&lt;p&gt;One of the first things I liked about Buffalo is that the project layout pushes you toward a clean separation of concerns.&lt;/p&gt;

&lt;p&gt;In this app, the important folders are easy to understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;actions/&lt;/code&gt; → request handlers&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;models/&lt;/code&gt; → structs and validation&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;migrations/&lt;/code&gt; → schema changes&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;templates/&lt;/code&gt; → Plush views&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;config/&lt;/code&gt; → application setup&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cmd/app/&lt;/code&gt; → application entry point&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is a big part of the Buffalo experience:&lt;br&gt;
instead of deciding everything from scratch, you start with conventions.&lt;/p&gt;

&lt;p&gt;For small and medium-sized applications, that is a real productivity boost.&lt;/p&gt;


&lt;h2&gt;
  
  
  Routing: Very Easy to Read
&lt;/h2&gt;

&lt;p&gt;In Buffalo, routes are straightforward.&lt;/p&gt;

&lt;p&gt;For a helpdesk app, the route tree is very readable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HomeHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/tickets"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TicketsIndex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/tickets/new"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TicketsNew&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/tickets"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TicketsCreate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/tickets/{ticket_id}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TicketsShow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/tickets/{ticket_id}/edit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TicketsEdit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PUT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/tickets/{ticket_id}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TicketsUpdate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DELETE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/tickets/{ticket_id}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TicketsDestroy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/tickets/{ticket_id}/comments"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CommentsCreate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/admin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AdminDashboard&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are used to MVC frameworks, this feels natural immediately.&lt;/p&gt;

&lt;p&gt;It also makes the request flow easy to follow:&lt;br&gt;
URL → action → model/database → template.&lt;/p&gt;


&lt;h2&gt;
  
  
  Actions: The Core of the Request Flow
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;actions/&lt;/code&gt; folder is where the app logic starts to feel real.&lt;/p&gt;

&lt;p&gt;A very typical Buffalo action looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;get the database transaction from the context&lt;/li&gt;
&lt;li&gt;bind request data into a struct&lt;/li&gt;
&lt;li&gt;validate the data&lt;/li&gt;
&lt;li&gt;save it with Pop&lt;/li&gt;
&lt;li&gt;either render a template again on failure or redirect on success&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That pattern appears again and again in a Buffalo app.&lt;/p&gt;

&lt;p&gt;For example, creating a ticket or comment follows the same idea:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;c.Bind(...)&lt;/code&gt; to map form data into a struct&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Validate(...)&lt;/code&gt; or &lt;code&gt;ValidateAndCreate(...)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tx.Create(...)&lt;/code&gt; to persist&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;c.Render(...)&lt;/code&gt; for validation errors&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;c.Redirect(...)&lt;/code&gt; after success&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I like this because it keeps actions explicit.&lt;br&gt;
Nothing feels too magical.&lt;br&gt;
You can read the code top to bottom and understand the full lifecycle of the request.&lt;/p&gt;


&lt;h2&gt;
  
  
  Pop: Database Work Without Too Much Boilerplate
&lt;/h2&gt;

&lt;p&gt;Buffalo uses &lt;strong&gt;Pop&lt;/strong&gt; for database access and migrations.&lt;/p&gt;

&lt;p&gt;For a project like HelpDesk Lite, that feels like a nice middle ground:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;simpler than hand-writing everything with raw SQL&lt;/li&gt;
&lt;li&gt;less heavy than some ORMs that hide too much&lt;/li&gt;
&lt;li&gt;integrated well into the Buffalo workflow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In practice, you work with your models as Go structs, and Pop helps you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;query data&lt;/li&gt;
&lt;li&gt;create/update/delete rows&lt;/li&gt;
&lt;li&gt;validate models&lt;/li&gt;
&lt;li&gt;manage migrations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That was especially useful for ticket and comment creation because the code stays readable.&lt;/p&gt;

&lt;p&gt;A request can bind data into a struct, validate it, save it, then redirect — all in a very compact way.&lt;/p&gt;


&lt;h2&gt;
  
  
  Migrations: A Good Workflow Once You Understand the Rules
&lt;/h2&gt;

&lt;p&gt;Buffalo migrations are powered by Pop, and you can write them with &lt;strong&gt;Fizz&lt;/strong&gt; or raw SQL.&lt;/p&gt;

&lt;p&gt;For this project, the migrations created tables like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;users&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tickets&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;comments&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One thing that is important to understand early:&lt;br&gt;
&lt;strong&gt;only proper migration files are executed by Pop&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So files like these are valid:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;20260318102228_create_tickets.up.fizz&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;20260318102228_create_tickets.down.fizz&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But a standalone file like &lt;code&gt;schema.sql&lt;/code&gt; is &lt;strong&gt;not&lt;/strong&gt; treated as a migration by &lt;code&gt;soda migrate&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That was a useful lesson.&lt;/p&gt;

&lt;p&gt;Another practical point:&lt;br&gt;
once a migration is marked as applied, changing its content later does not magically re-run it.&lt;br&gt;
If you want to evolve the schema, create a &lt;strong&gt;new migration&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That is standard migration discipline, but Buffalo/Pop makes you feel it very quickly.&lt;/p&gt;


&lt;h2&gt;
  
  
  Plush Templates: Familiar, but You Need to Respect the Context
&lt;/h2&gt;

&lt;p&gt;Buffalo uses &lt;strong&gt;Plush&lt;/strong&gt; for templates.&lt;/p&gt;

&lt;p&gt;If you already know server-side template engines, Plush is easy to pick up:&lt;br&gt;
you print values, loop through collections, and render HTML with embedded logic.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;templates stay readable&lt;/li&gt;
&lt;li&gt;forms are easy to build&lt;/li&gt;
&lt;li&gt;rendering is tightly integrated with actions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What I learned the hard way:&lt;br&gt;
&lt;strong&gt;your template only sees what you explicitly put into the context&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If a template uses &lt;code&gt;ticket.Title&lt;/code&gt;, the action must provide &lt;code&gt;ticket&lt;/code&gt; with &lt;code&gt;c.Set("ticket", ticket)&lt;/code&gt; before rendering.&lt;/p&gt;

&lt;p&gt;That sounds obvious, but it is one of those details that teaches you how Buffalo thinks:&lt;br&gt;
templates are not magically connected to your action state.&lt;br&gt;
You pass the data they need, explicitly.&lt;/p&gt;

&lt;p&gt;That is actually a good thing.&lt;br&gt;
It keeps the rendering layer predictable.&lt;/p&gt;


&lt;h2&gt;
  
  
  Validation and Error Rendering
&lt;/h2&gt;

&lt;p&gt;One part I found especially educational was validation handling.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;validate the model&lt;/li&gt;
&lt;li&gt;if there are errors, put them into the context&lt;/li&gt;
&lt;li&gt;re-render the form with a &lt;code&gt;422&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;if validation passes, save and redirect&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes form handling very clear.&lt;/p&gt;

&lt;p&gt;It also reinforces a classic web application pattern:&lt;br&gt;
&lt;strong&gt;render on failure, redirect on success&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That kind of flow is where Buffalo feels very comfortable.&lt;/p&gt;


&lt;h2&gt;
  
  
  Running the App
&lt;/h2&gt;

&lt;p&gt;The basic local workflow is straightforward.&lt;/p&gt;

&lt;p&gt;Run migrations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;soda migrate &lt;span class="nt"&gt;-e&lt;/span&gt; development &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then start 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;buffalo dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And Buffalo handles the development loop.&lt;/p&gt;

&lt;p&gt;One useful detail:&lt;br&gt;
if you use SQLite, make sure your Buffalo binary actually includes SQLite support.&lt;br&gt;
I ran into that while testing the project.&lt;br&gt;
&lt;code&gt;buffalo dev&lt;/code&gt; and &lt;code&gt;soda&lt;/code&gt; are not always built the same way, so environment setup matters.&lt;/p&gt;

&lt;p&gt;That was a good reminder that frameworks improve workflow, but understanding the tooling still matters.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Like About Buffalo
&lt;/h2&gt;

&lt;p&gt;After building this helpdesk example, here is what stood out to me.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. It gives Go a more “full-stack framework” feel
&lt;/h3&gt;

&lt;p&gt;A lot of Go web stacks are intentionally minimal.&lt;br&gt;
Buffalo is more opinionated, and that is exactly its value.&lt;/p&gt;

&lt;p&gt;You get a real application structure instead of assembling everything manually.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The request flow is easy to understand
&lt;/h3&gt;

&lt;p&gt;Routes, actions, models, templates, redirects:&lt;br&gt;
everything follows a clear pattern.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Pop and migrations are practical
&lt;/h3&gt;

&lt;p&gt;For CRUD-style applications, Pop feels productive without becoming too abstract.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. The framework teaches discipline
&lt;/h3&gt;

&lt;p&gt;Buffalo makes some mistakes very visible:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;missing context variables in templates&lt;/li&gt;
&lt;li&gt;incorrect migration assumptions&lt;/li&gt;
&lt;li&gt;validation/rendering mistakes&lt;/li&gt;
&lt;li&gt;SQLite setup issues in the CLI tooling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That might sound annoying at first, but it helps you understand the framework faster.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>go</category>
      <category>programming</category>
      <category>backend</category>
    </item>
    <item>
      <title>Building a Simple Notes REST API with Go and Fiber</title>
      <dc:creator>A0mineTV</dc:creator>
      <pubDate>Mon, 16 Mar 2026 13:55:23 +0000</pubDate>
      <link>https://forem.com/blamsa0mine/building-a-simple-notes-rest-api-with-go-and-fiber-ghb</link>
      <guid>https://forem.com/blamsa0mine/building-a-simple-notes-rest-api-with-go-and-fiber-ghb</guid>
      <description>&lt;p&gt;When learning a new web framework, I like to avoid huge starter kits and focus on something small, useful, and easy to reason about.&lt;/p&gt;

&lt;p&gt;That is exactly why I built a &lt;strong&gt;notes REST API&lt;/strong&gt; with &lt;strong&gt;Go&lt;/strong&gt; and &lt;strong&gt;Fiber&lt;/strong&gt;. The goal was not to create a production-ready SaaS, but to understand the pieces that matter when starting with Fiber: routing, handlers, middleware, error handling, and a clean project structure.&lt;/p&gt;

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

&lt;p&gt;Fiber is attractive because it gives you a very approachable developer experience while still feeling fast and lightweight. It is inspired by Express, but built for Go, and it keeps the routing and middleware story simple enough that you can focus on your API design instead of fighting the framework.&lt;/p&gt;

&lt;p&gt;For this project, I wanted something that would let me practice the basics of a REST API without adding unnecessary complexity. So instead of starting with a database, authentication, migrations, and Docker, I began with an &lt;strong&gt;in-memory notes store&lt;/strong&gt; and a simple layered structure. That let me focus on the request flow first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project structure
&lt;/h2&gt;

&lt;p&gt;One thing I did not want was a single &lt;code&gt;main.go&lt;/code&gt; file with everything inside it. Even for a small API, separating concerns makes the code easier to read and evolve.&lt;/p&gt;

&lt;p&gt;This is the structure I used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fiber-notes/
├── main.go
├── handlers/
├── middleware/
├── models/
├── routes/
└── storage/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;main.go&lt;/code&gt; wires the application together, the handlers contain the HTTP logic, routes register endpoints, models define the data shape, storage handles persistence, and middleware contains reusable request logic like logging.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bootstrapping the Fiber app
&lt;/h2&gt;

&lt;p&gt;The application setup is intentionally small. I create the Fiber app, initialize the memory store, inject it into the handler, register middleware, then mount the notes routes under &lt;code&gt;/api&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;store&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewMemoryStore&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;noteHandler&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;handlers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewNoteHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;recover&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;middleware&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SimpleLogger&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"message"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Hello, World!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"status"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;"ok"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;routes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RegisterNoteRoutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;noteHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":3000"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Defining the model
&lt;/h2&gt;

&lt;p&gt;The notes API only needs one model, and that simplicity is part of the exercise.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Note&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ID&lt;/span&gt;        &lt;span class="kt"&gt;int&lt;/span&gt;       &lt;span class="s"&gt;`json:"id"`&lt;/span&gt;
    &lt;span class="n"&gt;Title&lt;/span&gt;     &lt;span class="kt"&gt;string&lt;/span&gt;    &lt;span class="s"&gt;`json:"title"`&lt;/span&gt;
    &lt;span class="n"&gt;Content&lt;/span&gt;   &lt;span class="kt"&gt;string&lt;/span&gt;    &lt;span class="s"&gt;`json:"content"`&lt;/span&gt;
    &lt;span class="n"&gt;CreatedAt&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt; &lt;span class="s"&gt;`json:"created_at"`&lt;/span&gt;
    &lt;span class="n"&gt;UpdatedAt&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt; &lt;span class="s"&gt;`json:"updated_at"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives the API enough structure to feel real without becoming noisy. The current repo uses &lt;code&gt;Title&lt;/code&gt;, &lt;code&gt;Content&lt;/code&gt;, and timestamps, which already makes the responses clearer than a minimal &lt;code&gt;{ id, text }&lt;/code&gt; example.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding an in-memory store
&lt;/h2&gt;

&lt;p&gt;To keep the project focused on Fiber itself, I used an in-memory store rather than a database. The store keeps notes in a &lt;code&gt;map[int]models.Note&lt;/code&gt;, tracks the next ID, and uses &lt;code&gt;sync.RWMutex&lt;/code&gt; so reads and writes stay safe. It exposes methods like &lt;code&gt;List&lt;/code&gt;, &lt;code&gt;GetById&lt;/code&gt;, &lt;code&gt;Create&lt;/code&gt;, &lt;code&gt;Update&lt;/code&gt;, and &lt;code&gt;Delete&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That choice was useful for two reasons.&lt;/p&gt;

&lt;p&gt;First, it made the project easier to understand. Second, it made the handler layer cleaner because the HTTP code does not need to care about how notes are stored. It only calls the store interface.&lt;/p&gt;

&lt;p&gt;Here is the kind of constructor I used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewMemoryStore&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;MemoryStore&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;MemoryStore&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;notes&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;nextId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&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;And here is the interface the handlers depend on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;NoteStore&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Note&lt;/span&gt;
    &lt;span class="n"&gt;GetById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Note&lt;/span&gt;
    &lt;span class="n"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That separation makes it easy to replace the memory store later with SQLite or PostgreSQL without rewriting the handlers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Registering routes
&lt;/h2&gt;

&lt;p&gt;Once the API group is created, the routes become straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;RegisterNoteRoutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;noteHandler&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;handlers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NoteHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;notes&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/notes"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;notes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;noteHandler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListNotes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;notes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/:id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;noteHandler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetByID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;notes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;noteHandler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;notes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/:id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;noteHandler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;notes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/:id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;noteHandler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Delete&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;This gives a clean REST shape:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GET /api/notes/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GET /api/notes/:id&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;POST /api/notes/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PUT /api/notes/:id&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DELETE /api/notes/:id&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Writing the handlers
&lt;/h2&gt;

&lt;p&gt;The handlers are where Fiber starts to feel pleasant. Each method receives a &lt;code&gt;fiber.Ctx&lt;/code&gt;, reads route params or request bodies, validates input, calls the store, and returns JSON.&lt;/p&gt;

&lt;p&gt;Here is the create handler pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;NoteHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HasBody&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="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusBadRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"request body is required"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="n"&gt;CreateNoteInput&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bind&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusBadRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"invalid input"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TrimSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TrimSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusBadRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"title is required"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;note&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCreated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"message"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"note created successfully"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"data"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="n"&gt;note&lt;/span&gt;&lt;span class="p"&gt;,&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;There are a few things I like here.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;c.Bind().Body(&amp;amp;input)&lt;/code&gt; keeps request parsing readable. &lt;code&gt;strings.TrimSpace&lt;/code&gt; gives quick input cleanup. And &lt;code&gt;fiber.NewError(...)&lt;/code&gt; makes invalid states explicit.&lt;/p&gt;

&lt;p&gt;For route parameters, I convert the &lt;code&gt;id&lt;/code&gt; with &lt;code&gt;strconv.Atoi(c.Params("id"))&lt;/code&gt;. If the value is invalid, the handler returns a &lt;code&gt;400 Bad Request&lt;/code&gt;. If the note is not found, it returns a &lt;code&gt;404 Not Found&lt;/code&gt;. That small distinction already makes the API feel much more correct.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding middleware
&lt;/h2&gt;

&lt;p&gt;I also wanted the project to include middleware from day one, because middleware is one of the best ways to understand how requests flow through Fiber.&lt;/p&gt;

&lt;p&gt;The repo includes a simple custom logger:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;SimpleLogger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handler&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;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%s %s %d %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Since&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&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;This logs the HTTP method, path, status code, and execution time. It is small, but it already makes the server feel more like a real API. Alongside that, I added &lt;code&gt;recover.New()&lt;/code&gt; so panics are intercepted and forwarded through Fiber’s error pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this project was useful
&lt;/h2&gt;

&lt;p&gt;The biggest value of this project was not the notes domain itself. It was the repetition of the same backend fundamentals in a small space:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;structure the app&lt;/li&gt;
&lt;li&gt;register routes&lt;/li&gt;
&lt;li&gt;parse JSON&lt;/li&gt;
&lt;li&gt;validate input&lt;/li&gt;
&lt;li&gt;return proper status codes&lt;/li&gt;
&lt;li&gt;isolate storage from HTTP logic&lt;/li&gt;
&lt;li&gt;add middleware early&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because the project stays small, each layer remains easy to inspect. And because the app still performs real CRUD operations, it feels much closer to real backend work than a “Hello World” example.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;p&gt;This version uses in-memory storage on purpose, which means data disappears when the server restarts. That is fine for learning, but the natural next steps are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;add SQLite or PostgreSQL&lt;/li&gt;
&lt;li&gt;write unit tests for the store and handlers&lt;/li&gt;
&lt;li&gt;add request validation&lt;/li&gt;
&lt;li&gt;introduce JWT authentication&lt;/li&gt;
&lt;li&gt;add pagination and filtering&lt;/li&gt;
&lt;li&gt;dockerize the app&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That progression turns a simple learning project into a stronger backend foundation without changing the core structure too much.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Fiber made this project enjoyable because it stayed out of the way. I could focus on HTTP concepts, project structure, and clean handler logic instead of spending too much time wiring infrastructure.&lt;/p&gt;

&lt;p&gt;For anyone learning Go web development, building a small CRUD API like this is a great way to move from toy examples to something more realistic. And starting with notes is enough to learn the framework without getting buried in domain complexity.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>go</category>
      <category>ai</category>
      <category>programming</category>
    </item>
    <item>
      <title>Building a Clean REST API in Go — No Frameworks, No Fuss</title>
      <dc:creator>A0mineTV</dc:creator>
      <pubDate>Sat, 14 Mar 2026 15:36:12 +0000</pubDate>
      <link>https://forem.com/blamsa0mine/building-a-clean-rest-api-in-go-no-frameworks-no-fuss-2dg1</link>
      <guid>https://forem.com/blamsa0mine/building-a-clean-rest-api-in-go-no-frameworks-no-fuss-2dg1</guid>
      <description>&lt;p&gt;Go's standard library is powerful enough to build a production-ready REST API without reaching for a framework. In this post, we'll walk through a small but well-structured &lt;strong&gt;Notes API&lt;/strong&gt; that covers all CRUD operations — and the architectural decisions behind it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we're building
&lt;/h2&gt;

&lt;p&gt;A simple API to manage notes, with five routes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;POST&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/notes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create a note&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/notes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;List all notes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/notes/{id}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Get a note by ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PUT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/notes/{id}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Update a note&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DELETE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/notes/{id}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Delete a note&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Project structure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;notes-api/
├── cmd/api/          # Entry point
├── internal/
│   ├── httpapi/      # HTTP handlers
│   ├── note/         # Model, service, storage interface
│   └── storage/      # In-memory implementation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three distinct layers: &lt;strong&gt;HTTP&lt;/strong&gt;, &lt;strong&gt;business logic&lt;/strong&gt;, and &lt;strong&gt;storage&lt;/strong&gt;. Each one&lt;br&gt;
knows nothing about the others except through interfaces.&lt;/p&gt;


&lt;h2&gt;
  
  
  The model
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Note&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ID&lt;/span&gt;        &lt;span class="kt"&gt;int64&lt;/span&gt;     &lt;span class="s"&gt;`json:"id"`&lt;/span&gt;
    &lt;span class="n"&gt;Title&lt;/span&gt;     &lt;span class="kt"&gt;string&lt;/span&gt;    &lt;span class="s"&gt;`json:"title"`&lt;/span&gt;
    &lt;span class="n"&gt;Content&lt;/span&gt;   &lt;span class="kt"&gt;string&lt;/span&gt;    &lt;span class="s"&gt;`json:"content"`&lt;/span&gt;
    &lt;span class="n"&gt;CreatedAt&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt; &lt;span class="s"&gt;`json:"created_at"`&lt;/span&gt;
    &lt;span class="n"&gt;UpdatedAt&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt; &lt;span class="s"&gt;`json:"updated_at"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Simple and flat. Timestamps are managed by the service, not by the caller.&lt;/p&gt;


&lt;h2&gt;
  
  
  The storage interface
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Store&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="n"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="n"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;GetByID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Defining a &lt;code&gt;Store&lt;/code&gt; interface in the &lt;code&gt;note&lt;/code&gt; package (not in &lt;code&gt;storage&lt;/code&gt;) is a key decision: the business logic owns the contract, and the storage implementation satisfies it. This makes swapping backends (PostgreSQL, Redis, etc.) trivial later.&lt;/p&gt;


&lt;h2&gt;
  
  
  The service layer
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ErrInvalidTitle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"invalid title"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ErrNotFound&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"not found"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="n"&gt;ErrInvalidTitle&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CreatedAt&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;UpdatedAt&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;now&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="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&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;The service is where business rules live. Here it validates that the title is non-empty and stamps the timestamps before delegating to the store. Typed sentinel errors (&lt;code&gt;ErrInvalidTitle&lt;/code&gt;, &lt;code&gt;ErrNotFound&lt;/code&gt;) let the HTTP layer map them to the right status codes without leaking implementation details.&lt;/p&gt;


&lt;h2&gt;
  
  
  The in-memory storage
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;MemoryStorage&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;mu&lt;/span&gt;     &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RWMutex&lt;/span&gt;
    &lt;span class="n"&gt;notes&lt;/span&gt;  &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;note&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Note&lt;/span&gt;
    &lt;span class="n"&gt;nextID&lt;/span&gt; &lt;span class="kt"&gt;int64&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A slice protected by a &lt;code&gt;sync.RWMutex&lt;/code&gt;. Read operations (&lt;code&gt;List&lt;/code&gt;, &lt;code&gt;GetByID&lt;/code&gt;) use &lt;code&gt;RLock&lt;/code&gt; to allow concurrent reads; write operations use &lt;code&gt;Lock&lt;/code&gt; for exclusive access. Note that the returned slice from &lt;code&gt;List&lt;/code&gt; is a copy — so callers can't accidentally mutate the internal state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;MemoryStorage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="n"&gt;note&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RLock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RUnlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="n"&gt;note&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notes&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nb"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The HTTP handler
&lt;/h2&gt;

&lt;p&gt;Routing is done with the standard &lt;code&gt;http.ServeMux&lt;/code&gt;. Two handler functions cover&lt;br&gt;
all five routes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mux&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServeMux&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/notes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handleNotes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/notes/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handleNoteByID&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;/notes&lt;/code&gt; handles &lt;code&gt;GET&lt;/code&gt; (list) and &lt;code&gt;POST&lt;/code&gt; (create). &lt;code&gt;/notes/&lt;/code&gt; catches everything&lt;br&gt;
with an ID suffix and dispatches on the method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;handleNoteByID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;parseIDFromPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Invalid note ID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusBadRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MethodGet&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getNote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MethodPut&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;updateNote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MethodDelete&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deleteNote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Method not allowed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusMethodNotAllowed&lt;/span&gt;&lt;span class="p"&gt;)&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;Service errors are translated to HTTP status codes in one place:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;handleServiceError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;note&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrInvalidTitle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Invalid title"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusBadRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;note&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrNotFound&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Note not found"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusNotFound&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Internal server error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusInternalServerError&lt;/span&gt;&lt;span class="p"&gt;)&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;h2&gt;
  
  
  Wiring it all together
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;store&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewMemoryStore&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;note&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;httpapi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;mux&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewServeMux&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Server is running on port 8080"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListenAndServe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&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;Three lines of wiring. Each dependency is injected explicitly — no globals, no service locators.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try it out
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go run ./cmd/api

&lt;span class="c"&gt;# Create&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8080/notes &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"title": "Hello", "content": "World"}'&lt;/span&gt;

&lt;span class="c"&gt;# List&lt;/span&gt;
curl http://localhost:8080/notes

&lt;span class="c"&gt;# Get&lt;/span&gt;
curl http://localhost:8080/notes/1

&lt;span class="c"&gt;# Update&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; PUT http://localhost:8080/notes/1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"title": "Updated", "content": "New content"}'&lt;/span&gt;

&lt;span class="c"&gt;# Delete&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; DELETE http://localhost:8080/notes/1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No framework needed&lt;/strong&gt; for a simple CRUD API — &lt;code&gt;net/http&lt;/code&gt; is enough.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interface in the consumer package&lt;/strong&gt; (&lt;code&gt;note.Store&lt;/code&gt; lives in &lt;code&gt;note&lt;/code&gt;, not&lt;code&gt;storage&lt;/code&gt;) keeps dependencies pointing inward.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Typed sentinel errors&lt;/strong&gt; decouple business logic from HTTP concerns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;sync.RWMutex&lt;/code&gt;&lt;/strong&gt; with defensive copies keeps the in-memory store safe under concurrency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependency injection via constructors&lt;/strong&gt; makes the whole thing trivially testable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The next natural step would be swapping &lt;code&gt;MemoryStorage&lt;/code&gt; for a real database (PostgreSQL with &lt;code&gt;pgx&lt;/code&gt;, for example) — and because of the &lt;code&gt;Store&lt;/code&gt; interface, &lt;code&gt;main.go&lt;/code&gt; is the only file that changes.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>go</category>
      <category>api</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Building a Deterministic Password CLI in Go with Argon2id</title>
      <dc:creator>A0mineTV</dc:creator>
      <pubDate>Thu, 12 Mar 2026 14:43:11 +0000</pubDate>
      <link>https://forem.com/blamsa0mine/building-a-deterministic-password-cli-in-go-with-argon2id-59mp</link>
      <guid>https://forem.com/blamsa0mine/building-a-deterministic-password-cli-in-go-with-argon2id-59mp</guid>
      <description>&lt;p&gt;Most password generators focus on randomness.&lt;/p&gt;

&lt;p&gt;GitHub repo: &lt;a href="https://github.com/VincentCapek/argon2-password-cli" rel="noopener noreferrer"&gt;VincentCapek/argon2-password-cli&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This project takes a different route: it derives the &lt;strong&gt;same password every time&lt;/strong&gt; from a &lt;strong&gt;master password&lt;/strong&gt; and a &lt;strong&gt;label&lt;/strong&gt; such as &lt;code&gt;github&lt;/code&gt;, &lt;code&gt;gmail&lt;/code&gt;, or &lt;code&gt;bank&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That makes it a great little Go project for learning a few useful concepts at once:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;building a CLI with &lt;code&gt;flag&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;structuring a small Go codebase&lt;/li&gt;
&lt;li&gt;using Argon2id&lt;/li&gt;
&lt;li&gt;understanding deterministic derivation versus random generation&lt;/li&gt;
&lt;li&gt;shaping output with validation and character groups&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The idea
&lt;/h2&gt;

&lt;p&gt;Instead of generating a brand-new password on every run, the CLI derives a reproducible one from two inputs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a master password&lt;/li&gt;
&lt;li&gt;a label&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go run &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-master&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"MyUltraSecret"&lt;/span&gt; &lt;span class="nt"&gt;-label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"github"&lt;/span&gt; &lt;span class="nt"&gt;-length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Will always return the same password as long as the inputs and options stay the same.&lt;/p&gt;

&lt;p&gt;Change the label to &lt;code&gt;gmail&lt;/code&gt;, and the output changes too.&lt;/p&gt;

&lt;p&gt;That gives you a simple mental model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;same master + same label = same password&lt;/li&gt;
&lt;li&gt;same master + different label = different password&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why this is interesting
&lt;/h2&gt;

&lt;p&gt;When you first build a password generator, the obvious route is to use &lt;code&gt;crypto/rand&lt;/code&gt; and produce something fully random.&lt;/p&gt;

&lt;p&gt;That is still the right approach for many use cases.&lt;/p&gt;

&lt;p&gt;But a deterministic password CLI teaches a different lesson: you are not generating randomness for storage or one-time usage, you are &lt;strong&gt;deriving a stable result from a secret and a context&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That is exactly why this kind of mini project is interesting from a learning perspective.&lt;/p&gt;

&lt;p&gt;It pushes you to think about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what a salt is&lt;/li&gt;
&lt;li&gt;why context matters&lt;/li&gt;
&lt;li&gt;how to separate CLI parsing from generation logic&lt;/li&gt;
&lt;li&gt;how to build a reproducible byte stream&lt;/li&gt;
&lt;li&gt;how to shape the final password so it remains usable&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Project structure
&lt;/h2&gt;

&lt;p&gt;The repository is intentionally small:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;main.go&lt;/code&gt; handles the CLI flags and builds the config&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;generator.go&lt;/code&gt; contains the derivation and password generation logic&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;go.mod&lt;/code&gt; defines the module and dependencies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That small size is actually one of the project’s strengths. You can read the whole codebase in one sitting and still touch several important ideas.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the generator works
&lt;/h2&gt;

&lt;p&gt;The generation pipeline is clean and easy to follow.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Parse CLI options
&lt;/h3&gt;

&lt;p&gt;The CLI accepts a master password, a label, a desired length, and options for lowercase, uppercase, digits, and symbols.&lt;/p&gt;

&lt;p&gt;That already makes the project practical while staying beginner-friendly.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Normalize the label
&lt;/h3&gt;

&lt;p&gt;Before using the label, the code normalizes it by trimming spaces and converting it to lowercase.&lt;/p&gt;

&lt;p&gt;That means values like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;github&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GitHub&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GITHUB&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;all resolve to the same normalized label.&lt;/p&gt;

&lt;p&gt;This is a small but important detail, because it avoids accidental password drift caused by casing or whitespace differences.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Turn the label into a salt
&lt;/h3&gt;

&lt;p&gt;The normalized label is hashed with SHA-256, and the first 16 bytes are used as the salt.&lt;/p&gt;

&lt;p&gt;This is a neat design choice for a deterministic CLI:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the label remains human-friendly&lt;/li&gt;
&lt;li&gt;the salt is derived in a consistent way&lt;/li&gt;
&lt;li&gt;changing the label changes the salt, which changes the result&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Derive a seed with Argon2id
&lt;/h3&gt;

&lt;p&gt;The project then uses &lt;code&gt;argon2.IDKey(...)&lt;/code&gt; to derive a 32-byte seed from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the master password&lt;/li&gt;
&lt;li&gt;the derived salt&lt;/li&gt;
&lt;li&gt;the cost parameters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the cryptographic core of the project.&lt;/p&gt;

&lt;p&gt;Instead of directly turning the label into a password, the code first asks Argon2id to produce a strong derived key. That key becomes the seed for the next stage.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Build a deterministic byte stream
&lt;/h3&gt;

&lt;p&gt;From there, the project uses a custom &lt;code&gt;byteStream&lt;/code&gt;.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;combine the seed with a counter&lt;/li&gt;
&lt;li&gt;hash that combination with SHA-256&lt;/li&gt;
&lt;li&gt;use the resulting bytes as a deterministic stream&lt;/li&gt;
&lt;li&gt;increment the counter when more bytes are needed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a nice educational touch, because it makes the generation process easier to reason about than hiding everything behind a single opaque helper.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Build the password from character groups
&lt;/h3&gt;

&lt;p&gt;The code defines separate character sets for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;lowercase letters&lt;/li&gt;
&lt;li&gt;uppercase letters&lt;/li&gt;
&lt;li&gt;digits&lt;/li&gt;
&lt;li&gt;symbols&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It first guarantees at least one character from each enabled group.&lt;/p&gt;

&lt;p&gt;Then it fills the rest of the password from the merged character set.&lt;/p&gt;

&lt;p&gt;Finally, it performs a deterministic shuffle so the password does not always start with the same kind of characters.&lt;/p&gt;

&lt;p&gt;That last part matters a lot. Without the shuffle, the output shape would be predictable even if the actual characters were not.&lt;/p&gt;

&lt;h2&gt;
  
  
  A small project, but a good set of lessons
&lt;/h2&gt;

&lt;p&gt;What I like about this repository is that it stays small without being trivial.&lt;/p&gt;

&lt;p&gt;It introduces several ideas that show up again and again in real projects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;validating input early&lt;/li&gt;
&lt;li&gt;separating config from logic&lt;/li&gt;
&lt;li&gt;composing small helper functions&lt;/li&gt;
&lt;li&gt;avoiding duplicated logic for character groups&lt;/li&gt;
&lt;li&gt;keeping the CLI thin and the generator focused&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words, this is not just a “security toy.” It is also a solid exercise in writing clean Go.&lt;/p&gt;

&lt;h2&gt;
  
  
  Random generation vs deterministic derivation
&lt;/h2&gt;

&lt;p&gt;This is probably the most important distinction in the whole project.&lt;/p&gt;

&lt;p&gt;A traditional password generator says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;give me a fresh random password right now&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This CLI says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;derive the same password again from a secret and a label&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That difference changes the whole architecture.&lt;/p&gt;

&lt;p&gt;If you want true randomness, &lt;code&gt;crypto/rand&lt;/code&gt; is the right tool.&lt;/p&gt;

&lt;p&gt;If you want reproducibility tied to a context, a derivation approach like this becomes much more interesting.&lt;/p&gt;

&lt;p&gt;That distinction is one of the reasons this project works so well as a learning exercise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example commands
&lt;/h2&gt;

&lt;p&gt;Generate a 20-character password for GitHub:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go run &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-master&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"MyUltraSecret"&lt;/span&gt; &lt;span class="nt"&gt;-label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"github"&lt;/span&gt; &lt;span class="nt"&gt;-length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go run &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-master&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"MyUltraSecret"&lt;/span&gt; &lt;span class="nt"&gt;-label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"bank"&lt;/span&gt; &lt;span class="nt"&gt;-length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;16 &lt;span class="nt"&gt;-symbols&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Increase the Argon2 cost parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go run &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-master&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"MyUltraSecret"&lt;/span&gt; &lt;span class="nt"&gt;-label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"github"&lt;/span&gt; &lt;span class="nt"&gt;-time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4 &lt;span class="nt"&gt;-memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What I would improve next
&lt;/h2&gt;

&lt;p&gt;If I were extending this project, I would probably add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;hidden master-password input instead of passing it directly on the command line&lt;/li&gt;
&lt;li&gt;unit tests for deterministic output and validation rules&lt;/li&gt;
&lt;li&gt;profiles for site-specific password policies&lt;/li&gt;
&lt;li&gt;clipboard support&lt;/li&gt;
&lt;li&gt;a &lt;code&gt;cmd/&lt;/code&gt; layout if the project grows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of that is required for the project to be useful today, but each item would be a natural next step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;I like projects like this because they stay focused.&lt;/p&gt;

&lt;p&gt;You do not need a framework, a database, or a large architecture to learn something meaningful. Sometimes a compact CLI is enough to explore:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go flags&lt;/li&gt;
&lt;li&gt;data validation&lt;/li&gt;
&lt;li&gt;helper types&lt;/li&gt;
&lt;li&gt;deterministic generation&lt;/li&gt;
&lt;li&gt;Argon2id integration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want a small but instructive Go project, building a deterministic password CLI like this is a very good exercise.&lt;/p&gt;

&lt;p&gt;It teaches both &lt;strong&gt;language fundamentals&lt;/strong&gt; and &lt;strong&gt;design trade-offs&lt;/strong&gt; in a way that stays concrete from start to finish.&lt;/p&gt;

</description>
      <category>go</category>
      <category>security</category>
      <category>cli</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Building SearchForge: a lightweight search interface with Go and Vue</title>
      <dc:creator>A0mineTV</dc:creator>
      <pubDate>Wed, 11 Mar 2026 13:01:44 +0000</pubDate>
      <link>https://forem.com/blamsa0mine/building-searchforge-a-lightweight-search-interface-with-go-and-vue-33di</link>
      <guid>https://forem.com/blamsa0mine/building-searchforge-a-lightweight-search-interface-with-go-and-vue-33di</guid>
      <description>&lt;p&gt;I like building products that are small, focused, and easy to reason about.&lt;/p&gt;

&lt;p&gt;That is exactly why I started &lt;strong&gt;SearchForge&lt;/strong&gt;: a local search application with a Go backend and a Vue 3 + TypeScript frontend, designed to feel like a modern SERP while staying simple enough to evolve quickly.&lt;/p&gt;

&lt;p&gt;The project is available on GitHub here: &lt;strong&gt;&lt;a href="https://github.com/VincentCapek/SearchForge" rel="noopener noreferrer"&gt;VincentCapek/SearchForge&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The goal was not to recreate a full search engine from day one.&lt;/p&gt;

&lt;p&gt;The goal was to build a clean foundation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a backend that can fetch and normalize results,&lt;/li&gt;
&lt;li&gt;a frontend that can render them in a familiar way,&lt;/li&gt;
&lt;li&gt;and an architecture flexible enough to support multiple search providers later.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why I built it
&lt;/h2&gt;

&lt;p&gt;I wanted a project that sat at the intersection of backend structure, frontend UX, and search-oriented interfaces.&lt;/p&gt;

&lt;p&gt;Search is a great playground for that because even a “simple” search page forces you to think about several layers at once:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how results are fetched,&lt;/li&gt;
&lt;li&gt;how they are normalized,&lt;/li&gt;
&lt;li&gt;how errors are handled,&lt;/li&gt;
&lt;li&gt;how loading states feel,&lt;/li&gt;
&lt;li&gt;and how the UI stays readable when the data changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of starting with a huge stack, I deliberately chose a lightweight setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Go&lt;/strong&gt; for the backend,&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vue 3 + TypeScript&lt;/strong&gt; for the frontend,&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vite&lt;/strong&gt; for fast iteration,&lt;/li&gt;
&lt;li&gt;and &lt;strong&gt;UnoCSS&lt;/strong&gt; for a design system that stays close to the code.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;From the beginning, I wanted the codebase to be split by responsibility rather than mixed together in a single app layer.&lt;/p&gt;

&lt;p&gt;The project is organized around a few simple parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;search/&lt;/code&gt; contains the search fetching and parsing logic&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;server/&lt;/code&gt; contains the HTTP server and route registration&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;server/api/&lt;/code&gt; contains the API handlers and JSON error helpers&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;assets/&lt;/code&gt; contains the frontend application&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;utils/&lt;/code&gt; is where shared helpers live&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That separation matters more than it seems.&lt;/p&gt;

&lt;p&gt;It means I can improve parsing logic without touching the UI, and I can redesign the frontend without rewriting the backend contract.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Go for the backend
&lt;/h2&gt;

&lt;p&gt;For this kind of project, Go felt like a natural fit.&lt;/p&gt;

&lt;p&gt;I wanted a backend that was:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fast to run locally,&lt;/li&gt;
&lt;li&gt;easy to read,&lt;/li&gt;
&lt;li&gt;simple to deploy later,&lt;/li&gt;
&lt;li&gt;and not dependent on a heavy framework just to expose a few endpoints.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I used the standard library HTTP server and built a small router with dedicated handlers.&lt;/p&gt;

&lt;p&gt;That gave me a backend that stays explicit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a health endpoint,&lt;/li&gt;
&lt;li&gt;provider-specific search endpoints,&lt;/li&gt;
&lt;li&gt;and a small JSON layer for success and error responses.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One detail I particularly like is the generic handler factory.&lt;br&gt;&lt;br&gt;
Instead of repeating the same validation and response logic for every provider, I used a generic &lt;code&gt;makeSearchHandler&lt;/code&gt; function that wraps a search function and turns it into an HTTP handler.&lt;/p&gt;

&lt;p&gt;That keeps the API layer small while making it easy to plug in new providers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fetching and parsing search results
&lt;/h2&gt;

&lt;p&gt;The core of the project lives in the search layer.&lt;/p&gt;

&lt;p&gt;Right now, the backend already includes logic for multiple providers, including &lt;strong&gt;DuckDuckGo&lt;/strong&gt; and &lt;strong&gt;Brave&lt;/strong&gt;. The idea is straightforward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;build the search URL,&lt;/li&gt;
&lt;li&gt;fetch the HTML document,&lt;/li&gt;
&lt;li&gt;parse the relevant nodes,&lt;/li&gt;
&lt;li&gt;normalize the output,&lt;/li&gt;
&lt;li&gt;return a consistent JSON response to the frontend.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To do that, I used &lt;code&gt;goquery&lt;/code&gt;, which gives me a jQuery-like API for HTML parsing in Go.&lt;/p&gt;

&lt;p&gt;For DuckDuckGo, I parse result blocks, ignore ads, extract the title, URL, domain, and snippet, then normalize the URL before returning it.&lt;/p&gt;

&lt;p&gt;For Brave, I follow a similar flow, but with selectors adapted to Brave’s markup and a few extra fields such as site metadata.&lt;/p&gt;

&lt;p&gt;This is one of the most interesting parts of the project because scraping search results is never just “grab some HTML and done.”&lt;br&gt;&lt;br&gt;
You have to think about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;noisy markup,&lt;/li&gt;
&lt;li&gt;invalid links,&lt;/li&gt;
&lt;li&gt;duplicate results,&lt;/li&gt;
&lt;li&gt;result normalization,&lt;/li&gt;
&lt;li&gt;and how brittle selectors can become over time.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Adding a small relevance filter
&lt;/h2&gt;

&lt;p&gt;I also added a lightweight blocklist layer.&lt;/p&gt;

&lt;p&gt;This lets me exclude domains that are not useful for the kind of result quality I want in the interface. It is a tiny feature, but it changes the feel of the output immediately.&lt;/p&gt;

&lt;p&gt;I like this kind of pragmatic layer because it shows that ranking and filtering are not always giant machine learning problems.&lt;br&gt;&lt;br&gt;
Sometimes, a simple handcrafted rule already makes the product feel better.&lt;/p&gt;

&lt;h2&gt;
  
  
  Designing the frontend
&lt;/h2&gt;

&lt;p&gt;On the frontend side, I wanted the UI to stay clean and component-driven.&lt;/p&gt;

&lt;p&gt;The app is built with &lt;strong&gt;Vue 3&lt;/strong&gt;, &lt;strong&gt;TypeScript&lt;/strong&gt;, and &lt;strong&gt;Vite&lt;/strong&gt;, and split into small pieces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a search bar,&lt;/li&gt;
&lt;li&gt;a result list,&lt;/li&gt;
&lt;li&gt;individual result cards,&lt;/li&gt;
&lt;li&gt;and a composable dedicated to the search flow.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Instead of scattering state across the app, I grouped the core search state into a single place:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the query,&lt;/li&gt;
&lt;li&gt;loading,&lt;/li&gt;
&lt;li&gt;error,&lt;/li&gt;
&lt;li&gt;results,&lt;/li&gt;
&lt;li&gt;and whether the user has already searched.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That makes the UI easy to reason about because each visual state maps to a clear application state:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;initial state,&lt;/li&gt;
&lt;li&gt;loading state,&lt;/li&gt;
&lt;li&gt;empty state,&lt;/li&gt;
&lt;li&gt;error state,&lt;/li&gt;
&lt;li&gt;and result state.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the kind of pattern I like in frontend projects: not complicated, just predictable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I used UnoCSS
&lt;/h2&gt;

&lt;p&gt;I chose &lt;strong&gt;UnoCSS&lt;/strong&gt; because I wanted utility-first styling without turning the code into an unreadable wall of classes.&lt;/p&gt;

&lt;p&gt;The project defines shortcuts and theme tokens for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;layout,&lt;/li&gt;
&lt;li&gt;search fields,&lt;/li&gt;
&lt;li&gt;buttons,&lt;/li&gt;
&lt;li&gt;result cards,&lt;/li&gt;
&lt;li&gt;feedback panels,&lt;/li&gt;
&lt;li&gt;and typography.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That means I can keep a consistent visual language while still moving fast.&lt;/p&gt;

&lt;p&gt;I also like that the design system already reflects the kind of interface I wanted: soft borders, muted text, clean spacing, and a search-first layout that feels familiar without being a copy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping frontend and backend decoupled in development
&lt;/h2&gt;

&lt;p&gt;One practical choice that helped a lot was using &lt;strong&gt;Vite’s proxy&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The frontend runs on its own dev server, while API and health requests are forwarded to the Go backend. That keeps the local workflow smooth:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;run Go on port 8080,&lt;/li&gt;
&lt;li&gt;run Vite on port 5173,&lt;/li&gt;
&lt;li&gt;and let the frontend talk to the API without CORS headaches.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is a small detail, but it makes iteration much faster.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I like about this project
&lt;/h2&gt;

&lt;p&gt;What I like most about SearchForge is that it already has the right shape.&lt;/p&gt;

&lt;p&gt;It is not trying to do everything.&lt;br&gt;&lt;br&gt;
It is trying to do the important things clearly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;separate concerns,&lt;/li&gt;
&lt;li&gt;normalize data at the backend layer,&lt;/li&gt;
&lt;li&gt;keep the frontend simple,&lt;/li&gt;
&lt;li&gt;and leave enough room for future expansion.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are even traces of its earlier naming and evolution in the codebase, which I honestly like. It reminds me that good projects are rarely born “finished” — they become clearer as you build them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would improve next
&lt;/h2&gt;

&lt;p&gt;This project is already a strong base, but there are a lot of directions I want to explore next.&lt;/p&gt;

&lt;p&gt;A few obvious ones are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;adding more providers,&lt;/li&gt;
&lt;li&gt;introducing filters and engine selection,&lt;/li&gt;
&lt;li&gt;improving normalization across providers,&lt;/li&gt;
&lt;li&gt;storing search history,&lt;/li&gt;
&lt;li&gt;serving the frontend directly from Go in production,&lt;/li&gt;
&lt;li&gt;and refining the SERP UI further.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also want to keep improving the contract between backend and frontend so each provider can plug into the same interface cleanly.&lt;/p&gt;

&lt;p&gt;That is where this kind of project becomes really fun: once the foundation is stable, every new feature has a clear place to live.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;SearchForge is the kind of project I enjoy building the most: small enough to move fast, but structured enough to grow into something more serious.&lt;/p&gt;

&lt;p&gt;It gave me a chance to work on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;backend API design,&lt;/li&gt;
&lt;li&gt;HTML parsing in Go,&lt;/li&gt;
&lt;li&gt;provider normalization,&lt;/li&gt;
&lt;li&gt;frontend state management,&lt;/li&gt;
&lt;li&gt;and design system thinking inside a Vue app.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most importantly, it helped me build a product in layers instead of building everything at once.&lt;/p&gt;

&lt;p&gt;That is usually how I like to work:&lt;br&gt;
start with a clear backbone, make each part understandable, and leave room for the project to become more ambitious over time.&lt;/p&gt;

&lt;p&gt;If you are interested in lightweight full-stack projects, search interfaces, or pragmatic architecture, this kind of setup is a lot of fun to build.&lt;/p&gt;

&lt;p&gt;You can explore the codebase here: &lt;strong&gt;&lt;a href="https://github.com/VincentCapek/SearchForge" rel="noopener noreferrer"&gt;github.com/VincentCapek/SearchForge&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>go</category>
      <category>vue</category>
      <category>programming</category>
    </item>
    <item>
      <title>Why ChatGPT Codex Became My Go-To Partner for Backend Business Logic</title>
      <dc:creator>A0mineTV</dc:creator>
      <pubDate>Tue, 03 Mar 2026 16:35:22 +0000</pubDate>
      <link>https://forem.com/blamsa0mine/why-chatgpt-codex-became-my-go-to-partner-for-backend-business-logic-2kla</link>
      <guid>https://forem.com/blamsa0mine/why-chatgpt-codex-became-my-go-to-partner-for-backend-business-logic-2kla</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; I don’t use AI to "write CRUD faster." I use it where backend work is most expensive: &lt;strong&gt;business logic&lt;/strong&gt;, &lt;strong&gt;data invariants&lt;/strong&gt;, &lt;strong&gt;state transitions&lt;/strong&gt;, &lt;strong&gt;idempotency&lt;/strong&gt;, and &lt;strong&gt;regression-proof refactors&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Silent Danger of Backend Bugs
&lt;/h2&gt;

&lt;p&gt;On the backend, speed is meaningless if you ship the wrong behavior. Unlike a UI bug, which is usually visible, a backend bug can be &lt;strong&gt;silent and devastating&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  The wrong status is stored in the database.&lt;/li&gt;
&lt;li&gt;  A customer is charged the wrong amount.&lt;/li&gt;
&lt;li&gt;  A retry logic creates duplicate records.&lt;/li&gt;
&lt;li&gt;  A permission check is slightly too permissive.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These bugs generate support tickets and long-term distrust in the system. That’s why using an AI partner (like ChatGPT or Codex) is more about &lt;strong&gt;observability and precision&lt;/strong&gt; than just typing code.&lt;/p&gt;

&lt;p&gt;That’s why I started using &lt;strong&gt;ChatGPT Codex&lt;/strong&gt; as a &lt;em&gt;backend-focused pair programmer&lt;/em&gt;—not as a snippet generator.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Makes Business Logic Hard ?
&lt;/h2&gt;

&lt;p&gt;Business logic is rarely a single &lt;code&gt;if&lt;/code&gt; statement. It is a complex system of rules, priority between those rules, and &lt;strong&gt;invariants&lt;/strong&gt; (things that must never happen). The hard part isn't writing the code; it's stating the rule precisely and making it safe to evolve without breaking side effects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Codex helps most in backend projects
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Clarifying rules into a testable contract
&lt;/h3&gt;

&lt;p&gt;Before I implement, I use Codex to turn vague rules into something concrete:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Given / When / Then&lt;/strong&gt; scenarios&lt;/li&gt;
&lt;li&gt;edge cases (“what happens if…?”)&lt;/li&gt;
&lt;li&gt;invariants and failure modes&lt;/li&gt;
&lt;li&gt;a decision table or scenario matrix&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is not to “ask the model for the solution.”&lt;br&gt;
The goal is to &lt;strong&gt;remove ambiguity&lt;/strong&gt; and create a contract I can test and maintain.&lt;/p&gt;

&lt;p&gt;A simple prompt that works well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are my backend reviewer. Turn this business rule into:
1) a short spec (in plain English),
2) a scenario matrix (nominal + edge cases),
3) unit tests (PHPUnit for PHP / pytest for Python),
4) implementation notes (invariants, idempotency, transactions, concurrency).
Business rule: &amp;lt;paste here&amp;gt;
Constraints: &amp;lt;DB, performance, API compatibility, etc.&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Surfacing the “invisible constraints” early
&lt;/h3&gt;

&lt;p&gt;The real backend gotchas usually live outside the feature description:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Idempotency&lt;/strong&gt; (duplicate requests, retries)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transactions&lt;/strong&gt; (partial failure)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State machines&lt;/strong&gt; (allowed transitions)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concurrency&lt;/strong&gt; (race conditions)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration contracts&lt;/strong&gt; (API, queues, webhooks)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Codex is good at forcing those questions to the surface. Even if you decide “we don’t need this right now,” at least it becomes a &lt;strong&gt;conscious decision&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Safer refactors across multiple files
&lt;/h3&gt;

&lt;p&gt;Backend refactors are scary because logic tends to spread:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;controllers, services, repositories&lt;/li&gt;
&lt;li&gt;duplicated checks&lt;/li&gt;
&lt;li&gt;hidden coupling through shared helpers&lt;/li&gt;
&lt;li&gt;inconsistent error handling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Codex helps me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;identify duplication and drift&lt;/li&gt;
&lt;li&gt;propose extractions (services / policies / use-cases)&lt;/li&gt;
&lt;li&gt;rename across files consistently&lt;/li&gt;
&lt;li&gt;produce a &lt;em&gt;reviewable patch&lt;/em&gt; instead of isolated snippets&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A concrete example: idempotent checkout with state transitions
&lt;/h2&gt;

&lt;p&gt;Let’s use a common scenario: &lt;strong&gt;checkout&lt;/strong&gt;.&lt;br&gt;
We want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a single charge even if the request is retried&lt;/li&gt;
&lt;li&gt;safe state transitions&lt;/li&gt;
&lt;li&gt;atomic DB changes&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Business rule (simplified)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;A checkout request includes an &lt;code&gt;idempotency_key&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If the same key is seen again, return the previously created result (no duplicate charge).&lt;/li&gt;
&lt;li&gt;Order states: &lt;code&gt;DRAFT -&amp;gt; PENDING_PAYMENT -&amp;gt; PAID&lt;/code&gt; (and no jumping backwards).&lt;/li&gt;
&lt;li&gt;If payment fails, mark as &lt;code&gt;PAYMENT_FAILED&lt;/code&gt; and keep the order consistent.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Example in PHP (Laravel-ish style)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;State transitions as explicit rules:&lt;/strong&gt;&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;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderStatus&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;DRAFT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'DRAFT'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;PENDING_PAYMENT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'PENDING_PAYMENT'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;PAID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'PAID'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;PAYMENT_FAILED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'PAYMENT_FAILED'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;canTransition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$to&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$allowed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DRAFT&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PENDING_PAYMENT&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PENDING_PAYMENT&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PAID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PAYMENT_FAILED&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PAID&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PAYMENT_FAILED&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&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="nb"&gt;in_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$allowed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$from&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&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;strong&gt;Idempotency record (DB-backed):&lt;/strong&gt;&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="c1"&gt;// idempotency_keys table: key (unique), resource_type, resource_id, response_hash, created_at&lt;/span&gt;
&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;IdempotencyStore&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;?IdempotencyRecord&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;IdempotencyRecord&lt;/span&gt; &lt;span class="nv"&gt;$record&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&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;strong&gt;Checkout service with transaction:&lt;/strong&gt;&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;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CheckoutService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;IdempotencyStore&lt;/span&gt; &lt;span class="nv"&gt;$idem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;PaymentGateway&lt;/span&gt; &lt;span class="nv"&gt;$payments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;OrderRepository&lt;/span&gt; &lt;span class="nv"&gt;$orders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;checkout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$orderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;CheckoutResult&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$existing&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;idem&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$idempotencyKey&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;orders&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getCheckoutResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$existing&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;resourceId&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="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;transaction&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="k"&gt;use&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$orderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$order&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;orders&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;lockForUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$orderId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nc"&gt;OrderStatus&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;canTransition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;OrderStatus&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PENDING_PAYMENT&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DomainException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Order cannot be checked out from status &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OrderStatus&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PENDING_PAYMENT&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="n"&gt;orders&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nv"&gt;$payment&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;payments&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;charge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                &lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OrderStatus&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PAID&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="n"&gt;orders&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$order&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="n"&gt;idem&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;IdempotencyRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'order'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;CheckoutResult&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;paid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$payment&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PaymentFailed&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OrderStatus&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PAYMENT_FAILED&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="n"&gt;orders&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$order&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="n"&gt;idem&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;IdempotencyRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'order'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;CheckoutResult&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getMessage&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;});&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;h3&gt;
  
  
  Example tests (PHPUnit)
&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;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;test_checkout_is_idempotent&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$service&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="nf"&gt;makeService&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nv"&gt;$r1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$service&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;checkout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idempotencyKey&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'abc'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$r2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$service&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;checkout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idempotencyKey&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'abc'&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;assertSame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$r1&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$r2&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;orderId&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;assertSame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$r1&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$r2&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;status&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;assertEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&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="n"&gt;paymentGateway&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;chargeCount&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;h3&gt;
  
  
  Same idea in Python (pytest)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_checkout_is_idempotent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payment_gateway&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;r1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;checkout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idempotency_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;abc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;r2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;checkout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idempotency_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;abc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;r1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;r2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;r1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;r2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;payment_gateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;charge_count&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How Codex fits into this workflow
&lt;/h2&gt;

&lt;p&gt;When I work on features like this, I use Codex for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Spec shaping&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;“Rewrite this rule into a testable spec + scenario matrix.”&lt;/li&gt;
&lt;li&gt;“List invariants and failure modes.”&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Test-first scaffolding&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;“Generate PHPUnit/pytest tests for these scenarios.”&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Refactor safety&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;“Extract this into a domain service without changing behavior.”&lt;/li&gt;
&lt;li&gt;“Find duplicated business rules across the repo.”&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Review mode&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;“Act as a backend reviewer: point out hidden regressions, race conditions, missing cases.”&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Example “review prompt”:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Review this diff like a senior backend engineer.
Focus on:
- hidden behavior changes
- missing edge cases
- idempotency / retries
- transaction boundaries and consistency
- concurrency and locking
- error handling and observability
Then propose test cases to cover risks.
Diff: &amp;lt;paste diff or file paths&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Practical tips to get better results (without over-prompting)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Give constraints upfront:&lt;/strong&gt; DB, framework conventions, error handling style.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask for scenarios before code:&lt;/strong&gt; It reduces rework dramatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep business logic explicit:&lt;/strong&gt; state transitions, invariants, and idempotency rules should be visible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat outputs as drafts:&lt;/strong&gt; you still own the architecture and safety decisions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use Codex as a reviewer:&lt;/strong&gt; it’s often most valuable when it challenges your assumptions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What Codex doesn’t replace
&lt;/h2&gt;

&lt;p&gt;Codex is not a substitute for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;understanding your domain&lt;/li&gt;
&lt;li&gt;reviewing security implications&lt;/li&gt;
&lt;li&gt;deciding trade-offs (consistency vs latency, strict vs eventual)&lt;/li&gt;
&lt;li&gt;monitoring, logging, and alerting strategy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But it &lt;strong&gt;does&lt;/strong&gt; compress the feedback loop between “rule” and “correct behavior,” which is where backend teams spend a lot of time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing thoughts
&lt;/h2&gt;

&lt;p&gt;For backend projects, “AI that writes code” is nice.&lt;br&gt;&lt;br&gt;
“AI that helps you &lt;strong&gt;ship safe business logic&lt;/strong&gt;” is a different level of usefulness.&lt;/p&gt;

&lt;p&gt;If you’re building backends in PHP or Python, try using Codex less like a generator and more like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a spec editor,&lt;/li&gt;
&lt;li&gt;a test designer,&lt;/li&gt;
&lt;li&gt;a refactor assistant,&lt;/li&gt;
&lt;li&gt;and a relentless reviewer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s where it pays off.&lt;/p&gt;

</description>
      <category>php</category>
      <category>python</category>
      <category>backend</category>
      <category>testing</category>
    </item>
    <item>
      <title>Laravel 13 + Inertia + Vue 3 + TypeScript: A Practical Upgrade Path from Laravel 12</title>
      <dc:creator>A0mineTV</dc:creator>
      <pubDate>Sun, 01 Mar 2026 16:58:26 +0000</pubDate>
      <link>https://forem.com/blamsa0mine/laravel-13-inertia-vue-3-typescript-a-practical-upgrade-path-from-laravel-12-eck</link>
      <guid>https://forem.com/blamsa0mine/laravel-13-inertia-vue-3-typescript-a-practical-upgrade-path-from-laravel-12-eck</guid>
      <description>&lt;p&gt;Laravel upgrades are often discussed as dry changelogs and release notes. To understand the real-world impact, I built a functional application using &lt;strong&gt;Laravel 13 + Inertia + Vue 3 + TypeScript&lt;/strong&gt; and compared the experience with &lt;strong&gt;Laravel 12&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;The experiment focused on shipping actual features: CRUD screens, validation, complex Eloquent queries, and relationships like reviews and polymorphic media.&lt;/p&gt;

&lt;p&gt;This article is a hands-on summary of what I built and what I learned.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note on Timing:&lt;/strong&gt; At the time of this build, Laravel 13 was available as &lt;code&gt;13.x-dev&lt;/code&gt;. Expect minor ecosystem hiccups when using development branches.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  1. What Really Changes from Laravel 12 → Laravel 13
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The PHP Baseline Move
&lt;/h3&gt;

&lt;p&gt;The most significant "real-world" difference is the baseline: Laravel 13 pushes the minimum PHP version forward. While beneficial long-term, it immediately impacts your infrastructure, requiring updates to &lt;strong&gt;CI images&lt;/strong&gt;, &lt;strong&gt;Docker base images&lt;/strong&gt;, and hosting environments like &lt;strong&gt;Forge or Vapor&lt;/strong&gt;. In practice, the upgrade cost often lies more in your environment and dependencies than in your application code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Attributes Become First-Class
&lt;/h3&gt;

&lt;p&gt;Laravel 13 leans heavily into &lt;strong&gt;PHP Attributes (&lt;code&gt;#[...]&lt;/code&gt;)&lt;/strong&gt; for configuration that previously lived in protected class properties. This makes classes more &lt;strong&gt;declarative&lt;/strong&gt;; you can open a model and immediately see its exports, permissions, and scopes at the top of the file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Inertia Stability
&lt;/h3&gt;

&lt;p&gt;One major advantage is that &lt;strong&gt;Inertia stays stable&lt;/strong&gt; during this transition. You can modernize your backend baseline while keeping your SPA-like frontend workflow—using &lt;code&gt;Inertia::render()&lt;/code&gt;, Vue components, and &lt;code&gt;useForm&amp;lt;T&amp;gt;()&lt;/code&gt;—exactly the same.&lt;/p&gt;

&lt;h3&gt;
  
  
  2) Attributes (annotations) become first-class
&lt;/h3&gt;

&lt;p&gt;Laravel 13 leans into &lt;strong&gt;PHP Attributes&lt;/strong&gt; (&lt;code&gt;#[...]&lt;/code&gt;) for configuration that used to live in class properties.&lt;/p&gt;

&lt;p&gt;This is not about “saving lines of code”. It’s about making classes more &lt;strong&gt;declarative&lt;/strong&gt;: you open a model and immediately see what it allows/exports and which scopes exist.&lt;/p&gt;

&lt;p&gt;Here’s the exact style I used in the project (more on it later):&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="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Database\Eloquent\Builder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Database\Eloquent\Model&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Database\Eloquent\Attributes\Fillable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Database\Eloquent\Attributes\Visible&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Database\Eloquent\Attributes\Scope&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="na"&gt;#[Fillable(['name', 'price_cents', 'is_active'])]&lt;/span&gt;
&lt;span class="na"&gt;#[Visible(['id', 'name', 'price_cents', 'is_active'])]&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;#[Scope]&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Builder&lt;/span&gt; &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$q&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'like'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"%&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$q&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%"&lt;/span&gt;&lt;span class="p"&gt;);&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;h3&gt;
  
  
  3) Inertia stays stable (and that’s a good thing)
&lt;/h3&gt;

&lt;p&gt;On the frontend side, Inertia doesn’t “change with Laravel 13”. The big value is that you can modernize the backend while keeping your SPA-like workflow the same:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;controller returns &lt;code&gt;Inertia::render(...)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;pages are Vue components&lt;/li&gt;
&lt;li&gt;forms are powered by &lt;code&gt;useForm&amp;lt;T&amp;gt;()&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So your upgrade effort is &lt;strong&gt;concentrated in backend baseline + patterns&lt;/strong&gt;, not in “rewriting your frontend”.&lt;/p&gt;




&lt;h2&gt;
  
  
  Project setup: Laravel 13 + Inertia + Vue 3 + TypeScript
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1) Composer + environment
&lt;/h3&gt;

&lt;p&gt;If you are on &lt;code&gt;13.x-dev&lt;/code&gt;, keep your &lt;code&gt;composer.json&lt;/code&gt; clean:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;minimum-stability&lt;/code&gt; and &lt;code&gt;prefer-stable&lt;/code&gt; must be &lt;strong&gt;top-level keys&lt;/strong&gt;, not inside &lt;code&gt;require&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;you may need &lt;code&gt;prefer-stable: true&lt;/code&gt; to avoid pulling dev versions for everything&lt;/li&gt;
&lt;li&gt;some packages may lag behind (I hit this with &lt;code&gt;laravel/tinker&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A minimal starting point:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"require"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"php"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^8.3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"laravel/framework"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"13.x-dev"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"minimum-stability"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"prefer-stable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;If Composer gets stuck on conflicts, I recommend a clean resolution pass:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; vendor composer.lock
composer &lt;span class="nb"&gt;install
&lt;/span&gt;php artisan &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2) Inertia server setup
&lt;/h3&gt;

&lt;p&gt;Install the Laravel adapter:&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 inertiajs/inertia-laravel &lt;span class="nt"&gt;-W&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create the root template at &lt;code&gt;resources/views/app.blade.php&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang="en"&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;meta charset="utf-8" /&amp;gt;
  &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1.0" /&amp;gt;
  @vite(['resources/css/app.css', 'resources/js/app.ts'])
  @inertiaHead
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  @inertia
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then create a middleware to share props (auth user, flash messages, etc.):&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="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Http\Middleware&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Http\Request&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Inertia\Middleware&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HandleInertiaRequests&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Middleware&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$rootView&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'app'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;share&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="k"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;share&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;'auth'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'user'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="s1"&gt;'flash'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'success'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;session&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;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'success'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="s1"&gt;'error'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;session&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;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'error'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;];&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;blockquote&gt;
&lt;p&gt;Laravel 13 uses &lt;code&gt;bootstrap/app.php&lt;/code&gt; middleware registration (no &lt;code&gt;Http\Kernel.php&lt;/code&gt;), so make sure your &lt;code&gt;HandleInertiaRequests&lt;/code&gt; is appended to the &lt;strong&gt;web&lt;/strong&gt; middleware group.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  3) Vue 3 + TypeScript + Vite
&lt;/h3&gt;

&lt;p&gt;Install dependencies (example with pnpm):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm add vue @inertiajs/vue3
pnpm add &lt;span class="nt"&gt;-D&lt;/span&gt; typescript vue-tsc @types/node @vitejs/plugin-vue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;resources/js/app.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./bootstrap&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createApp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createInertiaApp&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@inertiajs/vue3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;resolvePageComponent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;laravel-vite-plugin/inertia-helpers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nf"&gt;createInertiaApp&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&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;resolvePageComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`./Pages/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.vue`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./Pages/**/*.vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
  &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;plugin&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;createApp&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;render&lt;/span&gt;&lt;span class="p"&gt;:&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;h&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;)&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;h2&gt;
  
  
  Feature 1: Products CRUD (Inertia + TS)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Generate the backend pieces
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan make:model Product &lt;span class="nt"&gt;-m&lt;/span&gt;
php artisan make:controller ProductController &lt;span class="nt"&gt;--resource&lt;/span&gt;
php artisan make:request StoreProductRequest
php artisan make:request UpdateProductRequest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Migration (simplified):&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="nc"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'products'&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="kt"&gt;Blueprint&lt;/span&gt; &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;unsignedInteger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'price_cents'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'is_active'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;timestamps&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;h3&gt;
  
  
  Laravel 13-style model configuration with attributes
&lt;/h3&gt;

&lt;p&gt;Here’s the model again (this was the main “Laravel 13” part of the CRUD):&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="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Database\Eloquent\Builder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Database\Eloquent\Model&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Database\Eloquent\Attributes\Fillable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Database\Eloquent\Attributes\Visible&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Database\Eloquent\Attributes\Scope&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="na"&gt;#[Fillable(['name', 'price_cents', 'is_active'])]&lt;/span&gt;
&lt;span class="na"&gt;#[Visible(['id', 'name', 'price_cents', 'is_active'])]&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;#[Scope]&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Builder&lt;/span&gt; &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$q&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'like'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"%&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$q&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%"&lt;/span&gt;&lt;span class="p"&gt;);&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;h3&gt;
  
  
  Controller (pagination + search)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Http\Controllers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Http\Requests\StoreProductRequest&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Http\Requests\UpdateProductRequest&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Models\Product&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Http\Request&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Inertia\Inertia&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Controller&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&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;$q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'q'&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;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="nv"&gt;$products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;query&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;when&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$q&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$q&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;// attribute scope&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;latest&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;paginate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&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;withQueryString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Inertia&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Products/Index'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'products'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$products&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'filters'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'q'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$q&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;create&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="nc"&gt;Inertia&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Products/Create'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&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;StoreProductRequest&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="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&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="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;validated&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;redirect&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;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'products.index'&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;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'success'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Product created.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;edit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Product&lt;/span&gt; &lt;span class="nv"&gt;$product&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="nc"&gt;Inertia&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Products/Edit'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'product'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$product&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;only&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'price_cents'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'is_active'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;UpdateProductRequest&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Product&lt;/span&gt; &lt;span class="nv"&gt;$product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$product&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;validated&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;redirect&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;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'products.index'&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;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'success'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Product updated.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Product&lt;/span&gt; &lt;span class="nv"&gt;$product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$product&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;redirect&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;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'products.index'&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;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'success'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Product deleted.'&lt;/span&gt;&lt;span class="p"&gt;);&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;h3&gt;
  
  
  TypeScript-friendly Inertia pages
&lt;/h3&gt;

&lt;p&gt;On the Vue side, the most important thing is typing your form state and avoiding &lt;code&gt;v-model&lt;/code&gt; on readonly props.&lt;/p&gt;

&lt;p&gt;Example form type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ProductForm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;price_cents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
  &lt;span class="na"&gt;is_active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useForm&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ProductForm&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;price_cents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;is_active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;h2&gt;
  
  
  Feature 2: Reviews (Eloquent relations + Inertia pages)
&lt;/h2&gt;

&lt;p&gt;Once CRUD was working, I added &lt;strong&gt;reviews&lt;/strong&gt; to exercise Eloquent relationships.&lt;/p&gt;

&lt;h3&gt;
  
  
  Database shape
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;products&lt;/code&gt; 1—N &lt;code&gt;reviews&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;customers&lt;/code&gt; 1—N &lt;code&gt;reviews&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;unique constraint: &lt;code&gt;(product_id, customer_id)&lt;/code&gt; to ensure one review per customer per product&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;relationship loading (&lt;code&gt;with('customer')&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;aggregates (&lt;code&gt;withCount&lt;/code&gt;, &lt;code&gt;withAvg&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;nested routes (&lt;code&gt;products/{product}/reviews&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Useful Eloquent aggregate
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;query&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;whereKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$product&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&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;withCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'reviews'&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;withAvg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'reviews'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'rating'&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;first&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This kind of query is where Laravel’s ORM really shines: you get meaningful stats without hand-writing raw SQL everywhere.&lt;/p&gt;




&lt;h2&gt;
  
  
  Feature 3: Polymorphic media (one table, multiple parents)
&lt;/h2&gt;

&lt;p&gt;A polymorphic relation lets you attach media to multiple models (Product, Customer, Order...) using one &lt;code&gt;media&lt;/code&gt; table:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;mediable_type&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mediable_id&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Migration helper:&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="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;morphs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'mediable'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Media&lt;/code&gt; has &lt;code&gt;morphTo()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Product&lt;/code&gt; has &lt;code&gt;morphMany(Media::class, 'mediable')&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why it’s great:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you don’t need &lt;code&gt;product_images&lt;/code&gt;, &lt;code&gt;customer_avatars&lt;/code&gt;, &lt;code&gt;order_files&lt;/code&gt;...&lt;/li&gt;
&lt;li&gt;you get a uniform API in Eloquent (&lt;code&gt;$model-&amp;gt;media()-&amp;gt;create(...)&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Running the app (backend + frontend)
&lt;/h2&gt;

&lt;p&gt;Two terminals:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or a single command if you wired a &lt;code&gt;composer run dev&lt;/code&gt; script that starts both processes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lessons learned (the honest part)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Laravel 13 dev branch is usable&lt;/strong&gt;, but expect dependency mismatches. If a package doesn’t support &lt;code&gt;^13&lt;/code&gt;, either drop it temporarily or use its dev branch.&lt;/li&gt;
&lt;li&gt;The biggest win of Laravel 13 in my build was &lt;strong&gt;declarative configuration with attributes&lt;/strong&gt;. It doesn’t rewrite your app, but it makes your codebase easier to read as it grows.&lt;/li&gt;
&lt;li&gt;Inertia is a perfect match for this kind of exploration: you can build real screens fast while keeping backend logic “Laravel-native”.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Next steps (if you want to push the ORM further)
&lt;/h2&gt;

&lt;p&gt;If you want to turn this into an even better Eloquent playground:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;product variants (Product → ProductVariant, options/values, complex pivots)&lt;/li&gt;
&lt;li&gt;inventory &amp;amp; stock movements (transactions + aggregates)&lt;/li&gt;
&lt;li&gt;multi-tenant scoping (global scopes + tenant_id)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>vue</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Laravel Scheduler in Production: Why I Use It (and How I Make It Reliable)</title>
      <dc:creator>A0mineTV</dc:creator>
      <pubDate>Thu, 26 Feb 2026 12:49:20 +0000</pubDate>
      <link>https://forem.com/blamsa0mine/laravel-scheduler-in-production-why-i-use-it-and-how-i-make-it-reliable-43df</link>
      <guid>https://forem.com/blamsa0mine/laravel-scheduler-in-production-why-i-use-it-and-how-i-make-it-reliable-43df</guid>
      <description>&lt;p&gt;Scheduled tasks are easy—until they aren’t. The first time an invoice isn’t sent, a sync silently stops, or a report runs twice and crashes your server, you realize scheduled work isn’t just "ops trivia." It’s a &lt;strong&gt;product risk&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For a long time, I treated scheduling as a server concern by adding lines to a crontab. But that led to major pain points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Tasks were configured outside the codebase (not versioned or reviewable).&lt;/li&gt;
&lt;li&gt;  Differences between staging and production ("it works on my server").&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Overlapping jobs&lt;/strong&gt; because a previous run didn't finish.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Double executions&lt;/strong&gt; when the app scaled to multiple instances.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Laravel’s Scheduler solves the real problem: &lt;strong&gt;Governance&lt;/strong&gt;. It turns scheduling into something you can read, review, deploy, and reason about.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core idea: the server triggers, Laravel orchestrates
&lt;/h2&gt;

&lt;p&gt;In production, you only need &lt;strong&gt;one&lt;/strong&gt; cron entry on your server:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;run &lt;code&gt;php artisan schedule:run&lt;/code&gt; every minute&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That’s it.&lt;/p&gt;

&lt;p&gt;From this point, Laravel decides which tasks are due. Your schedule becomes part of your application code, shifting it from a "mystery ops config" to versioned application behavior.&lt;/p&gt;

&lt;p&gt;This shifts scheduling from “mystery ops config” to &lt;strong&gt;versioned application behavior&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I prefer Laravel Scheduler over “pure crontab”
&lt;/h2&gt;

&lt;p&gt;I’m not anti-cron. Cron is great at one thing: triggering commands at a regular cadence.&lt;/p&gt;

&lt;p&gt;The issues start when cron becomes the place where &lt;em&gt;business-critical workflows&lt;/em&gt; live. Because then you’re maintaining system behavior in a place that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;is not part of pull requests&lt;/li&gt;
&lt;li&gt;can’t be code-reviewed the same way&lt;/li&gt;
&lt;li&gt;varies between environments&lt;/li&gt;
&lt;li&gt;becomes messy over time (and nobody wants to touch it)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Laravel Scheduler fixes that by letting me express scheduling as &lt;strong&gt;intent&lt;/strong&gt;, not cron syntax.&lt;/p&gt;

&lt;p&gt;Instead of thinking “what is the crontab line for weekdays at 02:00?”, I can encode the intent directly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;every day at 2 AM&lt;/li&gt;
&lt;li&gt;in the correct timezone&lt;/li&gt;
&lt;li&gt;never overlap&lt;/li&gt;
&lt;li&gt;only run once even with multiple servers&lt;/li&gt;
&lt;li&gt;keep output for auditing&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  My “production defaults”: make tasks safe by design
&lt;/h2&gt;

&lt;p&gt;Almost every important scheduled task in my projects includes two safety guarantees:&lt;/p&gt;

&lt;h3&gt;
  
  
  1) No overlaps
&lt;/h3&gt;

&lt;p&gt;If a job is still running, the next scheduled tick should &lt;strong&gt;not&lt;/strong&gt; start another copy.&lt;/p&gt;

&lt;p&gt;That’s what &lt;code&gt;withoutOverlapping()&lt;/code&gt; gives you.&lt;/p&gt;

&lt;p&gt;Overlaps cause the most annoying class of bugs: &lt;em&gt;duplicates&lt;/em&gt;. Duplicate emails. Duplicate invoices. Duplicate exports. Duplicate API calls. Duplicate side effects.&lt;/p&gt;

&lt;h3&gt;
  
  
  2) One execution across multiple servers
&lt;/h3&gt;

&lt;p&gt;When an app scales horizontally, cron runs on every instance by default.&lt;/p&gt;

&lt;p&gt;Without protection, the same scheduled task can run N times.&lt;/p&gt;

&lt;p&gt;That’s what &lt;code&gt;onOneServer()&lt;/code&gt; is for.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you’ve ever scaled to two servers and suddenly saw doubled notifications… you only need that incident once to adopt &lt;code&gt;onOneServer()&lt;/code&gt; forever.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  A concrete example (Carbon-friendly snippet)
&lt;/h2&gt;

&lt;p&gt;Let’s say you generate a daily report at 2 AM:&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="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Schedule&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;Schedule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'reports:daily'&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;dailyAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'02:00'&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;timezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Europe/Paris'&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;onOneServer&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;withoutOverlapping&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;sendOutputTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;storage_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'logs/schedule-reports.log'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What I like about this code is that it reads like a checklist of business intent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Daily at 02:00&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Correct timezone&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Single run across instances&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;No overlap&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Output preserved for auditing&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Observability: don’t “trust” schedules—prove they run
&lt;/h2&gt;

&lt;p&gt;A scheduled task that fails silently is worse than one that fails loudly.&lt;/p&gt;

&lt;p&gt;So I treat scheduled work like I treat anything business-critical: it needs traceability.&lt;/p&gt;

&lt;p&gt;At minimum, I want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;output persisted somewhere (&lt;code&gt;sendOutputTo&lt;/code&gt;, &lt;code&gt;appendOutputTo&lt;/code&gt;, etc.)&lt;/li&gt;
&lt;li&gt;a way to audit the configured schedule (&lt;code&gt;php artisan schedule:list&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;visibility in logs/monitoring when something breaks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the task is critical (payments, invoices, notifications), I go a step further and connect failures to alerts.&lt;/p&gt;

&lt;p&gt;The point is not “more tooling”. The point is &lt;strong&gt;shorter time to detect&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The scheduler is an orchestrator, not a worker
&lt;/h2&gt;

&lt;p&gt;Here’s a rule that saved me multiple times:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The scheduler should trigger work, not &lt;em&gt;be&lt;/em&gt; the work.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If something can be slow, fragile, or dependent on external services, I don’t want it to run as one long synchronous command inside &lt;code&gt;schedule:run&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Instead, I schedule a command that dispatches a job:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the schedule stays quick and predictable&lt;/li&gt;
&lt;li&gt;the heavy work runs in the queue&lt;/li&gt;
&lt;li&gt;retries and failures are handled properly&lt;/li&gt;
&lt;li&gt;monitoring becomes easier&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is also how you avoid minute-based drift when tasks take longer than expected.&lt;/p&gt;




&lt;h2&gt;
  
  
  The “SQL vs PHP” equivalent in scheduling
&lt;/h2&gt;

&lt;p&gt;I use a similar separation of concerns as with data transformations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cron/Laravel schedule: &lt;strong&gt;when&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;job/command: &lt;strong&gt;what&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;queue workers: &lt;strong&gt;how it executes reliably&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When those responsibilities are mixed, maintenance becomes painful.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common pitfalls (that look fine until production)
&lt;/h2&gt;

&lt;p&gt;I’ve seen these issues repeatedly:&lt;/p&gt;

&lt;h3&gt;
  
  
  “It works locally but not in prod”
&lt;/h3&gt;

&lt;p&gt;Often the schedule is correct, but the server isn’t actually triggering it (missing cron entry, wrong PHP path, wrong user).&lt;/p&gt;

&lt;h3&gt;
  
  
  Overlaps
&lt;/h3&gt;

&lt;p&gt;The task runs every minute, but takes 2 minutes. Now you have two copies running. Then three.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-instance duplicates
&lt;/h3&gt;

&lt;p&gt;Scaling from 1 to 2 servers doubles everything—emails, webhooks, cleanup jobs.&lt;/p&gt;

&lt;h3&gt;
  
  
  No logs, no audit trail
&lt;/h3&gt;

&lt;p&gt;You’re guessing whether it ran. Guessing is not a strategy.&lt;/p&gt;




&lt;h2&gt;
  
  
  A quick production checklist
&lt;/h2&gt;

&lt;p&gt;If I’m shipping scheduled tasks, I want to answer these questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is there exactly one trigger cron on the server ?&lt;/li&gt;
&lt;li&gt;Can this task overlap? If yes, how do I prevent it ?&lt;/li&gt;
&lt;li&gt;Can this task run on multiple servers ? If yes, how do I enforce single execution ?&lt;/li&gt;
&lt;li&gt;Where do I see output ?&lt;/li&gt;
&lt;li&gt;If it fails, how do I find out quickly ?&lt;/li&gt;
&lt;li&gt;Should this be a queued job instead ?&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;Laravel Scheduler doesn’t just “schedule tasks”.&lt;/p&gt;

&lt;p&gt;It gives you a way to treat scheduled work like &lt;strong&gt;real application behavior&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;versioned&lt;/li&gt;
&lt;li&gt;reviewable&lt;/li&gt;
&lt;li&gt;predictable&lt;/li&gt;
&lt;li&gt;safer in production&lt;/li&gt;
&lt;li&gt;easier to audit and troubleshoot&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And once you’ve dealt with a silent failure or duplicated side effects in production, that shift is worth a lot.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>devops</category>
      <category>backend</category>
    </item>
    <item>
      <title>Laravel Collections: Why I Use Them for Business Logic (Not Just Convenience)</title>
      <dc:creator>A0mineTV</dc:creator>
      <pubDate>Wed, 25 Feb 2026 12:30:42 +0000</pubDate>
      <link>https://forem.com/blamsa0mine/laravel-collections-why-i-use-them-for-business-logic-not-just-convenience-1n4n</link>
      <guid>https://forem.com/blamsa0mine/laravel-collections-why-i-use-them-for-business-logic-not-just-convenience-1n4n</guid>
      <description>&lt;p&gt;When we talk about Laravel, the conversation usually focuses on Eloquent, migrations, or queues. But if I had to pick one feature that improves my day-to-day code quality the most, it would be &lt;strong&gt;Laravel Collections&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I don't use them because they look "fancy." I use them because they help me write &lt;strong&gt;business logic&lt;/strong&gt; that is easier to read, maintain, and explain to others.&lt;/p&gt;

&lt;p&gt;I use Collections because they help me write &lt;strong&gt;business logic&lt;/strong&gt; in a way that is easier to read, easier to maintain, and easier to explain to another developer.&lt;/p&gt;

&lt;p&gt;In short: they help me separate &lt;strong&gt;what the code does&lt;/strong&gt; from &lt;strong&gt;how the data is being iterated&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real problem with &lt;code&gt;foreach&lt;/code&gt; in business logic
&lt;/h2&gt;

&lt;p&gt;There is nothing wrong with &lt;code&gt;foreach&lt;/code&gt; for simple iterations. However, as projects grow, business logic inside loops often turns into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Nested loops&lt;/strong&gt; and temporary "accumulator" arrays.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Branching logic (if/else)&lt;/strong&gt; spread everywhere.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Mixed responsibilities&lt;/strong&gt;: The code handles &lt;em&gt;how&lt;/em&gt; to iterate data and &lt;em&gt;what&lt;/em&gt; to do with it at the same time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes the code hard to review, debug, or change without introducing subtle bugs.&lt;/p&gt;

&lt;p&gt;That’s the reason I reach for Collections so often.&lt;/p&gt;




&lt;h2&gt;
  
  
  How I think about it: SQL/Eloquent vs Collections
&lt;/h2&gt;

&lt;p&gt;To keep my projects clean, I follow a strict split of responsibilities:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Database / Eloquent (Data Retrieval)&lt;/strong&gt;: I use SQL for filtering large datasets, joins, and basic aggregates. This reduces the amount of data loaded into memory.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Collections (Business Transformations)&lt;/strong&gt;: Once the data is in PHP, I use Collections for reshaping, grouping, formatting, and applying complex conditional rules.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This separation ensures the database does what it’s good at, while the Collection pipeline handles the &lt;strong&gt;business intent&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The database does what it is good at.&lt;br&gt;
The Collection pipeline handles what is easier to express in PHP business logic.&lt;/p&gt;


&lt;h2&gt;
  
  
  Why Collections improve readability
&lt;/h2&gt;

&lt;p&gt;Collections allow you to write code as a sequence of &lt;strong&gt;intentional steps&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Filter&lt;/strong&gt; out what you don't need.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Transform&lt;/strong&gt; the data shape.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Group&lt;/strong&gt; by a specific criteria.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Aggregate&lt;/strong&gt; (sum, count, etc.).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Sort&lt;/strong&gt; the result.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s a huge difference compared to a loop-heavy approach where those steps are mixed together.&lt;/p&gt;

&lt;p&gt;When I read a Collection pipeline, I can usually understand the &lt;strong&gt;business intent&lt;/strong&gt; first.&lt;/p&gt;

&lt;p&gt;That matters a lot in real work, because most of the time we are not writing algorithms from scratch — we are maintaining and extending business rules.&lt;/p&gt;


&lt;h2&gt;
  
  
  A concrete example: top categories from paid orders
&lt;/h2&gt;

&lt;p&gt;Imagine you need to compute revenue per category for paid orders, sorted by the highest earners.&lt;/p&gt;

&lt;p&gt;Here is how I would write it with Collections:&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="nv"&gt;$topCategories&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$orders&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;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'paid'&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;flatMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'items'&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;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'category'&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;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$category&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'category'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'revenue'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$items&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'price'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'qty'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s1"&gt;'products'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$items&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;pluck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'name'&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;unique&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(),&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;sortByDesc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'revenue'&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;take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&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;values&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What I like about this snippet is that I can almost read it as plain English.&lt;/p&gt;

&lt;p&gt;It describes the &lt;strong&gt;business process&lt;/strong&gt; step by step.&lt;/p&gt;




&lt;h2&gt;
  
  
  The same kind of logic with loops gets noisy fast
&lt;/h2&gt;

&lt;p&gt;A loop-based version is absolutely possible.&lt;/p&gt;

&lt;p&gt;But it usually introduces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;mutable state (&lt;code&gt;$result&lt;/code&gt;, &lt;code&gt;$totals&lt;/code&gt;, &lt;code&gt;$seenProducts&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;branching logic inside loops&lt;/li&gt;
&lt;li&gt;manual initialization&lt;/li&gt;
&lt;li&gt;more room for subtle bugs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Something like this (simplified):&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="nv"&gt;$categories&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$orders&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="s1"&gt;'paid'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'items'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$category&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'category'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="k"&gt;isset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$categories&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$category&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$categories&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$category&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'category'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'revenue'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'products'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
            &lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$categories&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$category&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'revenue'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'price'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'qty'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="nv"&gt;$categories&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$category&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'products'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nv"&gt;$topCategories&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$categories&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$category&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$category&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'products'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$category&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'products'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="nv"&gt;$topCategories&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$category&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;usort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$topCategories&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'revenue'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'revenue'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="nv"&gt;$topCategories&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;array_slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$topCategories&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works.&lt;/p&gt;

&lt;p&gt;But the business intent is less obvious because the iteration mechanics are taking center stage.&lt;/p&gt;

&lt;p&gt;That’s exactly why I prefer Collections when the transformation is more “business” than “algorithmic”.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this scales better in a team
&lt;/h2&gt;

&lt;p&gt;For me, the biggest benefit is not syntax.&lt;/p&gt;

&lt;p&gt;It’s &lt;strong&gt;team readability&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When code is written as a Collection pipeline, another developer can usually:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;scan it quickly&lt;/li&gt;
&lt;li&gt;identify each transformation step&lt;/li&gt;
&lt;li&gt;modify one step without rewriting everything&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That makes refactoring safer.&lt;/p&gt;

&lt;p&gt;It also makes code reviews faster, because the intent is explicit.&lt;/p&gt;

&lt;p&gt;And in client work or product teams, that matters a lot more than saving 3 lines of code.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Collection methods I use the most (and why)
&lt;/h2&gt;

&lt;p&gt;Here are the methods I use constantly in Laravel projects:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;filter()&lt;/code&gt; / &lt;code&gt;reject()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;For cleaning a dataset before doing anything else.&lt;/p&gt;

&lt;p&gt;Use case: keep only valid items, paid orders, active users, etc.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;map()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;For transforming the shape of data.&lt;/p&gt;

&lt;p&gt;Use case: converting internal arrays/models into API-friendly or UI-friendly output.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;flatMap()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Great when each item contains a nested list and you want a single flattened list.&lt;/p&gt;

&lt;p&gt;Use case: orders → items, users → permissions, posts → tags.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;groupBy()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;One of the most useful methods for reports.&lt;/p&gt;

&lt;p&gt;Use case: group by category, day, status, team, source, etc.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;pluck()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Perfect for extracting a single field quickly.&lt;/p&gt;

&lt;p&gt;Use case: IDs, names, emails, labels.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;sum()&lt;/code&gt;, &lt;code&gt;count()&lt;/code&gt;, &lt;code&gt;reduce()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;For aggregations.&lt;/p&gt;

&lt;p&gt;I use &lt;code&gt;reduce()&lt;/code&gt; when I need custom accumulation logic. For simpler totals, &lt;code&gt;sum()&lt;/code&gt; and &lt;code&gt;count()&lt;/code&gt; are clearer.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;sortBy()&lt;/code&gt; / &lt;code&gt;sortByDesc()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;For final ordering before returning the result.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this works so well with Eloquent
&lt;/h2&gt;

&lt;p&gt;Another reason I use Collections so much: they fit naturally into Laravel’s flow.&lt;/p&gt;

&lt;p&gt;You fetch a set of models with Eloquent, and then you can immediately transform them with Collection methods.&lt;/p&gt;

&lt;p&gt;That transition from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;retrieved data&lt;/strong&gt;
to&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;business-ready data&lt;/strong&gt;
is one of the things Laravel does really well.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It makes the backend code feel consistent.&lt;/p&gt;




&lt;h2&gt;
  
  
  A practical rule I follow (important)
&lt;/h2&gt;

&lt;p&gt;I do &lt;strong&gt;not&lt;/strong&gt; use Collections blindly for everything.&lt;/p&gt;

&lt;p&gt;Here’s my rule:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If it is faster, simpler, and more correct in SQL → do it in SQL.&lt;/li&gt;
&lt;li&gt;If it is easier to understand as business logic in PHP → use Collections.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This matters because Collections run in PHP memory.&lt;/p&gt;

&lt;p&gt;So if I am dealing with a very large dataset, I first reduce the dataset in the query layer, then use Collections for the final transformation.&lt;/p&gt;

&lt;p&gt;That balance is usually the sweet spot.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bonus: use macros for repeated business transformations
&lt;/h2&gt;

&lt;p&gt;One underrated feature is that Collections are &lt;strong&gt;macroable&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That means you can define your own reusable Collection methods for recurring transformations.&lt;/p&gt;

&lt;p&gt;If your project often computes the same kind of report or formatting, a macro can help standardize that logic and keep pipelines even cleaner.&lt;/p&gt;

&lt;p&gt;This is especially useful in larger codebases where business transformations start to repeat.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;Laravel Collections are not just a convenience API.&lt;/p&gt;

&lt;p&gt;For me, they are a &lt;strong&gt;design tool&lt;/strong&gt; for writing clearer backend code.&lt;/p&gt;

&lt;p&gt;They help me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;separate retrieval from transformation&lt;/li&gt;
&lt;li&gt;express business logic as a pipeline&lt;/li&gt;
&lt;li&gt;reduce loop noise&lt;/li&gt;
&lt;li&gt;make code easier to maintain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And in real projects, that readability pays off every time.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>backend</category>
      <category>cleancode</category>
    </item>
    <item>
      <title>Laravel Notifications in Practice: Mail, Database, Queues, and Clean Testing</title>
      <dc:creator>A0mineTV</dc:creator>
      <pubDate>Fri, 20 Feb 2026 12:21:14 +0000</pubDate>
      <link>https://forem.com/blamsa0mine/laravel-notifications-le-guide-pratique-mail-database-et-performance-18hm</link>
      <guid>https://forem.com/blamsa0mine/laravel-notifications-le-guide-pratique-mail-database-et-performance-18hm</guid>
      <description>&lt;p&gt;Notifications look simple at first—until your app grows. You start by sending a quick email from a controller, but soon you need more:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Email &lt;strong&gt;and&lt;/strong&gt; in-app notifications.&lt;/li&gt;
&lt;li&gt;  Different channels depending on user preferences.&lt;/li&gt;
&lt;li&gt;  Queued delivery so the UI stays fast.&lt;/li&gt;
&lt;li&gt;  Clean tests that don't actually hit your SMTP server.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is exactly where &lt;strong&gt;Laravel Notifications&lt;/strong&gt; shine. They provide a structured way to send short, event-driven messages across multiple channels using a single class.&lt;/p&gt;

&lt;p&gt;Laravel gives you a clean, structured way to send short, event-driven messages (like &lt;em&gt;Order shipped&lt;/em&gt;, &lt;em&gt;Invoice paid&lt;/em&gt;, &lt;em&gt;New comment&lt;/em&gt;, &lt;em&gt;Password changed&lt;/em&gt;) across multiple channels with one notification class.&lt;/p&gt;

&lt;p&gt;In this article, I’ll show a practical setup you can reuse in real projects.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why use Notifications instead of sending mail directly ?
&lt;/h2&gt;

&lt;p&gt;While you &lt;em&gt;could&lt;/em&gt; call &lt;code&gt;Mail::to()-&amp;gt;send()&lt;/code&gt; everywhere, notifications offer a better architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Centralized logic&lt;/strong&gt;: One place to define delivery channels via the &lt;code&gt;via()&lt;/code&gt; method.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Multi-channel support&lt;/strong&gt;: Easily switch between or combine mail, database, Slack, and SMS.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Built-in Queues&lt;/strong&gt;: Native support for background processing.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Observability&lt;/strong&gt;: Built-in support for testing with &lt;code&gt;Notification::fake()&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes your code easier to maintain when your app evolves.&lt;/p&gt;




&lt;h2&gt;
  
  
  A real example: order shipped
&lt;/h2&gt;

&lt;p&gt;Let’s say you have an e-commerce app and want to notify a user when an order is shipped.&lt;/p&gt;

&lt;p&gt;We’ll send:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an &lt;strong&gt;email notification&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;an &lt;strong&gt;in-app notification&lt;/strong&gt; stored in the database&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 1: Generate a notification
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan make:notification OrderShipped
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Laravel creates a class in &lt;code&gt;app/Notifications/OrderShipped.php&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Build the notification class
&lt;/h2&gt;

&lt;p&gt;Here is a practical version:&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="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Notifications&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Models\Order&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Bus\Queueable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Contracts\Queue\ShouldQueue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Notifications\Messages\MailMessage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Notifications\Notification&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderShipped&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Notification&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;ShouldQueue&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Queueable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;Order&lt;/span&gt; &lt;span class="nv"&gt;$order&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Decide which channels to use.
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;via&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="nv"&gt;$notifiable&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'mail'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'database'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Email version.
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;toMail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="nv"&gt;$notifiable&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;MailMessage&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MailMessage&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;subject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Your order has been shipped'&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;greeting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Hello '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$notifiable&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;' 👋'&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;line&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Good news! Your order #&lt;/span&gt;&lt;span class="si"&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;order&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; has been shipped."&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;action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Track my order'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/orders/&lt;/span&gt;&lt;span class="si"&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;order&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&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;line&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Thank you for your purchase.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Database version (stored as JSON in notifications table).
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;toArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="nv"&gt;$notifiable&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'order_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;order&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'status'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'shipped'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'message'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"Your order #&lt;/span&gt;&lt;span class="si"&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;order&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; has been shipped."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'url'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"/orders/&lt;/span&gt;&lt;span class="si"&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;order&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;];&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;h3&gt;
  
  
  Why this is clean
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;via()&lt;/code&gt; defines the channels in one place&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;toMail()&lt;/code&gt; handles only the email message&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;toArray()&lt;/code&gt; formats the in-app payload&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ShouldQueue&lt;/code&gt; + &lt;code&gt;Queueable&lt;/code&gt; keeps delivery async and your request fast&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 3: Send the notification
&lt;/h2&gt;

&lt;p&gt;If your &lt;code&gt;User&lt;/code&gt; model uses Laravel’s default &lt;code&gt;Notifiable&lt;/code&gt; trait (it usually does), sending is straightforward:&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="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OrderShipped&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also send to multiple users:&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="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Notification&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;Notification&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$admins&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OrderShipped&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is super useful for admin alerts, moderation events, or internal ops notifications.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Enable database notifications
&lt;/h2&gt;

&lt;p&gt;If you want in-app notifications (bell icon, notification list, unread count), create the notifications table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan make:notifications-table
php artisan migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Laravel stores notification payloads in a &lt;code&gt;notifications&lt;/code&gt; table (including the &lt;code&gt;type&lt;/code&gt;, JSON &lt;code&gt;data&lt;/code&gt;, and &lt;code&gt;read_at&lt;/code&gt; status).&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: Display notifications in your app
&lt;/h2&gt;

&lt;p&gt;Because your model uses &lt;code&gt;Notifiable&lt;/code&gt;, you get relationships out of the box:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;$user-&amp;gt;notifications&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$user-&amp;gt;unreadNotifications&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$user-&amp;gt;readNotifications&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example in a controller:&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;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&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;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&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;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="s1"&gt;'unread_count'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;unreadNotifications&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="s1"&gt;'items'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;notifications&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;latest&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;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&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;get&lt;/span&gt;&lt;span class="p"&gt;(),&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;This is enough to build a simple notification center in Vue / React / Blade.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6: Mark notifications as read
&lt;/h2&gt;

&lt;p&gt;A common pattern: when a user opens the notification list, mark unread notifications as read.&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;markAllAsRead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&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;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;unreadNotifications&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;markAsRead&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&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;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'message'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Notifications marked as read'&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;For large volumes, you can also use a direct query update (more efficient than looping in memory).&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 7: Keep your app fast with queues
&lt;/h2&gt;

&lt;p&gt;Notifications often call external services (SMTP, Slack, SMS providers), so they should usually be queued.&lt;/p&gt;

&lt;p&gt;We already added:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;implements ShouldQueue&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;use Queueable&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now make sure your queue is configured and a worker is running (Redis is a great choice in production).&lt;/p&gt;

&lt;p&gt;Example local setup:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Then run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan queue:work
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why queueing matters
&lt;/h3&gt;

&lt;p&gt;Without queues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;user clicks a button&lt;/li&gt;
&lt;li&gt;request waits for email/SMS API&lt;/li&gt;
&lt;li&gt;response feels slower&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With queues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;notification job is pushed to the queue&lt;/li&gt;
&lt;li&gt;request returns quickly&lt;/li&gt;
&lt;li&gt;worker sends the notification in the background&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This becomes a big win as soon as you have real traffic.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 8: Test notifications cleanly
&lt;/h2&gt;

&lt;p&gt;One of Laravel’s best features here is &lt;code&gt;Notification::fake()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It lets you test behavior without sending anything.&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="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Models\Order&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Models\User&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Notifications\OrderShipped&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Notification&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'notifies the user when an order is shipped'&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="nc"&gt;Notification&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;fake&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&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;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Order&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;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'user_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="c1"&gt;// Your application logic...&lt;/span&gt;
    &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OrderShipped&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="nc"&gt;Notification&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;assertSentTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;OrderShipped&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&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;This keeps tests fast, reliable, and focused on your business logic.&lt;/p&gt;




&lt;h2&gt;
  
  
  A practical pattern I like
&lt;/h2&gt;

&lt;p&gt;In real projects, I usually trigger notifications &lt;strong&gt;after a state change&lt;/strong&gt; in a service/action class.&lt;/p&gt;

&lt;p&gt;Example:&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ShipOrderAction&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Order&lt;/span&gt; &lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'shipped'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OrderShipped&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="p"&gt;));&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;Why this works well:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;controller stays thin&lt;/li&gt;
&lt;li&gt;notification logic is tied to the domain action&lt;/li&gt;
&lt;li&gt;easy to test&lt;/li&gt;
&lt;li&gt;easy to reuse from HTTP, CLI, or jobs&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Common mistakes to avoid
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1) Sending notifications directly in controllers everywhere
&lt;/h3&gt;

&lt;p&gt;It works at first, but it spreads your logic across the app.&lt;/p&gt;

&lt;h3&gt;
  
  
  2) Forgetting to queue notifications
&lt;/h3&gt;

&lt;p&gt;Mail and third-party channels can slow down your requests a lot.&lt;/p&gt;

&lt;h3&gt;
  
  
  3) Overloading notifications with business logic
&lt;/h3&gt;

&lt;p&gt;Keep notifications focused on &lt;strong&gt;delivery + presentation&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
Your business rules should live in services/actions.&lt;/p&gt;
&lt;h3&gt;
  
  
  4) Mixing database and broadcast payloads without thinking
&lt;/h3&gt;

&lt;p&gt;If your database payload and realtime frontend payload need to differ, define &lt;code&gt;toDatabase()&lt;/code&gt; and &lt;code&gt;toBroadcast()&lt;/code&gt; separately instead of relying on one &lt;code&gt;toArray()&lt;/code&gt; for both.&lt;/p&gt;


&lt;h2&gt;
  
  
  Bonus ideas for production projects
&lt;/h2&gt;

&lt;p&gt;Once your base setup is working, Laravel Notifications scale really well:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Admin alerts&lt;/strong&gt; (new signup, failed payment, suspicious login)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User activity&lt;/strong&gt; (comment replies, mentions, reminders)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subscription events&lt;/strong&gt; (trial ending, invoice paid, renewal failed)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Realtime UI notifications&lt;/strong&gt; with broadcasting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Channel preferences&lt;/strong&gt; (email only, app only, both)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can even make the &lt;code&gt;via()&lt;/code&gt; method dynamic:&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;via&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="nv"&gt;$notifiable&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&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;$notifiable&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;wants_email_notifications&lt;/span&gt;
        &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'mail'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'database'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'database'&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 a very clean way to support user preferences.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Laravel Notifications are simple to start with, but very powerful when your app grows.&lt;/p&gt;

&lt;p&gt;They give you a consistent structure for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;deciding &lt;strong&gt;where&lt;/strong&gt; a message should be sent&lt;/li&gt;
&lt;li&gt;formatting it for each channel&lt;/li&gt;
&lt;li&gt;queueing it for performance&lt;/li&gt;
&lt;li&gt;testing it safely&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re building Laravel apps with user workflows (orders, bookings, payments, accounts, dashboards), Notifications are one of those features that quickly become &lt;strong&gt;foundational&lt;/strong&gt;.&lt;/p&gt;

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