<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nicolas Bonnici</title>
    <description>The latest articles on DEV Community by Nicolas Bonnici (@nicolasbonnici).</description>
    <link>https://dev.to/nicolasbonnici</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%2F30167%2F500aedaa-8525-4cde-9e66-e1a32f911940.jpeg</url>
      <title>DEV Community: Nicolas Bonnici</title>
      <link>https://dev.to/nicolasbonnici</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nicolasbonnici"/>
    <language>en</language>
    <item>
      <title>Say hello to GoREST v0.3</title>
      <dc:creator>Nicolas Bonnici</dc:creator>
      <pubDate>Thu, 04 Dec 2025 21:12:46 +0000</pubDate>
      <link>https://dev.to/nicolasbonnici/say-hello-to-gorest-v03-4k8k</link>
      <guid>https://dev.to/nicolasbonnici/say-hello-to-gorest-v03-4k8k</guid>
      <description>&lt;h2&gt;
  
  
  What's New Under the Hood?
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Plugins
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Plugins for all, plugins everywhere, plugins for everything. Whether&lt;br&gt;
custom or core features there's a plugin for that. Want to create&lt;br&gt;
your own? Let's go!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Futuwxq6h0tnsjp74znzg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Futuwxq6h0tnsjp74znzg.png" alt="Meme about&amp;lt;br&amp;gt;
plugins" width="600" height="327"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With &lt;a href="https://github.com/nicolasbonnici/gorest" rel="noopener noreferrer"&gt;the release of v0.3&lt;/a&gt;, every feature of the GoREST core is now optional and fully configurable.&lt;/p&gt;

&lt;p&gt;Each plugin has its own configuration in the &lt;a href="https://github.com/nicolasbonnici/gorest?tab=readme-ov-file#plugin-configuration" rel="noopener noreferrer"&gt;&lt;code&gt;gorest.yaml&lt;/code&gt; plugins&lt;br&gt;
section&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here's the list of the first available core plugins:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authentication with JWT support&lt;/li&gt;
&lt;li&gt;Content-type negotiation&lt;/li&gt;
&lt;li&gt;Logger&lt;/li&gt;
&lt;li&gt;Rate limiter&lt;/li&gt;
&lt;li&gt;Request identifier&lt;/li&gt;
&lt;li&gt;Security&lt;/li&gt;
&lt;li&gt;CORS management&lt;/li&gt;
&lt;li&gt;Health check&lt;/li&gt;
&lt;li&gt;Benchmark&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you need something specific, you can build your own plugin. Here's an&lt;br&gt;
example of a simple &lt;code&gt;customplugin&lt;/code&gt; that calculates request duration time&lt;br&gt;
and adds it to the response headers.&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;package&lt;/span&gt; &lt;span class="n"&gt;customplugin&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"time"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/gofiber/fiber/v2"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/nicolasbonnici/gorest/plugin"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// TimingPlugin adds request execution time headers to all responses&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;TimingPlugin&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;enabled&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// NewTimingPlugin creates a new timing plugin instance&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewTimingPlugin&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;plugin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Plugin&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;TimingPlugin&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;enabled&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&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;p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;TimingPlugin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Name&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="s"&gt;"timing"&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;p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;TimingPlugin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&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;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;interface&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="n"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"enabled"&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="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;enabled&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;nil&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;p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;TimingPlugin&lt;/span&gt;&lt;span class="p"&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;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="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;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;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enabled&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;Next&lt;/span&gt;&lt;span class="p"&gt;()&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="c"&gt;// Process request&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="c"&gt;// Calculate duration&lt;/span&gt;
        &lt;span class="n"&gt;duration&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;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="c"&gt;// Add timing headers&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;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"X-Response-Time"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;duration&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;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"X-Response-Time-Ms"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%.2f"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;float64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Microseconds&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1000.0&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;Main advantages of this new plugin system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Plugins can be applied globally across the API or restricted to specific endpoints and methods.&lt;/li&gt;
&lt;li&gt;Plugins can &lt;strong&gt;create new API endpoints&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Plugin can also provide &lt;strong&gt;CLI commands&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Plugins vs Existing Hook System
&lt;/h3&gt;

&lt;p&gt;Plugins are designed for generic API functionalities, while &lt;a href="https://github.com/nicolasbonnici/gorest/blob/trunk/HOOKS.md" rel="noopener noreferrer"&gt;the hook&lt;br&gt;
system&lt;/a&gt; lets you implement business-specific logic. You can share GoREST plugins publicly, but hooks are tied to your business objects and logic.&lt;/p&gt;
&lt;h3&gt;
  
  
  Flexibility Is Key
&lt;/h3&gt;

&lt;p&gt;Every API has its own needs and specificities. The GoREST core focuses only on essential API behaviors ** resource interactions ** while everything else can be modular.&lt;/p&gt;
&lt;h2&gt;
  
  
  Performance
&lt;/h2&gt;

&lt;p&gt;Performance matters. Does all this flexibility come with a cost?&lt;br&gt;
Absolutely not. Modularity increases performance by removing unused plugins that were previously tightly coupled to the core.&lt;/p&gt;
&lt;h3&gt;
  
  
  Benchmark
&lt;/h3&gt;

&lt;p&gt;GoREST includes a built‑in benchmarking plugin. Simply run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make benchmark
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Below is the output, executed locally on my laptop with the following&lt;br&gt;
specs, while running Debian, GNOME Shell, and several applications like a browser, IDE, and Insomnia:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz
16GB RAM
&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;&lt;span class="o"&gt;=========================================&lt;/span&gt;
      API Performance Benchmark
&lt;span class="o"&gt;=========================================&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;INFO] Setting up benchmark table...
&lt;span class="o"&gt;[&lt;/span&gt;INFO] Generating &lt;span class="nb"&gt;test &lt;/span&gt;data...
&lt;span class="o"&gt;[&lt;/span&gt;INFO] Generating models and resources...
&lt;span class="o"&gt;[&lt;/span&gt;INFO] Building API server...
&lt;span class="o"&gt;[&lt;/span&gt;INFO] Starting API server...
&lt;span class="o"&gt;[&lt;/span&gt;INFO] Waiting &lt;span class="k"&gt;for &lt;/span&gt;server to be ready...
&lt;span class="o"&gt;[&lt;/span&gt;INFO] Server is ready

&lt;span class="o"&gt;=========================================&lt;/span&gt;
       Running Benchmarks
&lt;span class="o"&gt;=========================================&lt;/span&gt;

Benchmarking GET /benchmarkitems?limit&lt;span class="o"&gt;=&lt;/span&gt;10
─────────────────────────────────────────────────────────────────
  Concurrency: 1   | RPS:       1 | p50: 871.26µs | p95: 1.433519ms | p99: 1.433519ms | Errors: 0 | Total: 5
  Concurrency: 10  | RPS:      10 | p50: 821.151µs | p95: 1.289257ms | p99: 1.597766ms | Errors: 0 | Total: 50
  Concurrency: 50  | RPS:      50 | p50: 817.202µs | p95: 1.569706ms | p99: 2.275461ms | Errors: 0 | Total: 250

Benchmarking GET /benchmarkitems?limit&lt;span class="o"&gt;=&lt;/span&gt;100
─────────────────────────────────────────────────────────────────
  Concurrency: 1   | RPS:       1 | p50: 1.965903ms | p95: 2.383311ms | p99: 2.383311ms | Errors: 0 | Total: 5
  Concurrency: 10  | RPS:      10 | p50: 1.872685ms | p95: 2.449289ms | p99: 2.95571ms | Errors: 0 | Total: 50
  Concurrency: 50  | RPS:      50 | p50: 1.706943ms | p95: 2.682877ms | p99: 5.437662ms | Errors: 0 | Total: 250

Benchmarking GET /benchmarkitems?limit&lt;span class="o"&gt;=&lt;/span&gt;1000
─────────────────────────────────────────────────────────────────
  Concurrency: 1   | RPS:       1 | p50: 11.371178ms | p95: 14.578225ms | p99: 14.578225ms | Errors: 0 | Total: 5
  Concurrency: 10  | RPS:      10 | p50: 11.772652ms | p95: 13.573469ms | p99: 14.813448ms | Errors: 0 | Total: 50
  Concurrency: 50  | RPS:      50 | p50: 11.403417ms | p95: 13.064978ms | p99: 16.583982ms | Errors: 0 | Total: 250

&lt;span class="o"&gt;=========================================&lt;/span&gt;
       Benchmark Complete
&lt;span class="o"&gt;=========================================&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;INFO] Cleaning up...
&lt;span class="o"&gt;[&lt;/span&gt;INFO] Restoring original schema...
&lt;span class="o"&gt;[&lt;/span&gt;INFO] Cleanup &lt;span class="nb"&gt;complete&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Performance Summary
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Small Page Size (limit=10)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Concurrency 1&lt;/strong&gt;: p50 871µs, p95 1.4ms, p99 1.4ms&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concurrency 10&lt;/strong&gt;: p50 821µs, p95 1.3ms, p99 1.6ms&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concurrency 50&lt;/strong&gt;: p50 817µs, p95 1.6ms, p99 2.3ms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Excellent&lt;/strong&gt; --- sub‑millisecond median response times even under load.&lt;/p&gt;

&lt;h4&gt;
  
  
  Medium Page Size (limit=100)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Concurrency 1&lt;/strong&gt;: p50 1.97ms, p95 2.4ms, p99 2.4ms&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concurrency 10&lt;/strong&gt;: p50 1.87ms, p95 2.4ms, p99 3.0ms&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concurrency 50&lt;/strong&gt;: p50 1.71ms, p95 2.7ms, p99 5.4ms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Very good&lt;/strong&gt; --- under 2ms median for 100 items.&lt;/p&gt;

&lt;h4&gt;
  
  
  Large Page Size (limit=1000)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Concurrency 1&lt;/strong&gt;: p50 11.4ms, p95 14.6ms, p99 14.6ms&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concurrency 10&lt;/strong&gt;: p50 11.8ms, p95 13.6ms, p99 14.8ms&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concurrency 50&lt;/strong&gt;: p50 11.4ms, p95 13.1ms, p99 16.6ms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Good&lt;/strong&gt; --- around 11ms for 1000 items is reasonable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Observations
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Zero errors&lt;/strong&gt; across all tests&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Excellent scalability&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consistently low latency&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Strong connection pooling&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Linear performance growth&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Security
&lt;/h2&gt;

&lt;p&gt;Thanks to the core security plugin, building a REST API with GoREST&lt;br&gt;
gives you a highly secure framework.&lt;/p&gt;

&lt;p&gt;We performed a security audit and penetration test using Claude AI from&lt;br&gt;
Anthropic. It checked for OWASP Top 10 vulnerabilities, test coverage,&lt;br&gt;
attack surface analysis, pentesting, and stress testing.&lt;/p&gt;

&lt;p&gt;Standard              Requirement                GoREST Status&lt;/p&gt;




&lt;p&gt;OWASP ASVS L2         Security headers           ✅ Complete&lt;br&gt;
  OWASP ASVS L2         TRACE disabled             ✅ Complete&lt;br&gt;
  OWASP ASVS L2         Rate limiting              ✅ Complete&lt;br&gt;
  OWASP ASVS L2         JWT security               ✅ Complete&lt;br&gt;
  OWASP ASVS L2         SQL injection prevention   ✅ Complete&lt;br&gt;
  OWASP ASVS L2         XSS prevention             ✅ Complete&lt;br&gt;
  OWASP ASVS L2         CSRF protection            ✅ N/A (API)&lt;br&gt;
  CIS Benchmarks        Secure headers             ✅ Complete&lt;br&gt;
  CIS Benchmarks        Method whitelisting        ✅ Complete&lt;br&gt;
  Mozilla Observatory   Security grade             ✅ A+&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Compliance Level:&lt;/strong&gt; OWASP ASVS Level 2 (Standard Web Applications)&lt;/p&gt;

&lt;h3&gt;
  
  
  Results
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Security Score:&lt;/strong&gt; 10/10&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vulnerabilities:&lt;/strong&gt; 0&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tests Passed:&lt;/strong&gt; 27/27&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production Status:&lt;/strong&gt; ✅ Approved&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GoREST is now &lt;strong&gt;production‑ready&lt;/strong&gt; with &lt;strong&gt;industry‑leading security&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Coming Next?
&lt;/h2&gt;

&lt;p&gt;Roadmap to the &lt;strong&gt;v1 milestone&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Plugins repository&lt;/li&gt;
&lt;li&gt;OAuth2 support&lt;/li&gt;
&lt;li&gt;gRPC plugin&lt;/li&gt;
&lt;li&gt;RBAC plugin&lt;/li&gt;
&lt;li&gt;Development profiler plugin&lt;/li&gt;
&lt;li&gt;Complete documentation&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Contribute
&lt;/h3&gt;

&lt;p&gt;Feel free to create new plugins and contribute to the library. Even a&lt;br&gt;
single ⭐ can motivate continued development toward the v1 milestone.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/nicolasbonnici/gorest/stargazers" rel="noopener noreferrer"&gt;Star the project&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/nicolasbonnici/gorest/fork" rel="noopener noreferrer"&gt;Fork the project&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/nicolasbonnici/gorest/issues" rel="noopener noreferrer"&gt;Report a bug&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/nicolasbonnici/gorest/issues" rel="noopener noreferrer"&gt;Suggest a feature&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/nicolasbonnici/gorest/pulls" rel="noopener noreferrer"&gt;Submit a pull request&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/nicolasbonnici/gorest/blob/trunk/CONTRIBUTING.md" rel="noopener noreferrer"&gt;Read contributing guidelines&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>tooling</category>
      <category>go</category>
      <category>opensource</category>
      <category>api</category>
    </item>
    <item>
      <title>Let's create a production grade REST API from an existing database or from scratch using Go in seconds</title>
      <dc:creator>Nicolas Bonnici</dc:creator>
      <pubDate>Sat, 15 Nov 2025 21:15:59 +0000</pubDate>
      <link>https://dev.to/nicolasbonnici/lets-create-a-production-grade-rest-api-from-an-existing-database-or-from-scratch-using-go-in-1c61</link>
      <guid>https://dev.to/nicolasbonnici/lets-create-a-production-grade-rest-api-from-an-existing-database-or-from-scratch-using-go-in-1c61</guid>
      <description>&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/nicolasbonnici/how-to-create-a-rest-api-in-seconds-59a4" class="crayons-story__hidden-navigation-link"&gt;How to create a REST API in seconds with Go&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/nicolasbonnici" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F30167%2F500aedaa-8525-4cde-9e66-e1a32f911940.jpeg" alt="nicolasbonnici profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/nicolasbonnici" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Nicolas Bonnici
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Nicolas Bonnici
                
              
              &lt;div id="story-author-preview-content-3024233" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/nicolasbonnici" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F30167%2F500aedaa-8525-4cde-9e66-e1a32f911940.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Nicolas Bonnici&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/nicolasbonnici/how-to-create-a-rest-api-in-seconds-59a4" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Nov 15 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/nicolasbonnici/how-to-create-a-rest-api-in-seconds-59a4" id="article-link-3024233"&gt;
          How to create a REST API in seconds with Go
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/howto"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;howto&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/api"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;api&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/rest"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;rest&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/go"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;go&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
            &lt;a href="https://dev.to/nicolasbonnici/how-to-create-a-rest-api-in-seconds-59a4#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            8 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;




</description>
      <category>howto</category>
      <category>api</category>
      <category>rest</category>
      <category>go</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Nicolas Bonnici</dc:creator>
      <pubDate>Sat, 15 Nov 2025 18:17:52 +0000</pubDate>
      <link>https://dev.to/nicolasbonnici/-5b8h</link>
      <guid>https://dev.to/nicolasbonnici/-5b8h</guid>
      <description>&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/nicolasbonnici/how-to-create-a-rest-api-in-seconds-59a4" class="crayons-story__hidden-navigation-link"&gt;How to create a REST API in seconds with Go&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/nicolasbonnici" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F30167%2F500aedaa-8525-4cde-9e66-e1a32f911940.jpeg" alt="nicolasbonnici profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/nicolasbonnici" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Nicolas Bonnici
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Nicolas Bonnici
                
              
              &lt;div id="story-author-preview-content-3024233" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/nicolasbonnici" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F30167%2F500aedaa-8525-4cde-9e66-e1a32f911940.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Nicolas Bonnici&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/nicolasbonnici/how-to-create-a-rest-api-in-seconds-59a4" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Nov 15 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/nicolasbonnici/how-to-create-a-rest-api-in-seconds-59a4" id="article-link-3024233"&gt;
          How to create a REST API in seconds with Go
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/howto"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;howto&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/api"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;api&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/rest"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;rest&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/go"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;go&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
            &lt;a href="https://dev.to/nicolasbonnici/how-to-create-a-rest-api-in-seconds-59a4#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            8 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;




</description>
      <category>howto</category>
      <category>api</category>
      <category>rest</category>
      <category>go</category>
    </item>
    <item>
      <title>How to create a REST API in seconds with Go</title>
      <dc:creator>Nicolas Bonnici</dc:creator>
      <pubDate>Sat, 15 Nov 2025 13:15:59 +0000</pubDate>
      <link>https://dev.to/nicolasbonnici/how-to-create-a-rest-api-in-seconds-59a4</link>
      <guid>https://dev.to/nicolasbonnici/how-to-create-a-rest-api-in-seconds-59a4</guid>
      <description>&lt;h2&gt;
  
  
  Quick Start (30 Seconds to Running API)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Clone and setup&lt;/span&gt;
git clone https://github.com/nicolasbonnici/gorest.git
&lt;span class="nb"&gt;cd &lt;/span&gt;examples/basic-api
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.dist .env
&lt;span class="c"&gt;# Start database and generate API&lt;/span&gt;
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt; 
make generate
make run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your API is now running at &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; with interactive documentation at &lt;a href="http://localhost:3000/openapi" rel="noopener noreferrer"&gt;http://localhost:3000/openapi&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;More informations on &lt;a href="https://github.com/nicolasbonnici/gorest/tree/trunk/examples/basic-api" rel="noopener noreferrer"&gt;the basic-api example README&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Start with Your Data Model
&lt;/h2&gt;

&lt;p&gt;Define your business objects and their relationships using standard SQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- schema.sql&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;gen_random_uuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;firstname&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;lastname&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&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="k"&gt;WITH&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;todo&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;gen_random_uuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;users&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;title&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&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="k"&gt;WITH&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two tables with a foreign key relationship. That's our entire starting point in this post.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Gets Generated Automatically
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Complete REST API Endpoints
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;For Users:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;POST /users&lt;/code&gt; - Create user&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /users&lt;/code&gt; - List users (with pagination, filtering, ordering)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /users/{id}&lt;/code&gt; - Get user&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PUT /users/{id}&lt;/code&gt; - Update user&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DELETE /users/{id}&lt;/code&gt; - Delete user&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;For Todos:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;POST /todos&lt;/code&gt; - Create todo&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /todos&lt;/code&gt; - List todos&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /todos/{id}&lt;/code&gt; - Get todo&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PUT /todos/{id}&lt;/code&gt; - Update todo&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DELETE /todos/{id}&lt;/code&gt; - Delete todo&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Code Structure
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gorest/
├── cmd/
│   └── server/
│       └── main.go      # Application entry point
├── internal/
│   ├── models/          # Database models
│   │   ├── user.go
│   │   └── todo.go
│   ├── dto/             # Data Transfer Objects
│   │   ├── user_dto.go
│   │   └── todo_dto.go
│   ├── hooks/           # Your business logic goes here
│   │   ├── user.go
│   │   └── todo.go
│   └── resources/       # REST resource configurations
│       ├── user.go
│       └── todo.go
└── openapi.json         # API documentation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key point:&lt;/strong&gt; Generated files (models, DTOs, resources) are recreated when you run &lt;code&gt;generate&lt;/code&gt;. Your custom business logic goes in &lt;strong&gt;hooks/&lt;/strong&gt;, which are never overwritten.&lt;/p&gt;

&lt;h3&gt;
  
  
  Interactive API Documentation
&lt;/h3&gt;

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

&lt;p&gt;Access at &lt;code&gt;http://localhost:3000/openapi&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Browse all endpoints with request/response schemas&lt;/li&gt;
&lt;li&gt;Test API calls directly from browser&lt;/li&gt;
&lt;li&gt;Copy code examples in multiple languages&lt;/li&gt;
&lt;li&gt;JSON spec available at &lt;code&gt;/openapi.json&lt;/code&gt; for Postman, Insomnia, or client SDK generation&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Authentication &amp;amp; Security
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Built-in JWT Authentication
&lt;/h3&gt;

&lt;p&gt;Registration and login endpoints are automatically generated:&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="c"&gt;# Register a new user&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:3000/register &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;'{
    "firstname": "John",
    "lastname": "Doe",
    "email": "john@example.com",
    "password": "secure123"
  }'&lt;/span&gt;

&lt;span class="c"&gt;# Login and get JWT token&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:3000/login &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;'{
    "email": "john@example.com",
    "password": "secure123"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Response:&lt;/strong&gt;&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;"token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"user"&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"abc-123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"firstname"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lastname"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Doe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"john@example.com"&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="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;All protected endpoints automatically require the JWT token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer YOUR_TOKEN"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     http://localhost:3000/todos
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Securing Sensitive Data with DTOs
&lt;/h3&gt;

&lt;p&gt;Control exactly what data is exposed through Data Transfer Objects:&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="c"&gt;// Database model - contains ALL fields&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Todo&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;gorest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;
    &lt;span class="n"&gt;ID&lt;/span&gt;          &lt;span class="kt"&gt;string&lt;/span&gt;    &lt;span class="s"&gt;`json:"id" gorm:"primaryKey"`&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;Password&lt;/span&gt;    &lt;span class="kt"&gt;string&lt;/span&gt;    &lt;span class="s"&gt;`json:"-"`&lt;/span&gt; &lt;span class="c"&gt;// Never serialized&lt;/span&gt;
    &lt;span class="n"&gt;UserID&lt;/span&gt;      &lt;span class="kt"&gt;string&lt;/span&gt;    &lt;span class="s"&gt;`json:"user_id"`&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="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// What clients see when reading&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;TodoReadDTO&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;string&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;UserID&lt;/span&gt;    &lt;span class="kt"&gt;string&lt;/span&gt;    &lt;span class="s"&gt;`json:"user_id"`&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="c"&gt;// Password is completely absent - type-safe&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// What clients can send when creating&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;TodoWriteDTO&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;Title&lt;/span&gt;    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"title" validate:"required,min=3"`&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;Password&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"password" validate:"required,min=8"`&lt;/span&gt;
    &lt;span class="c"&gt;// UserID is absent - auto-populated server-side&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;Benefits:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Type-safe: Compiler prevents exposing sensitive fields&lt;/li&gt;
&lt;li&gt;Flexible: Different DTOs per operation (list, detail, create, update)&lt;/li&gt;
&lt;li&gt;Self-documenting: API contract is explicit&lt;/li&gt;
&lt;li&gt;Zero overhead: No runtime transformation&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Advanced Querying Out of the Box
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Filtering
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;GET /todos?title[like]&lt;span class="o"&gt;=&lt;/span&gt;meeting              &lt;span class="c"&gt;# Pattern matching&lt;/span&gt;
GET /todos?priority[gte]&lt;span class="o"&gt;=&lt;/span&gt;5                  &lt;span class="c"&gt;# Greater than or equal&lt;/span&gt;
GET /todos?status[]&lt;span class="o"&gt;=&lt;/span&gt;active&amp;amp;status[]&lt;span class="o"&gt;=&lt;/span&gt;pending &lt;span class="c"&gt;# Multiple values (OR)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Ordering
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;GET /todos?order[priority]&lt;span class="o"&gt;=&lt;/span&gt;desc&amp;amp;order[created_at]&lt;span class="o"&gt;=&lt;/span&gt;asc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pagination
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;GET /todos?page&lt;span class="o"&gt;=&lt;/span&gt;2&amp;amp;limit&lt;span class="o"&gt;=&lt;/span&gt;20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Relationship Expansion
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Automatically expand foreign keys&lt;/span&gt;
GET /todos?expand&lt;span class="o"&gt;=&lt;/span&gt;user

&lt;span class="c"&gt;# Response includes nested user data&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"id"&lt;/span&gt;: &lt;span class="s2"&gt;"todo-123"&lt;/span&gt;,
  &lt;span class="s2"&gt;"title"&lt;/span&gt;: &lt;span class="s2"&gt;"Buy groceries"&lt;/span&gt;,
  &lt;span class="s2"&gt;"user"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"id"&lt;/span&gt;: &lt;span class="s2"&gt;"user-456"&lt;/span&gt;,
    &lt;span class="s2"&gt;"firstname"&lt;/span&gt;: &lt;span class="s2"&gt;"John"&lt;/span&gt;,
    &lt;span class="s2"&gt;"email"&lt;/span&gt;: &lt;span class="s2"&gt;"john@example.com"&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Adding Business Logic With Hooks
&lt;/h2&gt;

&lt;p&gt;Hooks execute at key moments in the request lifecycle without modifying generated code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Available Hooks
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;StateProcessor&lt;/code&gt; - Validate/transform data before Create/Update/Delete&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SQLQueryOverride&lt;/code&gt; - Replace default queries with custom SQL&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SQLQueryListener&lt;/code&gt; - Intercept queries before/after execution&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Authorize&lt;/code&gt; - Add authentication/authorization logic&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Example 1: Validation and Auto-Population
&lt;/h3&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;TodoHooks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;StateProcessor&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;operation&lt;/span&gt; &lt;span class="n"&gt;hooks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Operation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;todo&lt;/span&gt; &lt;span class="o"&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;Todo&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;switch&lt;/span&gt; &lt;span class="n"&gt;operation&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;hooks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OperationCreate&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="c"&gt;// Validate&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;todo&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;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&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="k"&gt;if&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;todo&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="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;3&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;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"title must be at least 3 characters"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c"&gt;// Auto-populate user_id from JWT authentication&lt;/span&gt;
        &lt;span class="c"&gt;// This happens server-side - clients cannot spoof it&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;userID&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;userID&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;todo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UserID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;userID&lt;/span&gt;&lt;span class="o"&gt;.&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="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;nil&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;Result:&lt;/strong&gt; Users can't assign todos to each other, and data is validated before reaching the database.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 2: Custom Query Logic
&lt;/h3&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;TodoHooks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;SQLQueryOverride&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;operation&lt;/span&gt; &lt;span class="n"&gt;hooks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Operation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;sqlbuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SelectQuery&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;operation&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;hooks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OperationList&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// Only show the authenticated user's incomplete todos&lt;/span&gt;
        &lt;span class="n"&gt;userID&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;sqlbuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
            &lt;span class="n"&gt;Select&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="s"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
            &lt;span class="n"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"todo"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
            &lt;span class="n"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"completed = FALSE AND user_id = ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userID&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;query&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;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&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;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Every &lt;code&gt;GET /todos&lt;/code&gt; automatically filters to the authenticated user's incomplete tasks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 3: Authorization Control
&lt;/h3&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;TodoHooks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Authorize&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;operation&lt;/span&gt; &lt;span class="n"&gt;hooks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Operation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;any&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;userID&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"user_id"&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;userID&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;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"authentication required"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Custom permission checks&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;hooks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OperationDelete&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// Only admins can delete&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;isAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userID&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;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"insufficient permissions"&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;return&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;
  
  
  Why Go and GoREST?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Language and Framework Choice
&lt;/h3&gt;

&lt;p&gt;To build a REST API, you need both a language and framework. We chose &lt;strong&gt;Go&lt;/strong&gt; with &lt;strong&gt;GoREST&lt;/strong&gt; based on these technical advantages:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Performance Benchmarks&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Response times: Sub-millisecond under normal load&lt;/li&gt;
&lt;li&gt;Concurrent requests: Handle thousands simultaneously via goroutines&lt;/li&gt;
&lt;li&gt;Startup time: ~1ms (vs. 1-2 seconds for interpreted languages)&lt;/li&gt;
&lt;li&gt;Compilation: Direct to machine code with no runtime overhead&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Development Experience&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clean syntax and strong typing reduce bugs&lt;/li&gt;
&lt;li&gt;Built-in testing frameworks and profiling tools&lt;/li&gt;
&lt;li&gt;Excellent standard library for HTTP, JSON, and database operations&lt;/li&gt;
&lt;li&gt;Simple concurrency with goroutines and channels&lt;/li&gt;
&lt;li&gt;Code remains maintainable months later&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Production Benefits&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single binary deployment (detailed in next section)&lt;/li&gt;
&lt;li&gt;Cross-platform compilation built-in&lt;/li&gt;
&lt;li&gt;No dependency version conflicts&lt;/li&gt;
&lt;li&gt;Trivial CI/CD pipelines&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  GoREST Framework Goals
&lt;/h3&gt;

&lt;p&gt;GoREST focuses on eliminating boilerplate while maintaining flexibility:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Code Generation&lt;/strong&gt; - Define your schema once, get a complete API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistency&lt;/strong&gt; - Predictable routes, uniform behavior, automatic validation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extensibility&lt;/strong&gt; - Add business logic via hooks without modifying generated code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance&lt;/strong&gt; - Leverage Go's speed and efficiency&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database Flexibility&lt;/strong&gt; - Works with PostgreSQL, MySQL, SQLite&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Deployment: The Single Binary Advantage
&lt;/h2&gt;

&lt;p&gt;One of Go's most powerful features is that your entire application compiles into &lt;strong&gt;a single, self-contained executable&lt;/strong&gt;. This fundamentally changes how you deploy and manage applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  Production Deployment Scenarios
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;GoREST deployment:&lt;/strong&gt;&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="c"&gt;# Build once&lt;/span&gt;
make build

&lt;span class="c"&gt;# Deploy&lt;/span&gt;
scp ./bin/gorest user@server:/opt/gorest/
./gorest

&lt;span class="c"&gt;# That's it. No dependencies. No runtime.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  1. Bare Metal / VM with systemd
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[Unit]&lt;/span&gt;
&lt;span class="py"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;GoREST API Server&lt;/span&gt;
&lt;span class="py"&gt;After&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;network.target postgresql.service&lt;/span&gt;

&lt;span class="nn"&gt;[Service]&lt;/span&gt;
&lt;span class="py"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;simple&lt;/span&gt;
&lt;span class="py"&gt;User&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;api&lt;/span&gt;
&lt;span class="py"&gt;WorkingDirectory&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/opt/gorest&lt;/span&gt;
&lt;span class="py"&gt;EnvironmentFile&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/opt/gorest/.env&lt;/span&gt;
&lt;span class="py"&gt;ExecStart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/opt/gorest/bin/gorest&lt;/span&gt;
&lt;span class="py"&gt;Restart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;on-failure&lt;/span&gt;
&lt;span class="py"&gt;RestartSec&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;5s&lt;/span&gt;

&lt;span class="nn"&gt;[Install]&lt;/span&gt;
&lt;span class="py"&gt;WantedBy&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;multi-user.target&lt;/span&gt;
&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;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;gorest
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start gorest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. Docker (Multi-stage Build)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Build stage&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;golang:1.22&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;make build

&lt;span class="c"&gt;# Final stage - just the binary&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; scratch&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/bin/gorest /gorest&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["/gorest"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; 10-20MB Docker image (vs. 500MB+ for Node.js/Python apps)&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Kubernetes
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gorest-api&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gorest&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;your-registry/gorest:latest&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3000&lt;/span&gt;
        &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;20Mi"&lt;/span&gt;
            &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;100m"&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DATABASE_URL&lt;/span&gt;
          &lt;span class="na"&gt;valueFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;secretKeyRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;db-secret&lt;/span&gt;
              &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;url&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each pod starts in milliseconds and uses minimal resources.&lt;/p&gt;

&lt;h4&gt;
  
  
  4. AWS Lambda (Custom Runtime)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Package as custom runtime&lt;/span&gt;
zip &lt;span class="k"&gt;function&lt;/span&gt;.zip gorest
aws lambda create-function &lt;span class="nt"&gt;--runtime&lt;/span&gt; provided.al2

&lt;span class="c"&gt;# Cold start: ~100ms (vs. seconds for interpreted languages)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  5. Edge &amp;amp; Serverless Platforms
&lt;/h4&gt;

&lt;p&gt;Deploy to Cloudflare Workers, Fly.io, or any edge platform. Small binary size and instant startup are ideal for serverless environments.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cross-Platform Compilation
&lt;/h3&gt;

&lt;p&gt;Build for any platform from any platform:&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="c"&gt;# Build for Linux from your Mac&lt;/span&gt;
&lt;span class="nv"&gt;GOOS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;linux &lt;span class="nv"&gt;GOARCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;amd64 make build

&lt;span class="c"&gt;# Build for Windows&lt;/span&gt;
&lt;span class="nv"&gt;GOOS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;windows &lt;span class="nv"&gt;GOARCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;amd64 make build

&lt;span class="c"&gt;# Build for ARM (Raspberry Pi, M1 Mac)&lt;/span&gt;
&lt;span class="nv"&gt;GOOS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;linux &lt;span class="nv"&gt;GOARCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;arm64 make build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  CI/CD Pipeline Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# GitHub Actions&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;make build&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Test&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./bin/gorest --version&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scp ./bin/gorest server:/app/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No dependency caching, no runtime installation, no complex build pipelines.&lt;/p&gt;

&lt;h3&gt;
  
  
  Version Management
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# No runtime version conflicts&lt;/span&gt;
&lt;span class="c"&gt;# Each binary is independent and portable&lt;/span&gt;
/opt/api-v1/gorest  &lt;span class="c"&gt;# Old version&lt;/span&gt;
/opt/api-v2/gorest  &lt;span class="c"&gt;# New version&lt;/span&gt;

&lt;span class="c"&gt;# Instant rollback&lt;/span&gt;
&lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-sf&lt;/span&gt; /opt/api-v1/gorest /usr/local/bin/gorest

&lt;span class="c"&gt;# Instant upgrade&lt;/span&gt;
&lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-sf&lt;/span&gt; /opt/api-v2/gorest /usr/local/bin/gorest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why This Matters
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zero Dependencies:&lt;/strong&gt; No runtime installation, no package managers, no dependency hell&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistent Behavior:&lt;/strong&gt; Same binary runs identically across dev, staging, and production&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduced Attack Surface:&lt;/strong&gt; No runtime vulnerabilities, minimal OS dependencies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost Efficiency:&lt;/strong&gt; Smaller instances, cheaper hosting, more instances per server&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Faster Deployment:&lt;/strong&gt; Copy one file, run it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simplified Operations:&lt;/strong&gt; No dependency updates, no runtime patches&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Complete Example: From Schema to Production
&lt;/h2&gt;

&lt;p&gt;Let's walk through a complete workflow:&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Define Schema
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;todo&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;gen_random_uuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;users&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;created_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&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="k"&gt;WITH&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Generate and Build
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make generate  &lt;span class="c"&gt;# Generates models, DTOs, resources, OpenAPI docs&lt;/span&gt;
make build     &lt;span class="c"&gt;# Compiles to single binary in ./bin/gorest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Deploy
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Copy to server&lt;/span&gt;
scp ./bin/gorest user@server:/opt/api/

&lt;span class="c"&gt;# Run&lt;/span&gt;
ssh user@server
&lt;span class="nb"&gt;cd&lt;/span&gt; /opt/api
./gorest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Use Your API
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Register and login (see Authentication section for details)&lt;/span&gt;
&lt;span class="nv"&gt;TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-jwt-token-here"&lt;/span&gt;

&lt;span class="c"&gt;# Create todo&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://your-server:3000/todos &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &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":"Buy groceries","content":"Milk, eggs, bread"}'&lt;/span&gt;

&lt;span class="c"&gt;# List todos with filtering and ordering&lt;/span&gt;
curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="s2"&gt;"http://your-server:3000/todos?title[like]=groceries&amp;amp;order[created_at]=desc"&lt;/span&gt;

&lt;span class="c"&gt;# Get specific todo&lt;/span&gt;
curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="s2"&gt;"http://your-server:3000/todos/{id}"&lt;/span&gt;

&lt;span class="c"&gt;# Update todo&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; PUT http://your-server:3000/todos/&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &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":"Buy groceries ASAP"}'&lt;/span&gt;

&lt;span class="c"&gt;# Delete todo&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; DELETE http://your-server:3000/todos/&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;View interactive documentation at &lt;code&gt;http://your-server:3000/openapi&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Ready to try GoREST?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/nicolasbonnici/gorest/tree/main/examples/basic-api" rel="noopener noreferrer"&gt;View the basic-api example&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Explore the full source code:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/nicolasbonnici/gorest/" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Found this helpful?&lt;/strong&gt; Please &lt;a href="https://github.com/nicolasbonnici/gorest/stargazers" rel="noopener noreferrer"&gt;star the project&lt;/a&gt; to show your support!&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;GoREST transforms your database schema into a production-ready REST API compiled as a single executable binary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What you get:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Complete CRUD operations for all tables&lt;/li&gt;
&lt;li&gt;JWT authentication with register/login endpoints&lt;/li&gt;
&lt;li&gt;Advanced filtering, ordering, pagination, and relationship expansion&lt;/li&gt;
&lt;li&gt;Interactive OpenAPI documentation&lt;/li&gt;
&lt;li&gt;Type-safe DTOs for security and flexibility&lt;/li&gt;
&lt;li&gt;Extensible hooks system for business logic&lt;/li&gt;
&lt;li&gt;Production-ready error handling and validation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Deployment benefits:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single self-contained binary with no dependencies&lt;/li&gt;
&lt;li&gt;Deploy anywhere: bare metal, Docker, Kubernetes, serverless, edge&lt;/li&gt;
&lt;li&gt;10-20MB total size with sub-millisecond response times&lt;/li&gt;
&lt;li&gt;Instant startup and minimal resource footprint&lt;/li&gt;
&lt;li&gt;Simple CI/CD and version management&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your database schema becomes a production-grade REST API. Define your data model, run &lt;code&gt;make generate &amp;amp;&amp;amp; make build&lt;/code&gt;, and ship anywhere.&lt;/p&gt;




&lt;h2&gt;
  
  
  Contribute to GoREST
&lt;/h2&gt;

&lt;p&gt;All contributions welcome - code, documentation, ideas, suggestions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/nicolasbonnici/gorest/stargazers" rel="noopener noreferrer"&gt;Star the project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/nicolasbonnici/gorest/fork" rel="noopener noreferrer"&gt;Fork the project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/nicolasbonnici/gorest/issues" rel="noopener noreferrer"&gt;Report a bug&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/nicolasbonnici/gorest/issues" rel="noopener noreferrer"&gt;Suggest a feature&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/nicolasbonnici/gorest/pulls" rel="noopener noreferrer"&gt;Submit a pull request&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/nicolasbonnici/gorest/blob/trunk/CONTRIBUTING.md" rel="noopener noreferrer"&gt;Read contributing guidelines&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks for reading! 🚀&lt;/p&gt;

</description>
      <category>howto</category>
      <category>api</category>
      <category>rest</category>
      <category>go</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Nicolas Bonnici</dc:creator>
      <pubDate>Wed, 12 Nov 2025 23:13:52 +0000</pubDate>
      <link>https://dev.to/nicolasbonnici/-j5p</link>
      <guid>https://dev.to/nicolasbonnici/-j5p</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/nicolasbonnici" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F30167%2F500aedaa-8525-4cde-9e66-e1a32f911940.jpeg" alt="nicolasbonnici"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/nicolasbonnici/gorest-is-dead-1ap9" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;GoREST is dead...&lt;/h2&gt;
      &lt;h3&gt;Nicolas Bonnici ・ Nov 12&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#go&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#sql&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>go</category>
      <category>sql</category>
    </item>
    <item>
      <title>GoREST is dead...</title>
      <dc:creator>Nicolas Bonnici</dc:creator>
      <pubDate>Wed, 12 Nov 2025 21:31:11 +0000</pubDate>
      <link>https://dev.to/nicolasbonnici/gorest-is-dead-1ap9</link>
      <guid>https://dev.to/nicolasbonnici/gorest-is-dead-1ap9</guid>
      <description>&lt;h1&gt;
  
  
  This Is the End
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxtp30uwz9udncgq7qjry.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxtp30uwz9udncgq7qjry.webp" alt="Jim Morrison from The Doors band, singing this is the end" width="500" height="368"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Recently, &lt;a href="https://github.com/nicolasbonnici/gorest" rel="noopener noreferrer"&gt;I shared a project skeleton&lt;/a&gt; and &lt;a href="https://dev.to/nicolasbonnici/from-a-database-to-a-rest-api-3cgf"&gt;a post&lt;/a&gt; that allows you to easily turn almost any relational database schema into a REST API.&lt;/p&gt;

&lt;p&gt;It took me about a month to develop the first release, just a few hours at night when the kids were asleep and during weekends, since I’m currently working full-time.&lt;/p&gt;

&lt;p&gt;The feedback was mixed. Some people liked it, others simply hated it. At some point on Reddit, people even downvoted the project without explaining why. Fortunately, some benevolent users also took the time to tell me what they liked or didn’t about the project.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Need and the Vision Behind the First Release
&lt;/h2&gt;

&lt;p&gt;My initial goal was to solve a problem I had: reusing an existing, functional relational data model and its data from a monolithic application, and turning it into a &lt;strong&gt;headless architecture&lt;/strong&gt;, where the backend is a REST API, and the resources are consumed by both a web app and an admin portal using Nuxt UI and Flutter respectively.&lt;/p&gt;

&lt;p&gt;After benchmarking the first release candidate, I was impressed by the performance and decided to share the code under an MIT license for anyone facing the same challenge.&lt;/p&gt;

&lt;p&gt;I’m not a Go expert. I recently learned the basics through &lt;a href="https://grok.com/" rel="noopener noreferrer"&gt;Grok&lt;/a&gt;, and this was my first real project involving more advanced use cases. I managed to build something genuinely useful, and overall, the performance was quite decent, so why not share it?&lt;/p&gt;




&lt;h2&gt;
  
  
  So, Why Kill It So Fast?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbcvsraiess514w1m4ifr.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbcvsraiess514w1m4ifr.webp" alt="Scary movie meme, I see dead people" width="480" height="206"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Programming languages are often more than mere instruction sets for machines. They embody implicit rules, conventions, structural philosophies, and specific subtleties. Go is no exception.&lt;/p&gt;

&lt;p&gt;The first and most important issue with GoREST v0.1 wasn’t specific to Go: &lt;strong&gt;separation of concerns&lt;/strong&gt;. Nobody wants to mix their business logic into a skeleton project unless that skeleton is meant to evolve directly into their final product.&lt;/p&gt;

&lt;p&gt;Then there’s &lt;strong&gt;maintainability&lt;/strong&gt;. Having a boilerplate that requires regular updates quickly becomes a burden and makes long-term maintenance painful.&lt;/p&gt;

&lt;p&gt;And finally, &lt;strong&gt;flexibility&lt;/strong&gt;. I wanted to offer multiple ways to use and implement GoREST, either as a library integrated into an existing project or as the foundation of a brand-new one. There are thousands of ways to code business logic, and I don’t want to impose just one.&lt;/p&gt;

&lt;p&gt;So, there’s no reason to maintain this skeleton anymore.&lt;br&gt;&lt;br&gt;
Let’s cut off its head (no headless architecture related nerdy joke intended, literally).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpokyjy5itcxx8fok2y9y.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpokyjy5itcxx8fok2y9y.webp" alt="Conan The Barbarian movie scene" width="319" height="177"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  RIP GoREST v0.1.0
&lt;/h2&gt;

&lt;p&gt;Long live &lt;strong&gt;&lt;a href="https://github.com/nicolasbonnici/gorest/releases/tag/v0.2.0" rel="noopener noreferrer"&gt;GoREST v0.2.0&lt;/a&gt;&lt;/strong&gt;!  &lt;/p&gt;

&lt;p&gt;Yup, from the fresh ashes of GoREST v0.1 comes a complete rewrite as a &lt;strong&gt;library&lt;/strong&gt; and &lt;strong&gt;toolbox&lt;/strong&gt;. It aims to help you quickly scaffold your business objects and write your business logic with ease.&lt;/p&gt;

&lt;p&gt;A new release marks a new chapter for GoREST. Yes, I know this is a total breaking change and should be a major version, but since no one is using it yet except me, and we’re still far from version v1.0.0, there’s no harm in making it a minor upgrade.&lt;/p&gt;

&lt;p&gt;In my opinion, &lt;a href="https://github.com/nicolasbonnici/gorest/" rel="noopener noreferrer"&gt;this new v0.2 release&lt;/a&gt; is a healthy foundation to iterate from. It keeps the same features and benefits as the previous version but is now decoupled into a library of Go modules and useful commands.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fege78ipvxwmc6b06tbqs.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fege78ipvxwmc6b06tbqs.webp" alt="Frankenstein movie scene, it's alive" width="245" height="240"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Road to v1.0.0
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqkoods35x2wv5fen7b1i.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqkoods35x2wv5fen7b1i.webp" alt="An old Amiga racing game called Lotus Turbo Challenge" width="641" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here’s what I want to accomplish before releasing the first stable, long-term support version:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Better performance
&lt;/li&gt;
&lt;li&gt;A fully modular middleware layer with install/uninstall hooks
&lt;/li&gt;
&lt;li&gt;100% test coverage (currently around 80%)
&lt;/li&gt;
&lt;li&gt;gRPC support
&lt;/li&gt;
&lt;li&gt;Support for more database engines
&lt;/li&gt;
&lt;li&gt;Scaffolded clients
&lt;/li&gt;
&lt;li&gt;Some kickass new features I haven’t even thought of yet...&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Contribute
&lt;/h2&gt;

&lt;p&gt;Wanna join the adventure and contribute to the project?  &lt;/p&gt;

&lt;p&gt;All kinds of contributions are welcome, even just ideas or suggestions.&lt;/p&gt;

&lt;p&gt;Read the &lt;a href="https://github.com/nicolasbonnici/gorest/blob/trunk/CONTRIBUTING.md" rel="noopener noreferrer"&gt;contributing section&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can &lt;a href="https://github.com/nicolasbonnici/gorest/fork" rel="noopener noreferrer"&gt;fork it&lt;/a&gt; and/or &lt;a href="https://github.com/nicolasbonnici/gorest/stargazers" rel="noopener noreferrer"&gt;star the project&lt;/a&gt; if you like it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Documentation
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://pkg.go.dev/github.com/nicolasbonnici/gorest" rel="noopener noreferrer"&gt;Full documention is available on pkg.go&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thanks for reading.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsz7ab4q2empepsuks9b2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsz7ab4q2empepsuks9b2.png" alt="Until next time, Skeletor meme" width="257" height="196"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>sql</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Nicolas Bonnici</dc:creator>
      <pubDate>Thu, 06 Nov 2025 09:41:25 +0000</pubDate>
      <link>https://dev.to/nicolasbonnici/-273g</link>
      <guid>https://dev.to/nicolasbonnici/-273g</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/nicolasbonnici" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F30167%2F500aedaa-8525-4cde-9e66-e1a32f911940.jpeg" alt="nicolasbonnici"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/nicolasbonnici/from-a-database-to-a-rest-api-3cgf" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;GoREST turn any database to a production grade REST API&lt;/h2&gt;
      &lt;h3&gt;Nicolas Bonnici ・ Nov 5&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#go&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#rest&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#sql&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#gorest&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>go</category>
      <category>rest</category>
      <category>sql</category>
      <category>gorest</category>
    </item>
    <item>
      <title>GoREST turn any database to a production grade REST API</title>
      <dc:creator>Nicolas Bonnici</dc:creator>
      <pubDate>Wed, 05 Nov 2025 21:29:37 +0000</pubDate>
      <link>https://dev.to/nicolasbonnici/from-a-database-to-a-rest-api-3cgf</link>
      <guid>https://dev.to/nicolasbonnici/from-a-database-to-a-rest-api-3cgf</guid>
      <description>&lt;blockquote&gt;
&lt;h1&gt;
  
  
  GoREST v0.1.0
&lt;/h1&gt;
&lt;h2&gt;
  
  
  🎉 Introducing GoREST: The First Open Source Release!
&lt;/h2&gt;

&lt;p&gt;I'm very proud to announce the &lt;strong&gt;first release of GoREST (v0.1.0)&lt;/strong&gt; - This project aim to easily transform any relational database to a production grade REST API and it's written in Go! After months of development, testing, and refinement, I'm proud to release it under the &lt;strong&gt;MIT license&lt;/strong&gt;, making it completely free and open source for everyone.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/nicolasbonnici/gorest" rel="noopener noreferrer"&gt;🔗 Get it now on GitHub!&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;Tired of writing CRUD endpoints by hand? Copy-pasting Go structs? Spending hours on Swagger documentation? Reimplementing the same authentication flow for the hundredth time? &lt;strong&gt;GoREST solves all of that.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F032ymodam3bqnuf8fz0r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F032ymodam3bqnuf8fz0r.png" alt="GoREST - Generate Production-Ready REST APIs in Seconds" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With its first release, GoREST offers a production-grade REST API generator that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔎 Auto-discovery of tables, relations, columns &amp;amp; types&lt;/li&gt;
&lt;li&gt;🛠 Scaffold REST endpoints for each table&lt;/li&gt;
&lt;li&gt;⚡ Type-safe generic CRUD operations with hooks system&lt;/li&gt;
&lt;li&gt;🔐 Full DTO support with field-level control (&lt;code&gt;dto&lt;/code&gt; tags)&lt;/li&gt;
&lt;li&gt;🔑 JWT authentication with context-aware middleware&lt;/li&gt;
&lt;li&gt;🎭 Hook layer to add your business logic onto your API resources&lt;/li&gt;
&lt;li&gt;🌐 JSON-LD support with semantic web context (&lt;a class="mentioned-user" href="https://dev.to/context"&gt;@context&lt;/a&gt;, @type, &lt;a class="mentioned-user" href="https://dev.to/id"&gt;@id&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;🔗 Automatic relation to IRI conversion (e.g., &lt;code&gt;/users/{uuid}&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;🔍 Advanced filtering &amp;amp; ordering&lt;/li&gt;
&lt;li&gt;📄 Page-based pagination with Hydra collections&lt;/li&gt;
&lt;li&gt;👨🏻‍💻 DAL for PostgreSQL, MySQL and SQLite engines&lt;/li&gt;
&lt;li&gt;🛡️ Production-grade error and process management&lt;/li&gt;
&lt;li&gt;🐳 Docker support with multi-database testing&lt;/li&gt;
&lt;li&gt;🧪 Full test coverage with automated testing&lt;/li&gt;
&lt;li&gt;💚 Health check endpoint (&lt;code&gt;/health&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;📜 OpenAPI 3 spec generation&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🎯 Why GoREST?
&lt;/h2&gt;

&lt;p&gt;GoREST isn't just another CRUD generator. It's a complete solution for building production-ready REST APIs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Feature&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;GoREST&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Manual Implementation&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Time to API&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2 commands (~30 seconds)&lt;/td&gt;
&lt;td&gt;Days/weeks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Schema Discovery&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Auto-discovery of tables, relations, columns &amp;amp; types&lt;/td&gt;
&lt;td&gt;Manual schema mapping&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Database Support&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;PostgreSQL, MySQL, SQLite (DAL)&lt;/td&gt;
&lt;td&gt;Pick one, code for one&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Type Safety&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Auto-generated models with type-safe CRUD&lt;/td&gt;
&lt;td&gt;Manual struct definitions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;REST Endpoints&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Scaffold for each table&lt;/td&gt;
&lt;td&gt;Write every endpoint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OpenAPI Docs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Auto-generated 3.0 spec&lt;/td&gt;
&lt;td&gt;Manual Swagger annotations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Authentication&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Built-in JWT + context-aware middleware&lt;/td&gt;
&lt;td&gt;Implement from scratch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Field Auto-Population&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Auto-populate from auth context&lt;/td&gt;
&lt;td&gt;Manual implementation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DTOs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Auto-generated with field-level security (&lt;code&gt;dto&lt;/code&gt; tags)&lt;/td&gt;
&lt;td&gt;Manual for every endpoint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;JSON-LD Support&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Built-in semantic web (&lt;a class="mentioned-user" href="https://dev.to/context"&gt;@context&lt;/a&gt;, @type, &lt;a class="mentioned-user" href="https://dev.to/id"&gt;@id&lt;/a&gt;)&lt;/td&gt;
&lt;td&gt;Complex manual implementation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Foreign Key IRIs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Automatic conversion (e.g., &lt;code&gt;/users/{uuid}&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Manual hypermedia links&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Filtering&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Equality, comparison, text search, multiple fields&lt;/td&gt;
&lt;td&gt;Hours of coding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ordering&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Multi-field sorting out of the box&lt;/td&gt;
&lt;td&gt;Manual SQL ORDER BY logic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pagination&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hydra collections with page-based navigation&lt;/td&gt;
&lt;td&gt;Manual cursor/offset implementation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hooks System&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;StateProcessor, SQLQueryOverride, Serializer&lt;/td&gt;
&lt;td&gt;Custom middleware for each case&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Production Features&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Health checks, graceful shutdown, structured logging&lt;/td&gt;
&lt;td&gt;One-by-one implementation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Error Handling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Production-grade error management&lt;/td&gt;
&lt;td&gt;Custom error handling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Testing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Full test coverage with Docker support&lt;/td&gt;
&lt;td&gt;Write all tests manually&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  📌 Real-World Example: Todo API in 60 Seconds
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkysfu3ptmlghh7bidv8t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkysfu3ptmlghh7bidv8t.png" alt="Another One DJ Khaled meme" width="400" height="204"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Start with Your Existing Database
&lt;/h3&gt;

&lt;p&gt;No need to change your schema - GoREST works with what you have.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;gen_random_uuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;firstname&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;lastname&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&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="k"&gt;WITH&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;todo&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;gen_random_uuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;users&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;title&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&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="k"&gt;WITH&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Generate Everything (Models, DTOs, Routes, OpenAPI)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/nicolasbonnici/gorest.git
&lt;span class="nb"&gt;cd &lt;/span&gt;gorest
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
&lt;span class="c"&gt;# Edit .env with your database connection&lt;/span&gt;

make generate     &lt;span class="c"&gt;# Generates models, DTOs, resources, routes, OpenAPI spec&lt;/span&gt;
make run         &lt;span class="c"&gt;# API is live!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;That's it.&lt;/strong&gt; Your API is running at &lt;strong&gt;&lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;&lt;/strong&gt; with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Full CRUD endpoints&lt;/strong&gt; for todos and users&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;OpenAPI documentation&lt;/strong&gt; at &lt;code&gt;/openapi.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Health check&lt;/strong&gt; at &lt;code&gt;/health&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;JWT authentication&lt;/strong&gt; ready to use&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Type-safe DTOs&lt;/strong&gt; with automatic validation&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;JSON-LD support&lt;/strong&gt; for semantic web compatibility&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can find the API spec here:&lt;/p&gt;

&lt;p&gt;Web version (using Scalar UI): &lt;strong&gt;&lt;a href="http://localhost:3000/openapi" rel="noopener noreferrer"&gt;http://localhost:3000/openapi&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
JSON format: &lt;strong&gt;&lt;a href="http://localhost:3000/openapi.json" rel="noopener noreferrer"&gt;http://localhost:3000/openapi.json&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Generated Endpoints:&lt;/strong&gt;&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;Endpoint&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;GET&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/todos&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;List todos (with filtering, ordering, pagination)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;POST&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/todos&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create todo&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;/todos/:id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Get specific todo&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;/todos/:id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Update todo&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;/todos/:id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Delete todo&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;/users&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;List users&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;POST&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/users&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create user&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;/users/:id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Get user&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;/users/:id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Update user&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;/users/:id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Delete user&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h2&gt;
  
  
  🔐 Security Done Right: DTOs &amp;amp; Field Control
&lt;/h2&gt;

&lt;p&gt;Here's where GoREST shines. Ever had a client send &lt;code&gt;user_id&lt;/code&gt; in a request to access someone else's data? Not anymore.&lt;/p&gt;
&lt;h3&gt;
  
  
  Automatic DTO Generation
&lt;/h3&gt;

&lt;p&gt;GoREST generates three DTO types for every model:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;CreateDTO&lt;/strong&gt; - For POST requests (excludes auto-generated fields like &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;created_at&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UpdateDTO&lt;/strong&gt; - For PUT requests (excludes auto-generated and protected fields)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ResponseDTO&lt;/strong&gt; - For GET responses (shows everything the client should see)&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Field-Level Control with &lt;code&gt;dto&lt;/code&gt; Tags
&lt;/h3&gt;

&lt;p&gt;Control what clients can send and receive using model tags:&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;Todo&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;string&lt;/span&gt;     &lt;span class="s"&gt;`json:"id" db:"id"`&lt;/span&gt;
    &lt;span class="n"&gt;UserId&lt;/span&gt;    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;    &lt;span class="s"&gt;`json:"user_id" db:"user_id" dto:"read"`&lt;/span&gt;  &lt;span class="c"&gt;// 🔒 Read-only!&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" db:"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" db:"content"`&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;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" db:"created_at"`&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;Result:&lt;/strong&gt; Clients &lt;strong&gt;cannot&lt;/strong&gt; send &lt;code&gt;user_id&lt;/code&gt; in POST/PUT requests. GoREST populates it server-side from JWT authentication:&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="c"&gt;// Client sends (TodoCreateDTO):&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="s"&gt;"title"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Buy groceries"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Milk, eggs, bread"&lt;/span&gt;
  &lt;span class="c"&gt;// No user_id - field not in DTO&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Server populates user_id from JWT token (happens in hooks)&lt;/span&gt;
&lt;span class="c"&gt;// Client receives (TodoDTO):&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"abc-123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"user_id"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"xyz-789"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c"&gt;// ✅ Populated server-side&lt;/span&gt;
  &lt;span class="s"&gt;"title"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Buy groceries"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Milk, eggs, bread"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"created_at"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"2024-01-15T10:30:00Z"&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;Security guarantee:&lt;/strong&gt; Client cannot spoof &lt;code&gt;user_id&lt;/code&gt;. It's extracted from the JWT token by the server. Period.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔍 Advanced Filtering, Ordering &amp;amp; Pagination
&lt;/h2&gt;

&lt;p&gt;GoREST provides production-grade querying out of the box. No code needed - it's all generated for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Filtering
&lt;/h3&gt;

&lt;p&gt;Filter your data using intuitive query parameters with support for various operators:&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="c"&gt;# Equality&lt;/span&gt;
GET /todos?status&lt;span class="o"&gt;=&lt;/span&gt;active

&lt;span class="c"&gt;# Multiple values (OR)&lt;/span&gt;
GET /todos?status[]&lt;span class="o"&gt;=&lt;/span&gt;active&amp;amp;status[]&lt;span class="o"&gt;=&lt;/span&gt;pending

&lt;span class="c"&gt;# Comparison operators&lt;/span&gt;
GET /todos?priority[gte]&lt;span class="o"&gt;=&lt;/span&gt;5           &lt;span class="c"&gt;# Greater than or equal&lt;/span&gt;
GET /todos?priority[lte]&lt;span class="o"&gt;=&lt;/span&gt;10          &lt;span class="c"&gt;# Less than or equal&lt;/span&gt;
GET /todos?priority[gt]&lt;span class="o"&gt;=&lt;/span&gt;3            &lt;span class="c"&gt;# Greater than&lt;/span&gt;
GET /todos?priority[lt]&lt;span class="o"&gt;=&lt;/span&gt;8            &lt;span class="c"&gt;# Less than&lt;/span&gt;
GET /todos?priority[ne]&lt;span class="o"&gt;=&lt;/span&gt;0            &lt;span class="c"&gt;# Not equal&lt;/span&gt;

&lt;span class="c"&gt;# Text search&lt;/span&gt;
GET /todos?title[like]&lt;span class="o"&gt;=&lt;/span&gt;meeting       &lt;span class="c"&gt;# Case-sensitive&lt;/span&gt;
GET /todos?title[ilike]&lt;span class="o"&gt;=&lt;/span&gt;MEETING      &lt;span class="c"&gt;# Case-insensitive&lt;/span&gt;

&lt;span class="c"&gt;# Date filtering&lt;/span&gt;
GET /todos?created_at[gte]&lt;span class="o"&gt;=&lt;/span&gt;2024-01-01
GET /todos?updated_at[lt]&lt;span class="o"&gt;=&lt;/span&gt;2024-12-31

&lt;span class="c"&gt;# Combine filters (AND)&lt;/span&gt;
GET /todos?status&lt;span class="o"&gt;=&lt;/span&gt;active&amp;amp;priority[gte]&lt;span class="o"&gt;=&lt;/span&gt;7&amp;amp;created_at[gte]&lt;span class="o"&gt;=&lt;/span&gt;2024-01-01
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Ordering
&lt;/h3&gt;

&lt;p&gt;Sort results by one or multiple fields:&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="c"&gt;# Single field&lt;/span&gt;
GET /todos?order[priority]&lt;span class="o"&gt;=&lt;/span&gt;desc
GET /todos?order[created_at]&lt;span class="o"&gt;=&lt;/span&gt;asc

&lt;span class="c"&gt;# Multiple fields (order matters)&lt;/span&gt;
GET /todos?order[priority]&lt;span class="o"&gt;=&lt;/span&gt;desc&amp;amp;order[created_at]&lt;span class="o"&gt;=&lt;/span&gt;asc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first &lt;code&gt;order&lt;/code&gt; parameter becomes the primary sort, second becomes secondary, etc.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pagination with Hydra Collections
&lt;/h3&gt;

&lt;p&gt;GoREST uses &lt;strong&gt;Hydra&lt;/strong&gt; for semantic pagination:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;GET /todos?page&lt;span class="o"&gt;=&lt;/span&gt;2&amp;amp;limit&lt;span class="o"&gt;=&lt;/span&gt;20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Response:&lt;/strong&gt;&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;"@context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://www.w3.org/ns/hydra/context.jsonld"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hydra:Collection"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hydra:totalItems"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hydra:member"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/todos/abc-123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TodoDTO"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"abc-123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Buy groceries"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/users/xyz-789"&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="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hydra:view"&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;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hydra:PartialCollectionView"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"hydra:first"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/todos"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"hydra:previous"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/todos"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"hydra:next"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/todos?page=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;"hydra:last"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/todos?page=5"&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="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Real-World Combined Query
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;GET /todos?status[]&lt;span class="o"&gt;=&lt;/span&gt;active&amp;amp;status[]&lt;span class="o"&gt;=&lt;/span&gt;pending&amp;amp;priority[gte]&lt;span class="o"&gt;=&lt;/span&gt;5&amp;amp;order[priority]&lt;span class="o"&gt;=&lt;/span&gt;desc&amp;amp;order[created_at]&lt;span class="o"&gt;=&lt;/span&gt;desc&amp;amp;page&lt;span class="o"&gt;=&lt;/span&gt;2&amp;amp;limit&lt;span class="o"&gt;=&lt;/span&gt;20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This single query:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Filters todos with status "active" OR "pending"&lt;/li&gt;
&lt;li&gt;✅ AND priority &amp;gt;= 5&lt;/li&gt;
&lt;li&gt;✅ Sorts by priority (descending), then created_at (descending)&lt;/li&gt;
&lt;li&gt;✅ Returns page 2 with 20 items per page&lt;/li&gt;
&lt;li&gt;✅ Includes navigation links (first, previous, next, last)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;All of this is auto-generated.&lt;/strong&gt; Zero code required.&lt;/p&gt;

&lt;h3&gt;
  
  
  Security
&lt;/h3&gt;

&lt;p&gt;Filtering and ordering are secure by design:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔒 Only database fields (with &lt;code&gt;db&lt;/code&gt; tags) can be filtered/sorted&lt;/li&gt;
&lt;li&gt;🔒 SQL injection protection via parameterized queries&lt;/li&gt;
&lt;li&gt;🔒 Invalid fields are silently ignored&lt;/li&gt;
&lt;li&gt;🔒 No arbitrary SQL execution possible&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🪝 The Hooks System: Resources Lifecycle Hooks
&lt;/h2&gt;

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

&lt;p&gt;GoREST generates code, but &lt;strong&gt;you control the business logic&lt;/strong&gt; through hooks. Here's the real power:&lt;/p&gt;

&lt;h3&gt;
  
  
  Hook Types
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;StateProcessor&lt;/strong&gt; - Validate/transform data before Create/Update/Delete&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQLQueryOverride&lt;/strong&gt; - Replace default queries with custom SQL&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQLQueryListener&lt;/strong&gt; - Intercept queries before/after execution&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Serializer&lt;/strong&gt; - Transform data before API response&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Example 1: Auto-Populate User ID (Server-Side Security)
&lt;/h3&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;TodoHooks&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;hooks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NoOpHooks&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;Todo&lt;/span&gt;&lt;span class="p"&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;h&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;TodoHooks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;StateProcessor&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;operation&lt;/span&gt; &lt;span class="n"&gt;hooks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Operation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;todo&lt;/span&gt; &lt;span class="o"&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;Todo&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="n"&gt;operation&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;hooks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OperationCreate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// Validate&lt;/span&gt;
        &lt;span class="k"&gt;if&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;todo&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="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;3&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;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"title must be at least 3 characters"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c"&gt;// Auto-populate user_id from JWT context (server-side!)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;userID&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;userID&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;if&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;userID&lt;/span&gt;&lt;span class="o"&gt;.&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="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;todo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UserId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;uid&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;return&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;h3&gt;
  
  
  Example 2: Custom SQL Queries
&lt;/h3&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;TodoHooks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;SQLQueryOverride&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;operation&lt;/span&gt; &lt;span class="n"&gt;hooks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Operation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;sqlbuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SelectQuery&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;operation&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;hooks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OperationList&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// Only show incomplete todos&lt;/span&gt;
        &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;sqlbuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
            &lt;span class="n"&gt;Select&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="s"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
            &lt;span class="n"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"todo"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
            &lt;span class="n"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"completed = FALSE"&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;query&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;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&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;h3&gt;
  
  
  Example 3: Per-Resource Authorization
&lt;/h3&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;TodoHooks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Authorize&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;operation&lt;/span&gt; &lt;span class="n"&gt;hooks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Operation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;any&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;userID&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"user_id"&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;userID&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;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"authentication required"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Add custom role checks, permissions, etc.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;hooks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OperationDelete&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// Only admins can delete&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;isAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userID&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;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"insufficient permissions"&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;return&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;p&gt;&lt;strong&gt;Key Benefit:&lt;/strong&gt; Hooks live in separate files. Regenerate your API anytime without losing business logic.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚡ Performance: Blazing Fast Under Load
&lt;/h2&gt;

&lt;p&gt;GoREST is built for production workloads. Here are real benchmark results from concurrent load testing with &lt;strong&gt;Vegeta&lt;/strong&gt; (Framework 14" laptop: i7 12th gen, 16GB RAM, PostgreSQL):&lt;/p&gt;

&lt;h3&gt;
  
  
  Load Test Results
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Records&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Concurrency&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;RPS&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;p50 (Median)&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;p95&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;p99 (Tail)&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Success Rate&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;810µs&lt;/td&gt;
&lt;td&gt;1.27ms&lt;/td&gt;
&lt;td&gt;1.27ms&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;744µs&lt;/td&gt;
&lt;td&gt;959µs&lt;/td&gt;
&lt;td&gt;1.04ms&lt;/td&gt;
&lt;td&gt;98%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;229µs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;338µs&lt;/td&gt;
&lt;td&gt;518µs&lt;/td&gt;
&lt;td&gt;99.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;100&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;257µs&lt;/td&gt;
&lt;td&gt;455µs&lt;/td&gt;
&lt;td&gt;455µs&lt;/td&gt;
&lt;td&gt;80%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;100&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;262µs&lt;/td&gt;
&lt;td&gt;412µs&lt;/td&gt;
&lt;td&gt;451µs&lt;/td&gt;
&lt;td&gt;98%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;100&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;231µs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;329µs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;373µs&lt;/td&gt;
&lt;td&gt;99.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;1000&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;228µs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;386µs&lt;/td&gt;
&lt;td&gt;386µs&lt;/td&gt;
&lt;td&gt;80%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;1000&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;297µs&lt;/td&gt;
&lt;td&gt;379µs&lt;/td&gt;
&lt;td&gt;475µs&lt;/td&gt;
&lt;td&gt;98%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;1000&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;230µs&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;329µs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;378µs&lt;/td&gt;
&lt;td&gt;99.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Performance Summary
&lt;/h3&gt;

&lt;p&gt;✅ &lt;strong&gt;Best p50 (median):&lt;/strong&gt; 228µs - typical response time&lt;br&gt;
✅ &lt;strong&gt;Best p95:&lt;/strong&gt; 329µs - 95% of requests complete faster&lt;br&gt;
✅ &lt;strong&gt;Worst p99 (tail latency):&lt;/strong&gt; 1.27ms - even worst-case is sub-2ms&lt;br&gt;
✅ &lt;strong&gt;All requests under 10ms&lt;/strong&gt; - Excellent production performance&lt;/p&gt;
&lt;h3&gt;
  
  
  What This Means
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;⚡ &lt;strong&gt;Sub-millisecond responses&lt;/strong&gt; for most queries (even with 1000 records)&lt;/li&gt;
&lt;li&gt;🚀 &lt;strong&gt;Scales horizontally&lt;/strong&gt; - performance stays consistent at 50 concurrent clients&lt;/li&gt;
&lt;li&gt;📦 &lt;strong&gt;Including JSON-LD transformation&lt;/strong&gt; - semantic web overhead is negligible&lt;/li&gt;
&lt;li&gt;🎯 &lt;strong&gt;Production-ready&lt;/strong&gt; - p99 latency under 2ms means happy users&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Benchmark It Yourself
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make benchmark
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The benchmark:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Creates a test table with 1000 records&lt;/li&gt;
&lt;li&gt;Generates models and API endpoints&lt;/li&gt;
&lt;li&gt;Starts the API server&lt;/li&gt;
&lt;li&gt;Runs Vegeta load tests at 1, 10, and 50 concurrent clients&lt;/li&gt;
&lt;li&gt;Tests fetching 10, 100, and 1000 records&lt;/li&gt;
&lt;li&gt;Measures p50, p95, p99 latency percentiles&lt;/li&gt;
&lt;li&gt;Cleans up and restores original schema&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Results depend on your hardware and database configuration. The above benchmarks use PostgreSQL with default settings.&lt;/p&gt;


&lt;h2&gt;
  
  
  🌐 JSON-LD: Semantic Web Out of the Box
&lt;/h2&gt;

&lt;p&gt;GoREST automatically supports &lt;strong&gt;JSON-LD&lt;/strong&gt; (Linked Data), making your API:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🤖 &lt;strong&gt;Machine-readable&lt;/strong&gt; - semantic context for AI/ML tools&lt;/li&gt;
&lt;li&gt;🔗 &lt;strong&gt;Interoperable&lt;/strong&gt; - works with semantic web technologies&lt;/li&gt;
&lt;li&gt;📈 &lt;strong&gt;SEO-friendly&lt;/strong&gt; - structured data for search engines&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Regular JSON&lt;/strong&gt; vs &lt;strong&gt;JSON-LD:&lt;/strong&gt;&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="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Accept:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;application/json&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"abc-123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"xyz-789"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Buy groceries"&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="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Accept:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;application/ld+json&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;"@context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://schema.org/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TodoDTO"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/todos/abc-123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"abc-123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/users/xyz-789"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;🔗&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Auto-converted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;IRI&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Buy groceries"&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;&lt;strong&gt;Foreign keys automatically become IRIs:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;user_id: "xyz-789"&lt;/code&gt; → &lt;code&gt;user_id: "/users/xyz-789"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Perfect for hypermedia-driven APIs (HATEOAS)&lt;/li&gt;
&lt;li&gt;Zero configuration required&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🛡️ Production Features
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Health Checks
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://localhost:3000/health
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"healthy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"database"&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;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"up"&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="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;Perfect for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Load balancer health checks&lt;/li&gt;
&lt;li&gt;Kubernetes readiness/liveness probes&lt;/li&gt;
&lt;li&gt;Monitoring systems (Prometheus, Datadog)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Graceful Shutdown
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Listens for &lt;code&gt;SIGTERM&lt;/code&gt; and &lt;code&gt;SIGINT&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;30-second timeout for in-flight requests&lt;/li&gt;
&lt;li&gt;Cleanly closes database connections&lt;/li&gt;
&lt;li&gt;Zero data corruption risk&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Structured Logging
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"github.com/nicolasbonnici/gorest/internal/logger"&lt;/span&gt;

&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&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;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"User created"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&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;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Database error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"error"&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="s"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;JSON output perfect for log aggregation (ELK, Splunk, etc.)&lt;/p&gt;




&lt;h2&gt;
  
  
  🤝 Contribute &amp;amp; Help Shape GoREST
&lt;/h2&gt;

&lt;p&gt;GoREST is &lt;strong&gt;100% open-source&lt;/strong&gt; and we need &lt;strong&gt;YOU&lt;/strong&gt; to make it better!&lt;/p&gt;

&lt;h3&gt;
  
  
  🌟 Why Contribute?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Learn Go, SQL, code generation, REST APIs&lt;/strong&gt; in a real-world project&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your code helps thousands of developers&lt;/strong&gt; build better APIs faster&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build your portfolio&lt;/strong&gt; with production-grade open-source contributions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Join a welcoming community&lt;/strong&gt; of passionate developers&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Support the free software movement&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  💡 How to Contribute
&lt;/h3&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;1. Test &amp;amp; Report Bugs&lt;/strong&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/nicolasbonnici/gorest.git
&lt;span class="nb"&gt;cd &lt;/span&gt;gorest
make test-up &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; make test-schema
make generate &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; make run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Found a bug? &lt;a href="https://github.com/nicolasbonnici/gorest/issues" rel="noopener noreferrer"&gt;Open an issue&lt;/a&gt;!&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;2. Contribute Code&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;For Beginners:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fix typos in documentation&lt;/li&gt;
&lt;li&gt;Add code comments&lt;/li&gt;
&lt;li&gt;Write examples and tutorials&lt;/li&gt;
&lt;li&gt;Improve error messages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Intermediate:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add tests for existing features&lt;/li&gt;
&lt;li&gt;Fix bugs from the issue tracker&lt;/li&gt;
&lt;li&gt;Improve code generation templates&lt;/li&gt;
&lt;li&gt;Add validation helpers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Advanced:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add support for new databases (MariaDB, CockroachDB?)&lt;/li&gt;
&lt;li&gt;Implement GraphQL support alongside REST&lt;/li&gt;
&lt;li&gt;Add gRPC code generation&lt;/li&gt;
&lt;li&gt;Create monitoring/metrics integrations&lt;/li&gt;
&lt;li&gt;Build a web UI for API management&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;3. Improve Documentation&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Write blog posts about your GoREST experience&lt;/li&gt;
&lt;li&gt;Create video tutorials&lt;/li&gt;
&lt;li&gt;Translate documentation to other languages&lt;/li&gt;
&lt;li&gt;Add architecture diagrams&lt;/li&gt;
&lt;li&gt;Write migration guides&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;4. Share Ideas&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Have a feature idea? Open a GitHub Discussion or Issue!&lt;/p&gt;

&lt;h3&gt;
  
  
  🚀 Easy First Contributions
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Add Database Support&lt;/strong&gt;: GoREST supports PostgreSQL, MySQL, SQLite - what about MariaDB or CockroachDB?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write Hook Examples&lt;/strong&gt;: Share your real-world hook implementations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create Middleware&lt;/strong&gt;: Rate limiting, CORS, request logging, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improve Benchmarks&lt;/strong&gt;: Test with different databases and workloads&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build Integrations&lt;/strong&gt;: Prometheus metrics, OpenTelemetry, etc.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  📋 Contribution Guidelines
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fork the repo&lt;/strong&gt; and create a feature branch&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write tests&lt;/strong&gt; for new features&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Follow Go conventions&lt;/strong&gt; (gofmt, golint)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use conventional commits&lt;/strong&gt; (&lt;code&gt;feat:&lt;/code&gt;, &lt;code&gt;fix:&lt;/code&gt;, &lt;code&gt;docs:&lt;/code&gt;, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Update documentation&lt;/strong&gt; for user-facing changes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open a PR&lt;/strong&gt; with a clear description&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See &lt;a href="https://github.com/nicolasbonnici/gorest/blob/main/CONTRIBUTING.md" rel="noopener noreferrer"&gt;CONTRIBUTING.md&lt;/a&gt; for detailed guidelines.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 What Could YOU Build with GoREST?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;💼 &lt;strong&gt;SaaS Backend&lt;/strong&gt; - Multi-tenant APIs in minutes&lt;/li&gt;
&lt;li&gt;📱 &lt;strong&gt;Mobile App Backend&lt;/strong&gt; - REST APIs ready for iOS/Android&lt;/li&gt;
&lt;li&gt;🤖 &lt;strong&gt;Microservices&lt;/strong&gt; - Generate APIs for each service&lt;/li&gt;
&lt;li&gt;🧪 &lt;strong&gt;Prototypes&lt;/strong&gt; - Validate ideas fast&lt;/li&gt;
&lt;li&gt;📊 &lt;strong&gt;Data APIs&lt;/strong&gt; - Expose your database as a service&lt;/li&gt;
&lt;li&gt;🎮 &lt;strong&gt;Game Backends&lt;/strong&gt; - Player data, leaderboards, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What will you build?&lt;/strong&gt; Tell us in the comments!&lt;/p&gt;




&lt;h2&gt;
  
  
  🔗 Links &amp;amp; Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/nicolasbonnici/gorest" rel="noopener noreferrer"&gt;github.com/nicolasbonnici/gorest&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation&lt;/strong&gt;: Full README with detailed guides&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Issues&lt;/strong&gt;: &lt;a href="https://github.com/nicolasbonnici/gorest/issues" rel="noopener noreferrer"&gt;Report bugs or request features&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contributing&lt;/strong&gt;: &lt;a href="https://github.com/nicolasbonnici/gorest/blob/main/CONTRIBUTING.md" rel="noopener noreferrer"&gt;CONTRIBUTING.md&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ⭐ Support the Project
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Star the repo&lt;/strong&gt; - it helps others discover GoREST&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Share this article&lt;/strong&gt; - Twitter, LinkedIn, Reddit, Dev.to&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write about your experience&lt;/strong&gt; - blog posts, tweets, videos&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contribute code or docs&lt;/strong&gt; - every bit helps!&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  💬 Join the Conversation
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Have you tried GoREST?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What's your use case?&lt;/li&gt;
&lt;li&gt;What features would you like to see?&lt;/li&gt;
&lt;li&gt;Want to contribute but don't know where to start?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Drop a comment below!&lt;/strong&gt; We reply to everyone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ready to generate your first API?&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/nicolasbonnici/gorest.git
&lt;span class="nb"&gt;cd &lt;/span&gt;gorest
make generate &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; make run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;It's that simple.&lt;/strong&gt; 🚀&lt;/p&gt;




&lt;p&gt;&lt;em&gt;GoREST v0.1.0 is MIT licensed and free to use in your projects. Built with ❤️ in Go, for developers who value their time.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>rest</category>
      <category>sql</category>
      <category>gorest</category>
    </item>
    <item>
      <title>Build a Symfony 7 boilerplate using FrankenPHP, Docker, PostgreSQL and php 8.4</title>
      <dc:creator>Nicolas Bonnici</dc:creator>
      <pubDate>Mon, 23 Dec 2024 08:08:47 +0000</pubDate>
      <link>https://dev.to/nicolasbonnici/build-a-symfony-7-boilerplate-using-frankenphp-docker-postgresql-and-php-84-4ej3</link>
      <guid>https://dev.to/nicolasbonnici/build-a-symfony-7-boilerplate-using-frankenphp-docker-postgresql-and-php-84-4ej3</guid>
      <description>&lt;h2&gt;
  
  
  What are we cooking?
&lt;/h2&gt;

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

&lt;p&gt;Hi everyone, in this post we're going to build a boilerplate to start any kind of &lt;a href="https://symfony.com/" rel="noopener noreferrer"&gt;Symfony&lt;/a&gt; project, such like a monolith or an API. We'll use the top tier app server &lt;a href="https://frankenphp.dev/" rel="noopener noreferrer"&gt;FrankenPHP&lt;/a&gt; written in Go. The boilerplate will also use &lt;a href="https://www.postgresql.org/" rel="noopener noreferrer"&gt;PostgreSQL&lt;/a&gt; SGDB for relational database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compose the stack using Docker and Compose
&lt;/h2&gt;

&lt;p&gt;First thing first to orchestrate all the containers we will use Compose, we're going to write the stack containers definition.&lt;/p&gt;

&lt;p&gt;The directory structure will be very simple, one folder for all docker related files and an other for the Symfony project source code.&lt;/p&gt;

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

&lt;p&gt;We'll add a &lt;code&gt;compose.yml&lt;/code&gt; file directly at the project root.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;boilerplate-database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:16&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;boilerplate-database&lt;/span&gt;
    &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;symfony/.env&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${DATABASE_NAME}&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${DATABASE_PWD}&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;15432:5432&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;database_data:/var/lib/postgresql/data:rw&lt;/span&gt;

  &lt;span class="na"&gt;boilerplate-app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;symfony/.env&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;boilerplate-app&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/api/Dockerfile&lt;/span&gt;
      &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;frankenphp_dev&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;boilerplate-database&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${IMAGES_PREFIX:-}boilerplate-app&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;SERVER_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${SERVER_NAME:-http://localhost}, boilerplate-app:80&lt;/span&gt;
      &lt;span class="na"&gt;MERCURE_PUBLISHER_JWT_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${CADDY_MERCURE_JWT_SECRET:-!ChangeThisMercureHubJWTSecretKey!}&lt;/span&gt;
      &lt;span class="na"&gt;MERCURE_SUBSCRIBER_JWT_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${CADDY_MERCURE_JWT_SECRET:-!ChangeThisMercureHubJWTSecretKey!}&lt;/span&gt;
      &lt;span class="na"&gt;TRUSTED_PROXIES&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${TRUSTED_PROXIES:-127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16}&lt;/span&gt;
      &lt;span class="na"&gt;TRUSTED_HOSTS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${TRUSTED_HOSTS:-^${SERVER_NAME:-nbonnici\.info|localhost}|php$$}&lt;/span&gt;
      &lt;span class="na"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgresql://${POSTGRES_USER:-app}:${POSTGRES_PASSWORD:-!ChangeMe!}@database:5432/${POSTGRES_DB:-app}?serverVersion=${POSTGRES_VERSION:-16}&amp;amp;charset=${POSTGRES_CHARSET:-utf8}&lt;/span&gt;
      &lt;span class="na"&gt;MERCURE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${CADDY_MERCURE_URL:-http://php/.well-known/mercure}&lt;/span&gt;
      &lt;span class="na"&gt;MERCURE_PUBLIC_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${CADDY_MERCURE_PUBLIC_URL:-http://${SERVER_NAME:-localhost}/.well-known/mercure}&lt;/span&gt;
      &lt;span class="na"&gt;MERCURE_JWT_SECRET&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${CADDY_MERCURE_JWT_SECRET:-!ChangeThisMercureHubJWTSecretKey!}&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./symfony:/app:cached&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;caddy_data:/data&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;caddy_config:/config&lt;/span&gt;
    &lt;span class="c1"&gt;# comment the following line in production, it allows to have nice human-readable logs in dev&lt;/span&gt;
    &lt;span class="na"&gt;tty&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;external&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;proxies&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;database_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;caddy_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;caddy_config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here nothing fancy, we create on a custom network a database container using the latest PostgreSQL version and another container built using frankenphp containing the Symfony app.&lt;/p&gt;

&lt;p&gt;We can override it this way for development purpose using a compose.override.yml at project root&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Development environment override&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;boilerplate-app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/api/Dockerfile&lt;/span&gt;
      &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;frankenphp_dev&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# HTTP&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
        &lt;span class="na"&gt;published&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${HTTP_PORT:-80}&lt;/span&gt;
        &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tcp&lt;/span&gt;
      &lt;span class="c1"&gt;# HTTPS&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;443&lt;/span&gt;
        &lt;span class="na"&gt;published&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${HTTPS_PORT:-443}&lt;/span&gt;
        &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tcp&lt;/span&gt;
      &lt;span class="c1"&gt;# HTTP/3&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;443&lt;/span&gt;
        &lt;span class="na"&gt;published&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${HTTP3_PORT:-443}&lt;/span&gt;
        &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;udp&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./symfony:/app&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/symfony/var&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./docker/frankenphp/Caddyfile:/etc/caddy/Caddyfile:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./docker/frankenphp/conf.d/app.dev.ini:/usr/local/etc/php/conf.d/app.dev.ini:ro&lt;/span&gt;
      &lt;span class="c1"&gt;# If you develop on Mac or Windows you can remove the vendor/ directory&lt;/span&gt;
      &lt;span class="c1"&gt;#  from the bind-mount for better performance by enabling the next line:&lt;/span&gt;
      &lt;span class="c1"&gt;#- /app/vendor&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;MERCURE_EXTRA_DIRECTIVES&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;demo&lt;/span&gt;
      &lt;span class="c1"&gt;# See https://xdebug.org/docs/all_settings#mode&lt;/span&gt;
      &lt;span class="na"&gt;XDEBUG_MODE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${XDEBUG_MODE:-off}"&lt;/span&gt;
    &lt;span class="na"&gt;extra_hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# Ensure that host.docker.internal is correctly defined on Linux&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;host.docker.internal:host-gateway&lt;/span&gt;
    &lt;span class="na"&gt;tty&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's take a closer look at the app container Dockerfile located in &lt;code&gt;docker/api/Dockerfile&lt;/code&gt; to discover how this image is built.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;dunglas/frankenphp:1-php8.4-bookworm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;frankenphp_upstream&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;frankenphp_upstream&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;frankenphp_base&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# persistent / runtime deps&lt;/span&gt;
&lt;span class="c"&gt;# hadolint ignore=DL3008&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    acl &lt;span class="se"&gt;\
&lt;/span&gt;    file &lt;span class="se"&gt;\
&lt;/span&gt;    gettext &lt;span class="se"&gt;\
&lt;/span&gt;    git &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="c"&gt;# https://getcomposer.org/doc/03-cli.md#composer-allow-superuser&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; COMPOSER_ALLOW_SUPERUSER=1&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-eux&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    install-php-extensions &lt;span class="se"&gt;\
&lt;/span&gt;        @composer &lt;span class="se"&gt;\
&lt;/span&gt;        apcu &lt;span class="se"&gt;\
&lt;/span&gt;        intl &lt;span class="se"&gt;\
&lt;/span&gt;        opcache &lt;span class="se"&gt;\
&lt;/span&gt;        zip &lt;span class="se"&gt;\
&lt;/span&gt;        pdo_mysql &lt;span class="se"&gt;\
&lt;/span&gt;        pdo_pgsql &lt;span class="se"&gt;\
&lt;/span&gt;        gd &lt;span class="se"&gt;\
&lt;/span&gt;        intl &lt;span class="se"&gt;\
&lt;/span&gt;        xdebug &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --link docker/frankenphp/conf.d/app.ini $PHP_INI_DIR/conf.d/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --link --chmod=755 docker/frankenphp/docker-entrypoint.sh /usr/local/bin/docker-entrypoint&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --link docker/frankenphp/Caddyfile /etc/caddy/Caddyfile&lt;/span&gt;

&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["docker-entrypoint"]&lt;/span&gt;

&lt;span class="k"&gt;HEALTHCHECK&lt;/span&gt;&lt;span class="s"&gt; --start-period=60s CMD curl -f http://localhost:2019/metrics || exit 1&lt;/span&gt;

&lt;span class="c"&gt;# Dev&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;frankenphp_base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;frankenphp_dev&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; APP_ENV=dev XDEBUG_MODE=off&lt;/span&gt;
&lt;span class="k"&gt;VOLUME&lt;/span&gt;&lt;span class="s"&gt; /app/var/&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mv&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PHP_INI_DIR&lt;/span&gt;&lt;span class="s2"&gt;/php.ini-development"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PHP_INI_DIR&lt;/span&gt;&lt;span class="s2"&gt;/php.ini"&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-eux&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    install-php-extensions &lt;span class="se"&gt;\
&lt;/span&gt;        xdebug &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --link docker/frankenphp/conf.d/app.dev.ini $PHP_INI_DIR/conf.d/&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; [ "frankenphp", "run", "--config", "/etc/caddy/Caddyfile", "--watch" ]&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here again nothing fancy unless the multi stages of this Dockerfile, first we build a base image using &lt;a href="https://www.debian.org/" rel="noopener noreferrer"&gt;Debian Bookworm&lt;/a&gt; based Frankenphp image, install container's dependencies and docker entrypoint. Then we can build and configure the dev image from it and production ready optimized image.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I use the &lt;a href="https://www.debian.org/" rel="noopener noreferrer"&gt;Debian&lt;/a&gt; Bookworm based image since i don't recommend using the &lt;a href="https://alpinelinux.org/" rel="noopener noreferrer"&gt;Alpine&lt;/a&gt; one, the perfs seems a little less stable and fast. This is related to the &lt;a href="https://musl.libc.org/" rel="noopener noreferrer"&gt;musl libc library&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Just-in-time_compilation" rel="noopener noreferrer"&gt;JIT AKA just in time compilation&lt;/a&gt; used by php core, more information here on the &lt;a href="https://frankenphp.dev/docs/performance/#dont-use-musl" rel="noopener noreferrer"&gt;official Frankenphp document&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The docker entrypoint, located in &lt;code&gt;docker/frankenphp&lt;/code&gt; look like this:&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="c"&gt;#!/bin/sh&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'frankenphp'&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'php'&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'bin/console'&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-A&lt;/span&gt; &lt;span class="s1"&gt;'vendor/'&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;composer &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--optimize-autoloader&lt;/span&gt; &lt;span class="nt"&gt;--prefer-dist&lt;/span&gt; &lt;span class="nt"&gt;--no-progress&lt;/span&gt; &lt;span class="nt"&gt;--no-interaction&lt;/span&gt;
    &lt;span class="k"&gt;fi

    if &lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; ^DATABASE_URL&lt;span class="o"&gt;=&lt;/span&gt; .env&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Waiting for database to be ready..."&lt;/span&gt;
        &lt;span class="nv"&gt;ATTEMPTS_LEFT_TO_REACH_DATABASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;60
        &lt;span class="k"&gt;until&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$ATTEMPTS_LEFT_TO_REACH_DATABASE&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;DATABASE_ERROR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;php bin/console dbal:run-sql &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="s2"&gt;"SELECT 1"&lt;/span&gt; 2&amp;gt;&amp;amp;1&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
            if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 255 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
                &lt;span class="c"&gt;# If the Doctrine command exits with 255, an unrecoverable error occurred&lt;/span&gt;
                &lt;span class="nv"&gt;ATTEMPTS_LEFT_TO_REACH_DATABASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
                &lt;span class="nb"&gt;break
            &lt;/span&gt;&lt;span class="k"&gt;fi
            &lt;/span&gt;&lt;span class="nb"&gt;sleep &lt;/span&gt;1
            &lt;span class="nv"&gt;ATTEMPTS_LEFT_TO_REACH_DATABASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;ATTEMPTS_LEFT_TO_REACH_DATABASE &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="k"&gt;))&lt;/span&gt;
            &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Still waiting for database to be ready... Or maybe the database is not reachable. &lt;/span&gt;&lt;span class="nv"&gt;$ATTEMPTS_LEFT_TO_REACH_DATABASE&lt;/span&gt;&lt;span class="s2"&gt; attempts left."&lt;/span&gt;
        &lt;span class="k"&gt;done

        if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$ATTEMPTS_LEFT_TO_REACH_DATABASE&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
            &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"The database is not up or not reachable:"&lt;/span&gt;
            &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DATABASE_ERROR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
            &lt;span class="nb"&gt;exit &lt;/span&gt;1
        &lt;span class="k"&gt;else
            &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"The database is now ready and reachable"&lt;/span&gt;
        &lt;span class="k"&gt;fi

        if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt; find ./migrations &lt;span class="nt"&gt;-iname&lt;/span&gt; &lt;span class="s1"&gt;'*.php'&lt;/span&gt; &lt;span class="nt"&gt;-print&lt;/span&gt; &lt;span class="nt"&gt;-quit&lt;/span&gt; &lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
            &lt;/span&gt;php bin/console doctrine:migrations:migrate &lt;span class="nt"&gt;--no-interaction&lt;/span&gt; &lt;span class="nt"&gt;--all-or-nothing&lt;/span&gt;
        &lt;span class="k"&gt;fi
    fi

    &lt;/span&gt;setfacl &lt;span class="nt"&gt;-R&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; u:www-data:rwX &lt;span class="nt"&gt;-m&lt;/span&gt; u:&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;whoami&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;:rwX var
    setfacl &lt;span class="nt"&gt;-dR&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; u:www-data:rwX &lt;span class="nt"&gt;-m&lt;/span&gt; u:&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;whoami&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;:rwX var
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;exec &lt;/span&gt;docker-php-entrypoint &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is exactly the same provided by the Symfony part of the FrankenPHP documentation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure Frankenphp
&lt;/h3&gt;

&lt;p&gt;FrankenPHP use &lt;a href="https://caddyserver.com/" rel="noopener noreferrer"&gt;Caddy&lt;/a&gt; as proxy server, so we'll need a Caddyfile to configure it and also provide basic php configurations. Here again we'll stick to the FrankenPHP documention. You can find it in the &lt;code&gt;docker/frankenphp&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;By default FrankenPHP will work on worker mode, by launching two proc by CPU core which can be tweak according to your project needs and also your hosting type.&lt;/p&gt;

&lt;h2&gt;
  
  
  Symfony project
&lt;/h2&gt;

&lt;p&gt;Here's a minimal list of dependencies to offer a first class developer experience. But first thing first we need to install the FrankenPHP runtime, the same we configure on the worker.Caddyfile configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;worker {
    file ./public/index.php
    env APP_RUNTIME Runtime\FrankenPhpSymfony\Runtime
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To do so simply install the &lt;a href="https://github.com/php-runtime/frankenphp-symfony" rel="noopener noreferrer"&gt;runtime/frankenphp-symfony&lt;/a&gt; composer package. Then we install the bare minimum for a kick ass developer experience, a linter using &lt;a href="https://github.com/PHPCSStandards/PHP_CodeSniffer/" rel="noopener noreferrer"&gt;Code Sniffer&lt;/a&gt;, &lt;a href="https://phpstan.org/" rel="noopener noreferrer"&gt;phpstan&lt;/a&gt; as code quality audit tool, &lt;a href="https://github.com/rectorphp/rector" rel="noopener noreferrer"&gt;Rector&lt;/a&gt; to ease and automate code maintenance, some useful Symfony components and package and of course the Doctrine ORM. Here the &lt;code&gt;composer.json&lt;/code&gt; file located at the symfony folder root.&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"project"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"license"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"proprietary"&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;"stable"&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="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;"&amp;gt;=8.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"ext-ctype"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"ext-iconv"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"doctrine/dbal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^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;"doctrine/doctrine-bundle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^2.13"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"doctrine/doctrine-migrations-bundle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^3.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;"doctrine/orm"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^3.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"nelmio/cors-bundle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^2.5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"phpdocumentor/reflection-docblock"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^5.4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"phpstan/phpdoc-parser"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^1.30"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"ramsey/uuid-doctrine"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^2.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"runtime/frankenphp-symfony"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^0.2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"symfony/asset"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"7.2.*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"symfony/console"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"7.2.*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"symfony/dotenv"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"7.2.*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"symfony/expression-language"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"7.2.*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"symfony/flex"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"symfony/framework-bundle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"7.2.*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"symfony/password-hasher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"7.2.*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"symfony/property-access"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"7.2.*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"symfony/property-info"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"7.2.*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"symfony/runtime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"7.2.*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"symfony/security-bundle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"7.2.*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"symfony/serializer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"7.2.*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"symfony/twig-bundle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"7.2.*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"symfony/validator"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"7.2.*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"symfony/yaml"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"7.2.*"&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;"require-dev"&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;"doctrine/doctrine-fixtures-bundle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^3.5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"friendsofphp/php-cs-fixer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^3.65"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"phpunit/phpunit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^9.5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"rector/rector"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^1.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"symfony/browser-kit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"7.2.*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"symfony/css-selector"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"7.2.*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"symfony/maker-bundle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^1.61"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"symfony/phpunit-bridge"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^7.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"symfony/stopwatch"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"7.2.*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"symfony/var-dumper"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"7.2.*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"symfony/web-profiler-bundle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"7.2.*"&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;"config"&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;"allow-plugins"&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-http/discovery"&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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"symfony/flex"&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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"symfony/runtime"&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;span class="nl"&gt;"sort-packages"&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;span class="nl"&gt;"autoload"&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;"psr-4"&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;"App\\"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"src/"&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="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"autoload-dev"&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;"psr-4"&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;"App\\Tests\\"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tests/"&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="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"replace"&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;"symfony/polyfill-ctype"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"symfony/polyfill-iconv"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"symfony/polyfill-php72"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"symfony/polyfill-php73"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"symfony/polyfill-php74"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"symfony/polyfill-php80"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"symfony/polyfill-php81"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"scripts"&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;"auto-scripts"&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;"cache:clear"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"symfony-cmd"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"assets:install %PUBLIC_DIR%"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"symfony-cmd"&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;"post-install-cmd"&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="s2"&gt;"@auto-scripts"&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;"post-update-cmd"&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="s2"&gt;"@auto-scripts"&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="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"conflict"&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;"symfony/symfony"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"extra"&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;"symfony"&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;"allow-contrib"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&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="s2"&gt;"7.2.*"&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why using the latest version of Symfony 7.2 and not waiting on the last LTS which is the 6.4 for now? Because this is not the way Symfony behave, let's listen to what &lt;a href="https://x.com/nicolasgrekas" rel="noopener noreferrer"&gt;Nicolas Grekas&lt;/a&gt; said the Forum PHP 2024 event:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/6z4DgJn8qmM"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;so first thing first, it will be more easy to maintain a project by updating it once monthly than migrating from the last LTS to the next one, which can be in some project very painless and time consuming. Tool like Rector can really help by the way this kind of migration and many others too. &lt;/p&gt;

&lt;h2&gt;
  
  
  Leverage all the power of Composer package manager
&lt;/h2&gt;

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

&lt;p&gt;In this project &lt;a href="https://getcomposer.org/" rel="noopener noreferrer"&gt;Composer&lt;/a&gt; is used to handle class autoloading, dependencies and also manage the project itself. Lets add a set of usefull scripts in the dedicated section of our &lt;code&gt;composer.json&lt;/code&gt;configuration file.&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="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&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="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;

        &lt;/span&gt;&lt;span class="nl"&gt;"setup"&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="s2"&gt;"composer run up"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"composer run deps:install"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"composer run database"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"composer run migrate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"composer run fixtures"&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;"up"&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="s2"&gt;"docker compose --env-file symfony/.env up -d --build"&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;"stop"&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="s2"&gt;"docker compose --env-file symfony/.env stop"&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;"down"&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="s2"&gt;"docker compose --env-file symfony/.env down"&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;"build"&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="s2"&gt;"docker compose --env-file symfony/.env build"&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;"deps:install"&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="s2"&gt;"docker exec -it boilerplate-app bin/composer install -o"&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;"database"&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="s2"&gt;"docker exec -it boilerplate-app bin/console doctrine:database:create -n --if-not-exists"&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;"migrate"&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="s2"&gt;"docker exec -it boilerplate-app bin/console doctrine:migration:migrate -n"&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;"fixtures"&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="s2"&gt;"docker exec -it boilerplate-app bin/console doctrine:fixtures:load -n"&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;"tests"&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="s2"&gt;"Composer&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;Config::disableProcessTimeout"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"docker exec -t boilerplate-app bash -c 'clear &amp;amp;&amp;amp; ./vendor/bin/phpunit --testdox --exclude=smoke'"&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;"lint"&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="s2"&gt;"docker exec -t boilerplate-app ./vendor/bin/php-cs-fixer ./src/"&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;"lint:fix"&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="s2"&gt;"docker exec -t boilerplate-app ./vendor/bin/php-cs-fixer fix ./src/"&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;"db"&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="s2"&gt;"Composer&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;Config::disableProcessTimeout"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"psql postgresql://postgres:password@127.0.0.1:15432/boilerplate"&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;"logs"&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="s2"&gt;"Composer&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;Config::disableProcessTimeout"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"docker compose logs -f"&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;"generate-keypair"&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="s2"&gt;"docker exec -t boilerplate-app bin/console lexik:jwt:generate-keypair"&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;"cache-clear"&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="s2"&gt;"docker exec -t boilerplate-app bin/console c:c"&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's an overview of available composer commands:&lt;/p&gt;

&lt;p&gt;To setup project's containers simply run:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Once built, you can start or stop project's containers like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer up
&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;composer stop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Destroys containers (but keep volumes)&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Migrate database&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Load fixtures&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Connect to postgresql database&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Show logs&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Fix code lint&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer lint:fix
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;The Composer\Config::disableProcessTimeout allow some composer scripts to disable the default process timeout behavior.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Optimize for production
&lt;/h2&gt;

&lt;p&gt;Symfony development mode cost a lot by caching nothing and add a lot of debug everywhere. On production environment we will use OPCache to store in cache class content, dump composer class autoload in a more optimized way and get rid of development related dependencies. You can find on the &lt;code&gt;docker/frankephp/conf.d&lt;/code&gt; the different configurations for php.&lt;/p&gt;

&lt;p&gt;First let's add a production specific stage on our Dockerfile&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Prod&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;frankenphp_base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;frankenphp_prod&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; APP_ENV=prod&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; FRANKENPHP_CONFIG="import worker.Caddyfile"&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mv&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PHP_INI_DIR&lt;/span&gt;&lt;span class="s2"&gt;/php.ini-production"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PHP_INI_DIR&lt;/span&gt;&lt;span class="s2"&gt;/php.ini"&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --link docker/frankenphp/conf.d/app.prod.ini $PHP_INI_DIR/conf.d/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --link docker/frankenphp/worker.Caddyfile /etc/caddy/worker.Caddyfile&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; symfony/ .&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-eux&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    composer &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache&lt;/span&gt; &lt;span class="nt"&gt;--prefer-dist&lt;/span&gt; &lt;span class="nt"&gt;--no-dev&lt;/span&gt; &lt;span class="nt"&gt;--no-autoloader&lt;/span&gt; &lt;span class="nt"&gt;--no-scripts&lt;/span&gt; &lt;span class="nt"&gt;--no-progress&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-eux&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; var/cache var/log&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    composer dump-autoload &lt;span class="nt"&gt;--classmap-authoritative&lt;/span&gt; &lt;span class="nt"&gt;--no-dev&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    composer dump-env prod&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    composer run-script &lt;span class="nt"&gt;--no-dev&lt;/span&gt; post-install-cmd&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;chmod&lt;/span&gt; +x bin/console&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; [ "frankenphp", "run", "--config", "/etc/caddy/Caddyfile", "--watch" ]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then create a specific override for compose to use in production, at the root of the project create a new composer.override.prod.yml with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Development environment override&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;boilerplate-app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./docker/api/Dockerfile&lt;/span&gt;
      &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;frankenphp_prod&lt;/span&gt;
    &lt;span class="na"&gt;expose&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./docker/frankenphp/Caddyfile:/etc/caddy/Caddyfile:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./docker/frankenphp/conf.d/app.prod.ini:/usr/local/etc/php/conf.d/app.prod.ini:ro&lt;/span&gt;
      &lt;span class="c1"&gt;# If you develop on Mac or Windows you can remove the vendor/ directory&lt;/span&gt;
      &lt;span class="c1"&gt;#  from the bind-mount for better performance by enabling the next line:&lt;/span&gt;
      &lt;span class="c1"&gt;#- /app/vendor&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;SERVER_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${SERVER_NAME:-http://api.nbonnici.info}, boilerplate-app:80&lt;/span&gt;
      &lt;span class="na"&gt;MERCURE_EXTRA_DIRECTIVES&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;demo&lt;/span&gt;
      &lt;span class="c1"&gt;# See https://xdebug.org/docs/all_settings#mode&lt;/span&gt;
      &lt;span class="na"&gt;XDEBUG_MODE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${XDEBUG_MODE:-off}"&lt;/span&gt;
    &lt;span class="na"&gt;extra_hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# Ensure that host.docker.internal is correctly defined on Linux&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;host.docker.internal:host-gateway&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The purpose is to specify the new build target which is now &lt;code&gt;frankenphp_prod&lt;/code&gt; and only expose the http port of the container without any forwarding. This is this spcecific port 80 on your container that your hostname with SSL support will target after a reverse proxy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benchmark
&lt;/h2&gt;

&lt;p&gt;Now it's time to benchmark, is FrankenPHP as blazing fast as most people stated? Short answer yes it is, but each project having his own needs you'll need to tweak it, and it's very flexible so no problem doing it.&lt;/p&gt;

&lt;p&gt;For this test we'll create a dead simple Todo entity containing a few columns and a foreign key to our User entity.&lt;/p&gt;

&lt;p&gt;Using fixtures we will create one thousand of todos and using the top tier REST api creation bundle API Platform we will load them in json format.&lt;/p&gt;

&lt;p&gt;For this test i'am using a local docker container on a 11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz CPU with 16go of RAM.&lt;/p&gt;

&lt;p&gt;The test itself consist to make an HTTP request to a RESTFUL API to retrieve a collection of todo resources, with more items per page from a page of 10 to 1000.&lt;/p&gt;

&lt;p&gt;So the project must route the request then use API Platform layers and the ORM to query from the database the todos, then serialize the objects in a json response.&lt;/p&gt;

&lt;p&gt;The container directly run on the host I send the request so there's almost no network latency and I use &lt;a href="https://insomnia.rest/" rel="noopener noreferrer"&gt;Insomnia&lt;/a&gt; to measure the response time.&lt;/p&gt;

&lt;h3&gt;
  
  
  GET /todos 10 / 50 / 100 / 500 / 1000 resource in &lt;strong&gt;dev staging&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Page of 10 resources: &lt;strong&gt;177 ms&lt;/strong&gt;&lt;br&gt;
Page of 50 resources: &lt;strong&gt;188 ms&lt;/strong&gt;&lt;br&gt;
Page of 100 resources: &lt;strong&gt;211 ms&lt;/strong&gt;&lt;br&gt;
Page of 500 resources: &lt;strong&gt;259 ms&lt;/strong&gt;&lt;br&gt;
Page of 1000 resources: &lt;strong&gt;346 ms&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  GET 10 / 50 / 100 / 500 / 1000 resource in &lt;strong&gt;production staging&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Page of 10 resources: &lt;strong&gt;9.03 ms&lt;/strong&gt;&lt;br&gt;
Page of 50 resources: &lt;strong&gt;15.1 ms&lt;/strong&gt;&lt;br&gt;
Page of 100 resources: &lt;strong&gt;29.2 ms&lt;/strong&gt;&lt;br&gt;
Page of 500 resources: &lt;strong&gt;106 ms&lt;/strong&gt;&lt;br&gt;
Page of 1000 resources: &lt;strong&gt;170 ms&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;Conclusion the gap is huge, between development and production nothing new here. By creating the same REST API without Symfony and API Platform and all the confort they bring you can win let's say a few milliseconds more which is totally nothing and almost impossible to detect from a human perception. Frankenphp per default using mecanism such like &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/103" rel="noopener noreferrer"&gt;early hint http code&lt;/a&gt;, the go routines and many modern and blazing fast concept can really improve your project performances.&lt;/p&gt;

&lt;h2&gt;
  
  
  Going further
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Security notes
&lt;/h3&gt;

&lt;p&gt;We can secure things a little more by not using root user container side, this is a bad practice.&lt;/p&gt;

&lt;p&gt;To do so, we need to follow the &lt;a href="https://frankenphp.dev/docs/docker/#running-as-a-non-root-user" rel="noopener noreferrer"&gt;official Frankenphp documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Migrate Symfony up to the incoming 7.4 LTS
&lt;/h3&gt;

&lt;p&gt;Here again keep your project freshly updated each month and also pay attention to the deprecated warning you can find.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuration
&lt;/h3&gt;

&lt;p&gt;All depend on your need, are you working on a CLI app, an API, a monolith? How do you host your app, on a cluster, just one bare metal onto lambda in all those cases you need to find the better settings by tweaking the worker thread number by core and also the right php configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This boilerplate can literally boot up any project from a monolith to a REST API, almost everything you can build with php and Symfony. Using top tier service like PostgreSQL and easily scalable using Kubernetes and Karpenter for instance, as well as a Gateway API to proxy and absorb mostly GET http incoming requests for high demand projects. You can also use it to migrate an existing project to Frankenphp.&lt;/p&gt;

&lt;p&gt;You can find the &lt;a href="https://gitlab.com/nicolasbonnici/symfony-7-frankenphp-boilerplate/" rel="noopener noreferrer"&gt;final boilerplate source code here on Gitlab&lt;/a&gt;. Feel free to contribute on it, i will maintain and update this post as well as the boilerplate, thank you for reading.&lt;/p&gt;

</description>
      <category>php</category>
      <category>docker</category>
      <category>symfony</category>
      <category>frankenphp</category>
    </item>
    <item>
      <title>Efficient code review process</title>
      <dc:creator>Nicolas Bonnici</dc:creator>
      <pubDate>Mon, 09 Sep 2024 05:46:59 +0000</pubDate>
      <link>https://dev.to/nicolasbonnici/how-to-code-review-efficiency-5bm</link>
      <guid>https://dev.to/nicolasbonnici/how-to-code-review-efficiency-5bm</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffh6eprjnky5t4b91xzrf.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffh6eprjnky5t4b91xzrf.jpg" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Welcome. If you're here reading this post, it means you already contributed to some projects where you were not alone, or you plan to. If you want to know more about what we call code review (aka code contribution reviewed by peers before merging it into a common trunk), read on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mindset
&lt;/h2&gt;

&lt;p&gt;In this process, the main purpose is to challenge a technical response to a problem within the project context.&lt;/p&gt;

&lt;p&gt;If a reviewer suggests another approach, you must ask yourself: is it a better approach than yours? If the project can benefit from the suggested change in any domain—user experience, developer experience, security, performance, or code clarity—you must consider it seriously and push the requested changes. Otherwise, at least ask for more information and, if needed, engage in a direct discussion.&lt;/p&gt;

&lt;p&gt;The whole process should be taken as nothing but a win-win. The more care you put into the review process, the more the produced code quality will improve, and thus the overall project quality will benefit as well.&lt;/p&gt;

&lt;p&gt;Code hygiene and stability are the main goals. You guarantee best practices, security, maintainability, and performance. Sometimes, on some projects, this is not already the case and you have to maintain a legacy project anyway. Apply strictly the same process to all new contributions; then you can leverage a technical roadmap to clean what needs to be cleaned.&lt;/p&gt;

&lt;p&gt;Of course, but it’s important to mention again: be respectful, kind, and never get upset—unless you're Linus Torvalds. &lt;strong&gt;Be open to suggested changes.&lt;/strong&gt;&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Interact with the reviewed developer
&lt;/h2&gt;

&lt;p&gt;Using the most popular Git web interfaces such as GitHub or GitLab, there are many ways to interact with the code author. Sometimes the changes you request are not mandatory or can be postponed on a technical roadmap. Sometimes the contribution will directly alter the project's code quality, security, or performance at merge time, so they must be addressed beforehand.&lt;/p&gt;

&lt;p&gt;When you open a thread and ask the author to implicitly push some changes, a suggestion is always easier to understand than a long explanation without one.&lt;/p&gt;

&lt;p&gt;It's your responsibility to follow up and resolve threads as soon as possible to avoid blocking the next steps in the process. Using common web apps such as GitHub or GitLab, you have administration panels where all your pending reviews can be found. Be sure to use those pages: on GitLab it's &lt;a href="https://gitlab.com/dashboard/todos" rel="noopener noreferrer"&gt;https://gitlab.com/dashboard/todos&lt;/a&gt; and on GitHub &lt;a href="https://github.com/pulls/review-requested" rel="noopener noreferrer"&gt;https://github.com/pulls/review-requested&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Sometimes &lt;strong&gt;the best way to understand each other is to simply start talking in person&lt;/strong&gt; or in a one-to-one online chat.&lt;/p&gt;

&lt;h1&gt;
  
  
  Project size considerations
&lt;/h1&gt;

&lt;p&gt;The more contributors your project has, the stricter the review and merge process must be. Be careful not to be overkill, but also not to be too permissive.&lt;/p&gt;

&lt;p&gt;At least 20% positive reviews from all contributors, combined with robust CI steps such as code audits, linting, and tests, should be sufficient. You can leverage role concepts in Git web apps to ensure at least one core maintainer approval for projects with many contributors.&lt;/p&gt;

&lt;p&gt;Again, your process, your rules—just make sure they are documented somewhere. Pull/merge request title formats, description metadata, and any other rules are the foundation of this process.&lt;/p&gt;

&lt;h2&gt;
  
  
  The process
&lt;/h2&gt;

&lt;p&gt;First of all, &lt;strong&gt;the smaller the contributions are, the better this process works&lt;/strong&gt;. Avoid TLTR reviews (Too Long To Read) by splitting task scopes into small chunks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;First of all, if you take time to review a merge request, the developer who assigned you must deliver a stable branch and a finished contribution. All CI and code quality steps must be green.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Another important point is to &lt;strong&gt;check that the reviewed code is correctly rebased on top of the target branch HEAD&lt;/strong&gt; and contains no conflicts at all. Otherwise, ask the contributor to rebase their branch before reviewing the code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The code lint must be valid, all kinds of test suites must pass, and all code quality analysis tools must succeed. Otherwise, ask the developer for related changes before reviewing the code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Now it's time to analyze the changes: which files were edited or added? You need a global point of view first. &lt;strong&gt;The more context the developer adds—such as explanations, links to tickets, or even media like videos or screen captures—the better the reviewer will understand the purpose of the code.&lt;/strong&gt; Document or override Git commit templates to set the standards your team needs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Does the code add a new feature or modify an existing one? Is it a fix? &lt;strong&gt;In any case, the code should be backed by tests to ensure it works as expected, fails correctly, and does not regress later.&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Is this contribution sufficiently &lt;strong&gt;documented&lt;/strong&gt;? Is the specification properly updated?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Does the contribution properly &lt;strong&gt;handle errors&lt;/strong&gt; or anything that could break normal code behavior? Is it possible to understand application behavior through logs?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Can this contribution be &lt;strong&gt;simplified&lt;/strong&gt; in any way?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Is this contribution at &lt;strong&gt;the right abstraction level&lt;/strong&gt;?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Does this project have solid code analysis steps? Is the test coverage at least 80%, and are critical user stories tested end to end? &lt;strong&gt;If not, you must check out the branch locally and rebuild it from scratch.&lt;/strong&gt; Ensure all available tools for testing, quality, and linting are used. &lt;strong&gt;A clean CI/CD pipeline is the core of a clean project.&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Does this code alter the database or persistence layer? &lt;strong&gt;Is the migration process safe when the CD pipeline applies it to production with existing large datasets?&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Can this contribution &lt;strong&gt;expose unwanted sensitive data&lt;/strong&gt;?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Does this code alter global project performance?&lt;/strong&gt; Is there a risk with high data volumes? Are new database indexes needed? Will it scale?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Is this contribution safe, or does it introduce a new security flaw?&lt;/strong&gt; Bad practices, potential XSS vulnerabilities, unsafe secrets committed to Git, or backdoors introduced by malicious contributors. Security tools should be part of your CI pipeline.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Does the project have contribution rules (often in a &lt;code&gt;CONTRIBUTING.md&lt;/code&gt; file)? Does this contribution respect them?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Do and don't
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Do
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Respect the checklist above and review code only when the contributor considers it ready.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Take the time to review thoroughly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ask the developer to flag their contribution as WIP or draft if the code is not ready.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add context to your review threads. Sometimes your request is not obvious. Saying something is “cleaner” or “better” without explanation is not helpful. Example: “Can you split this service into two—one in domain X and one in domain Y—to avoid mixing responsibilities?”&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ask for small contributions. Split tasks into small chunks to avoid TLTR reviews.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Always assign the best possible reviewers. Find experts for each part of the stack you touch.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Don't
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Assume a developer is so senior that you can approve blindly. No one is perfect—even AI makes mistakes. Doing so defeats the purpose of code review.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Review WIP code. Such contributions should be marked as drafts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Approve untested contributions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Approve commented-out code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Approve unused code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Approve something you don’t understand.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Approve hacks or workarounds instead of proper fixes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Repeat yourself for typos. If a typo appears everywhere, point it out once instead of opening dozens of threads. It wastes time and helps no one.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Gatekeeping
&lt;/h2&gt;

&lt;p&gt;One phenomenon you will encounter is known as gatekeeping—in other words, &lt;strong&gt;someone being extremely strict about contributions, or wanting everyone to code exactly the way they do.&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;The best way to deal with gatekeeping is to define strict rules and document them, especially around contributions.&lt;br&gt;&lt;br&gt;
You can also improve developer experience with code quality rules, templates, linting, and automated checks directly in your CI process.&lt;/p&gt;

&lt;p&gt;Most of the time, gatekeepers are more about rigid adherence to project rules. If it concerns project rules rather than personal preferences, thank that reviewer—they are fulfilling the core purpose of this process.&lt;/p&gt;

&lt;h1&gt;
  
  
  Word of end
&lt;/h1&gt;

&lt;p&gt;Keep in mind that code reviews are one of the best and fastest ways to grow as a developer. A single thoughtful comment from a generous reviewer can significantly improve your skills.&lt;/p&gt;

&lt;p&gt;Thank you for reading. Feel free to share your own best practices for reviewing code in the comments below.&lt;/p&gt;

</description>
      <category>code</category>
      <category>teamwork</category>
      <category>codequality</category>
      <category>review</category>
    </item>
    <item>
      <title>Dockerized Symfony 6.4 project boilerplate</title>
      <dc:creator>Nicolas Bonnici</dc:creator>
      <pubDate>Sat, 17 Feb 2024 16:12:37 +0000</pubDate>
      <link>https://dev.to/nicolasbonnici/dockerized-symfony-64-project-boilerplate-3629</link>
      <guid>https://dev.to/nicolasbonnici/dockerized-symfony-64-project-boilerplate-3629</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Repository and post updated &lt;strong&gt;September 22 2024&lt;/strong&gt; &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The perfect Dockerized Symfony 6.4 boilerplate doesn't exists... But wait, what if I create and share my own vision of it?!!&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Leggooo
&lt;/h2&gt;

&lt;p&gt;We gonna build a classic stack that i personaly love, with such cutting edge technologies like PostgreSQL 16, Redis and Nginx HTTP server and also PHP 8.3 and FPM and Symfony framwork in LTS version 6.4. &lt;/p&gt;

&lt;p&gt;First of all, I want something really easy to use, so let's leverage docker compose plugin without any extra argument to start and initialize all needed containers.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Project treeview&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;logs&lt;/li&gt;
&lt;li&gt;docker&lt;/li&gt;
&lt;li&gt;symfony&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything is at project root to do so, no fancy parameter needed since everything use default project path convention.&lt;/p&gt;

&lt;p&gt;Simply 3 folders, the first one "docker/" to store all Docker related configurations, one other "logs/" for the whole containers logs and a "symfony/" last one directory to put your Symfony project's source code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Go further
&lt;/h2&gt;

&lt;p&gt;Good but wait I have other developers to sync in that project and do they can scaffold it on their local development env without knowing any clue about Symfony and server side development or never work on containerized projects?&lt;/p&gt;

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

&lt;p&gt;Nope just kidding, ain't no voodoo involved in that process, we're not gonna reinvent the wheel and make a revolution. Not at all, just a simple leverage of composer and more specially his &lt;code&gt;scripts&lt;/code&gt; section.&lt;/p&gt;

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

&lt;p&gt;Ok if you don't rage quit this post or punch directly your screen you'll enjoy a little magic, with this simple one line command to run all needed containers then setup the database, create the data model before populating it if you agree the prompt by responding "yes" at the end. &lt;/p&gt;

&lt;p&gt;All that using one dead simple command:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Explanation we first build and launch all needed containers, the run them in demon mode, this the first part of the command: &lt;code&gt;docker compose up --build -d&lt;/code&gt;. The second part will then run the "install-project" composer script. See the &lt;code&gt;symfony/composer.json&lt;/code&gt; "scripts" section.&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;"scripts"&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="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"setup"&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="s2"&gt;"composer run up"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"composer run deps:install"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"composer run database"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"composer run migrate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"composer run fixtures"&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;"up"&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="s2"&gt;"docker compose --env-file symfony/.env up -d --build"&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;"down"&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="s2"&gt;"docker compose --env-file symfony/.env down"&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;"stop"&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="s2"&gt;"docker compose --env-file symfony/.env stop"&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;"build"&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="s2"&gt;"docker compose --env-file symfony/.env build"&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;"deps:install"&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="s2"&gt;"docker exec -it php-fpm bin/composer install -o"&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;"database"&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="s2"&gt;"docker exec -it php-fpm bin/console doctrine:database:create -n --if-not-exists"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"docker exec -it php-fpm bin/console doctrine:database:create -n --if-not-exists --env=test"&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;"migrate"&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="s2"&gt;"docker exec -it php-fpm bin/console doctrine:migration:migrate -n"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"docker exec -it php-fpm bin/console doctrine:migration:migrate -n --env=test"&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;"fixtures"&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="s2"&gt;"docker exec -it php-fpm bin/console doctrine:fixtures:load -n"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"docker exec -it php-fpm bin/console doctrine:fixtures:load -n --env=test"&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;"tests"&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="s2"&gt;"docker exec -t php-fpm bash -c 'clear &amp;amp;&amp;amp; ./vendor/bin/phpunit --testdox --exclude=smoke'"&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;"lint"&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="s2"&gt;"docker exec -t php-fpm ./vendor/bin/php-cs-fixer ./src/"&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;"lint:fix"&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="s2"&gt;"docker exec -t php-fpm ./vendor/bin/php-cs-fixer fix ./src/"&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;"db"&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="s2"&gt;"psql postgresql://postgres:password@127.0.0.1:15432/dbtest"&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;"logs"&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="s2"&gt;"docker compose logs -f"&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;"cache-clear"&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="s2"&gt;"docker exec -t php-fpm bin/console c:c"&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will first install all needed composer dependencies and optimize classes autoloader. Then execute migrations up to the latest version then populate database with fixtures respectively with "doctrine/doctrine-migrations-bundle" and "doctrine/doctrine-fixtures-bundle" bundles.&lt;/p&gt;

&lt;p&gt;The actual composer scripts available:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;composer [
    setup
    up
    stop
    build
    deps:install
    database
    migrate
    fixtures
    tests: launch phpunit tests suite
    lint
    db: connect to database container via CLI client 
    logs: show containers logs
    cache-clear
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Talk is cheap, show me the code
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmz9s2llyfl085hlvo0fz.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmz9s2llyfl085hlvo0fz.jpg" alt="Linus Torvalds famous quote: Talk is cheap, show me the code" width="800" height="376"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Feel free to fork, contribute and maintain this boilerplate using this Gitlab repository: &lt;a href="https://gitlab.com/nicolasbonnici/symfony-docker-boilerplate" rel="noopener noreferrer"&gt;nicolasbonnici/symfony-docker-boilerplate&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>webdev</category>
      <category>php</category>
      <category>symfony</category>
      <category>docker</category>
    </item>
    <item>
      <title>Dockerize Nextcloud with Postgre and Redis</title>
      <dc:creator>Nicolas Bonnici</dc:creator>
      <pubDate>Fri, 11 Nov 2022 23:02:09 +0000</pubDate>
      <link>https://dev.to/nicolasbonnici/dockerize-nextcloud-with-postgre-and-redis-16jl</link>
      <guid>https://dev.to/nicolasbonnici/dockerize-nextcloud-with-postgre-and-redis-16jl</guid>
      <description>&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;In this post we gonna build from scratch a dockerized Nextcloud instance. &lt;a href="https://nextcloud.com/" rel="noopener noreferrer"&gt;Nextcloud&lt;/a&gt; is a powerfull self hosted collaborative cloud platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Nextcloud 24.0.7&lt;/li&gt;
&lt;li&gt;PostgreSQL for the database&lt;/li&gt;
&lt;li&gt;Redis for cache&lt;/li&gt;
&lt;li&gt;Nginx as proxy&lt;/li&gt;
&lt;li&gt;Out of the box A+ SSL grade&lt;/li&gt;
&lt;li&gt;Easy backup&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We gonna use a &lt;a href="https://www.postgresql.org/" rel="noopener noreferrer"&gt;PostgreSQL&lt;/a&gt; 14 database container in our stack. We also create another container running a &lt;a href="https://redis.io/" rel="noopener noreferrer"&gt;Redis&lt;/a&gt; instance for cache purposes. &lt;/p&gt;

&lt;p&gt;Since Nextcloud is a web app, the frontend will be serve by an Nginx container in front of a reverse proxy to PHP FPM. We also need a to run Nextcloud related commands or cron job using an another container connected to the same volume.&lt;/p&gt;

&lt;p&gt;We'll also need some other containers to generate and also renew the SSL certificate using &lt;a href="https://letsencrypt.org/" rel="noopener noreferrer"&gt;Letsencrypt&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Legggo!
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Docker compose
&lt;/h2&gt;

&lt;p&gt;To build this complexe stack let's leverage the power of docker-compose and begin to write the docker-compose YAML file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3'&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nc-db&lt;/span&gt;
    &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nc_db&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:14&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${DATABASE_NAME}&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${DATABASE_PWD}&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db:/var/lib/postgresql/data&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;15432:5432"&lt;/span&gt;

  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nc-app&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;nextcloud:/usr/src/nextcloud&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_HOST=${DATABASE_HOST}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_DB=${DATABASE_NAME}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_USER=${DATABASE_USER}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD=${DATABASE_PWD}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;REDIS_HOST=${REDIS_HOST}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;REDIS_HOST_PASSWORD=${REDIS_PASSWORD}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DEFAULT_PHONE_REGION=FR&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;

  &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nc-cron&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nextcloud:fpm-alpine&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;nextcloud:/var/www/html&lt;/span&gt;
    &lt;span class="na"&gt;entrypoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/cron.sh&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;

  &lt;span class="na"&gt;nc-web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nc-web&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./nginx&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;VIRTUAL_HOST=${CLOUD_HOST}&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./nginx/conf.d/:/etc/nginx/conf.d&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./certbot/conf/:/etc/nginx/ssl/:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./certbot/www:/var/www/certbot/:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;nextcloud:/var/www/html&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./nginx/nginx.conf:/etc/nginx/nginx.conf&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./nginx/sites/:/etc/nginx/sites-available&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./nginx/logs:/var/log/nginx&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/var/run/docker.sock:/tmp/docker.sock:ro&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;proxy-tier&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8888:80"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;443:443"&lt;/span&gt;

  &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nc-redis&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redis:alpine"&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis-server --requirepass ${REDIS_PASSWORD}&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;6379:6379"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./redis/data:/var/lib/redis&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./redis/redis.conf:/usr/local/etc/redis/redis.conf&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;REDIS_REPLICATION_MODE=master&lt;/span&gt;

  &lt;span class="na"&gt;certbot&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nc-certbot&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;certbot/certbot:latest&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./certbot/www/:/var/www/certbot/:rw&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./certbot/conf/:/etc/letsencrypt/:rw&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;proxy-tier&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;nc-web&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;nextcloud&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;proxy-tier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing fancy here, we configure all the needed containers and mount handful volumes for configuration and logs directly to the host file system.&lt;/p&gt;

&lt;p&gt;Environment variables like &lt;code&gt;DATABASE_HOST&lt;/code&gt; will be resolved using dotenv, something like this for example IN &lt;code&gt;.env&lt;/code&gt; at project root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CLOUD_HOST=cloud.example.localhost

DATABASE_HOST=nc_db
DATABASE_NAME=nextcloud
DATABASE_USER=postgres
DATABASE_PWD=example!

REDIS_HOST=redis
REDIS_PASSWORD=example!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Lets break this in smaller parts
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Nextcloud
&lt;/h2&gt;

&lt;p&gt;First let's talk about the Nextcloud container, here's his DockerFile largely inspired by the official documentation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# DO NOT EDIT: created by update.sh from Dockerfile-alpine.template&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; php:8.0-fpm-alpine3.16&lt;/span&gt;

&lt;span class="c"&gt;# entrypoint.sh and cron.sh dependencies&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-ex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="se"&gt;\
&lt;/span&gt;    apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;        rsync &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;rm&lt;/span&gt; /var/spool/cron/crontabs/root&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'*/5 * * * * php -f /var/www/html/cron.php'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /var/spool/cron/crontabs/www-data

&lt;span class="c"&gt;# install the PHP extensions we need&lt;/span&gt;
&lt;span class="c"&gt;# see https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-ex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="se"&gt;\
&lt;/span&gt;    apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; &lt;span class="nt"&gt;--virtual&lt;/span&gt; .build-deps &lt;span class="se"&gt;\
&lt;/span&gt;        &lt;span class="nv"&gt;$PHPIZE_DEPS&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;        autoconf &lt;span class="se"&gt;\
&lt;/span&gt;        freetype-dev &lt;span class="se"&gt;\
&lt;/span&gt;        icu-dev &lt;span class="se"&gt;\
&lt;/span&gt;        libevent-dev &lt;span class="se"&gt;\
&lt;/span&gt;        libjpeg-turbo-dev &lt;span class="se"&gt;\
&lt;/span&gt;        libmcrypt-dev &lt;span class="se"&gt;\
&lt;/span&gt;        libpng-dev &lt;span class="se"&gt;\
&lt;/span&gt;        libmemcached-dev &lt;span class="se"&gt;\
&lt;/span&gt;        libxml2-dev &lt;span class="se"&gt;\
&lt;/span&gt;        libzip-dev &lt;span class="se"&gt;\
&lt;/span&gt;        openldap-dev &lt;span class="se"&gt;\
&lt;/span&gt;        pcre-dev &lt;span class="se"&gt;\
&lt;/span&gt;        postgresql-dev &lt;span class="se"&gt;\
&lt;/span&gt;        imagemagick-dev &lt;span class="se"&gt;\
&lt;/span&gt;        libwebp-dev &lt;span class="se"&gt;\
&lt;/span&gt;        gmp-dev &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="se"&gt;\
&lt;/span&gt;    docker-php-ext-configure gd &lt;span class="nt"&gt;--with-freetype&lt;/span&gt; &lt;span class="nt"&gt;--with-jpeg&lt;/span&gt; &lt;span class="nt"&gt;--with-webp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    docker-php-ext-configure ldap&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    docker-php-ext-install &lt;span class="nt"&gt;-j&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;nproc&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;        bcmath &lt;span class="se"&gt;\
&lt;/span&gt;        exif &lt;span class="se"&gt;\
&lt;/span&gt;        gd &lt;span class="se"&gt;\
&lt;/span&gt;        intl &lt;span class="se"&gt;\
&lt;/span&gt;        ldap &lt;span class="se"&gt;\
&lt;/span&gt;        opcache &lt;span class="se"&gt;\
&lt;/span&gt;        pcntl &lt;span class="se"&gt;\
&lt;/span&gt;        pdo_mysql &lt;span class="se"&gt;\
&lt;/span&gt;        pdo_pgsql &lt;span class="se"&gt;\
&lt;/span&gt;        zip &lt;span class="se"&gt;\
&lt;/span&gt;        gmp &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="se"&gt;\
&lt;/span&gt;&lt;span class="c"&gt;# pecl will claim success even if one install fails, so we need to perform each install separately&lt;/span&gt;
    pecl install APCu-5.1.21; \
    pecl install memcached-3.2.0; \
    pecl install redis-5.3.7; \
    pecl install imagick-3.7.0; \
    \
    docker-php-ext-enable \
        apcu \
        memcached \
        redis \
        imagick \
    ; \
    rm -r /tmp/pear; \
    \
    runDeps="$( \
        scanelf --needed --nobanner --format '%n&lt;span class="c"&gt;#p' --recursive /usr/local/lib/php/extensions \&lt;/span&gt;
            | tr ',' '\n' \
            | sort -u \
            | awk 'system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } { print "so:" $1 }' \
    )"; \
    apk add --virtual .nextcloud-phpext-rundeps $runDeps; \
    apk del .build-deps

&lt;span class="c"&gt;# set recommended PHP.ini settings&lt;/span&gt;
&lt;span class="c"&gt;# see https://docs.nextcloud.com/server/latest/admin_manual/installation/server_tuning.html#enable-php-opcache&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PHP_MEMORY_LIMIT 512M&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PHP_UPLOAD_LIMIT 10G&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'opcache.enable=1'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'opcache.interned_strings_buffer=16'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'opcache.max_accelerated_files=10000'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'opcache.memory_consumption=128'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'opcache.save_comments=1'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'opcache.revalidate_freq=60'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PHP_INI_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/conf.d/opcache-recommended.ini"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'apc.enable_cli=1'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PHP_INI_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/conf.d/docker-php-ext-apcu.ini"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'memory_limit=${PHP_MEMORY_LIMIT}'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'upload_max_filesize=${PHP_UPLOAD_LIMIT}'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'post_max_size=${PHP_UPLOAD_LIMIT}'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PHP_INI_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/conf.d/nextcloud.ini"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;mkdir&lt;/span&gt; /var/www/data&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; www-data:root /var/www&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;chmod&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; &lt;span class="nv"&gt;g&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;u /var/www

&lt;span class="k"&gt;VOLUME&lt;/span&gt;&lt;span class="s"&gt; /var/www/html&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NEXTCLOUD_VERSION 24.0.7&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-ex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; &lt;span class="nt"&gt;--virtual&lt;/span&gt; .fetch-deps &lt;span class="se"&gt;\
&lt;/span&gt;        bzip2 &lt;span class="se"&gt;\
&lt;/span&gt;        gnupg &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="se"&gt;\
&lt;/span&gt;    curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; nextcloud.tar.bz2 &lt;span class="se"&gt;\
&lt;/span&gt;        &lt;span class="s2"&gt;"https://download.nextcloud.com/server/releases/nextcloud-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;NEXTCLOUD_VERSION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.tar.bz2"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; nextcloud.tar.bz2.asc &lt;span class="se"&gt;\
&lt;/span&gt;        &lt;span class="s2"&gt;"https://download.nextcloud.com/server/releases/nextcloud-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;NEXTCLOUD_VERSION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.tar.bz2.asc"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GNUPGHOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;mktemp&lt;/span&gt; &lt;span class="nt"&gt;-d&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="se"&gt;\
&lt;/span&gt;&lt;span class="c"&gt;# gpg key from https://nextcloud.com/nextcloud.asc&lt;/span&gt;
    gpg --batch --keyserver keyserver.ubuntu.com  --recv-keys 28806A878AE423A28372792ED75899B9A724937A; \
    gpg --batch --verify nextcloud.tar.bz2.asc nextcloud.tar.bz2; \
    tar -xjf nextcloud.tar.bz2 -C /usr/src/; \
    gpgconf --kill all; \
    rm nextcloud.tar.bz2.asc nextcloud.tar.bz2; \
    rm -rf "$GNUPGHOME" /usr/src/nextcloud/updater; \
    mkdir -p /usr/src/nextcloud/data; \
    mkdir -p /usr/src/nextcloud/custom_apps; \
    chmod +x /usr/src/nextcloud/occ; \
    apk del .fetch-deps

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; *.sh upgrade.exclude /&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; config/* /usr/src/nextcloud/config/&lt;/span&gt;

&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["/entrypoint.sh"]&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["php-fpm"]&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 9000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we use the 24.0.7 Nextcloud version, to build a PHP-FPM container from &lt;code&gt;php:8.0-fpm-alpine3.16&lt;/code&gt;, install all needed dependencies, configure PHP then run the process on port 9000.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;As i write those lines, Nextcloud still don't support PHP 8.1 version&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We also configure PHP to accept upload up to 10go according to Nginx configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nginx
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.nginx.com/" rel="noopener noreferrer"&gt;Nginx&lt;/a&gt; main role will be to serve http/s requests from browser then forward the request to our previous PHP-FPM &lt;code&gt;nc-app&lt;/code&gt; container on port 9000.&lt;/p&gt;

&lt;p&gt;I chose to mount local folder nginx/logs to retrieve the access as well as error logs, the other volumes are useful to tweak the web server configuration.&lt;/p&gt;

&lt;p&gt;Here is the dead simple container Dockerfile, where the nginx instance will expose HTTP ports.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; nginx:alpine&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /var/www&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["nginx"]&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 80 443&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then here's the nginx.conf, with the global nginx configuration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;user&lt;/span&gt;  &lt;span class="n"&gt;nginx&lt;/span&gt;;
&lt;span class="n"&gt;worker_processes&lt;/span&gt;  &lt;span class="n"&gt;auto&lt;/span&gt;;
&lt;span class="n"&gt;daemon&lt;/span&gt; &lt;span class="n"&gt;off&lt;/span&gt;;

&lt;span class="n"&gt;error_log&lt;/span&gt;  /&lt;span class="n"&gt;var&lt;/span&gt;/&lt;span class="n"&gt;log&lt;/span&gt;/&lt;span class="n"&gt;nginx&lt;/span&gt;/&lt;span class="n"&gt;error&lt;/span&gt;.&lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="n"&gt;warn&lt;/span&gt;;
&lt;span class="n"&gt;pid&lt;/span&gt;        /&lt;span class="n"&gt;var&lt;/span&gt;/&lt;span class="n"&gt;run&lt;/span&gt;/&lt;span class="n"&gt;nginx&lt;/span&gt;.&lt;span class="n"&gt;pid&lt;/span&gt;;


&lt;span class="n"&gt;events&lt;/span&gt; {
    &lt;span class="n"&gt;worker_connections&lt;/span&gt;  &lt;span class="m"&gt;1024&lt;/span&gt;;
}


&lt;span class="n"&gt;http&lt;/span&gt; {
    &lt;span class="n"&gt;include&lt;/span&gt;       /&lt;span class="n"&gt;etc&lt;/span&gt;/&lt;span class="n"&gt;nginx&lt;/span&gt;/&lt;span class="n"&gt;mime&lt;/span&gt;.&lt;span class="n"&gt;types&lt;/span&gt;;
    &lt;span class="n"&gt;default_type&lt;/span&gt;  &lt;span class="n"&gt;application&lt;/span&gt;/&lt;span class="n"&gt;octet&lt;/span&gt;-&lt;span class="n"&gt;stream&lt;/span&gt;;

    &lt;span class="n"&gt;access_log&lt;/span&gt;  /&lt;span class="n"&gt;var&lt;/span&gt;/&lt;span class="n"&gt;log&lt;/span&gt;/&lt;span class="n"&gt;nginx&lt;/span&gt;/&lt;span class="n"&gt;access&lt;/span&gt;.&lt;span class="n"&gt;log&lt;/span&gt;;
    &lt;span class="c"&gt;#access_log /dev/stdout;
&lt;/span&gt;    &lt;span class="c"&gt;#error_log /dev/stderr;
&lt;/span&gt;
    &lt;span class="n"&gt;sendfile&lt;/span&gt;        &lt;span class="n"&gt;on&lt;/span&gt;;
    &lt;span class="c"&gt;#tcp_nopush     on;
&lt;/span&gt;
    &lt;span class="n"&gt;keepalive_timeout&lt;/span&gt;  &lt;span class="m"&gt;65&lt;/span&gt;;

    &lt;span class="n"&gt;gzip&lt;/span&gt;  &lt;span class="n"&gt;on&lt;/span&gt;;

    &lt;span class="n"&gt;include&lt;/span&gt; /&lt;span class="n"&gt;etc&lt;/span&gt;/&lt;span class="n"&gt;nginx&lt;/span&gt;/&lt;span class="n"&gt;conf&lt;/span&gt;.&lt;span class="n"&gt;d&lt;/span&gt;/*.&lt;span class="n"&gt;conf&lt;/span&gt;;
    &lt;span class="n"&gt;include&lt;/span&gt; /&lt;span class="n"&gt;etc&lt;/span&gt;/&lt;span class="n"&gt;nginx&lt;/span&gt;/&lt;span class="n"&gt;sites&lt;/span&gt;-&lt;span class="n"&gt;available&lt;/span&gt;/*.&lt;span class="n"&gt;conf&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally the default Nextcloud nginx block and his upstream from the &lt;code&gt;nc-app&lt;/code&gt; container.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="c"&gt;# Set the `immutable` cache control options only for assets with a cache busting `v` argument
&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt; $&lt;span class="n"&gt;arg_v&lt;/span&gt; $&lt;span class="n"&gt;asset_immutable&lt;/span&gt; {
    &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;;
    &lt;span class="n"&gt;default&lt;/span&gt; &lt;span class="s2"&gt;"immutable"&lt;/span&gt;;
}

&lt;span class="n"&gt;upstream&lt;/span&gt; &lt;span class="n"&gt;php&lt;/span&gt;-&lt;span class="n"&gt;handler&lt;/span&gt; {
    &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;:&lt;span class="m"&gt;9000&lt;/span&gt;;
}

&lt;span class="c"&gt;# server {
#     listen 80;
#     listen [::]:80;
&lt;/span&gt;
&lt;span class="c"&gt;#     server_name cloud.example.localhost;
#     server_tokens off;
&lt;/span&gt;
&lt;span class="c"&gt;#     location /.well-known/acme-challenge/ {
#         root /var/www/certbot;
#     }
&lt;/span&gt;
&lt;span class="c"&gt;#     location / {
#         return 301 https://cloud.example.localhost$request_uri;
#     }
# }
&lt;/span&gt;
&lt;span class="n"&gt;server&lt;/span&gt; {
    &lt;span class="c"&gt;# listen 443 default_server ssl http2;
&lt;/span&gt;    &lt;span class="c"&gt;# listen [::]:443 ssl http2;
&lt;/span&gt;
    &lt;span class="n"&gt;listen&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;;
    &lt;span class="n"&gt;server_name&lt;/span&gt; &lt;span class="n"&gt;cloud&lt;/span&gt;.&lt;span class="n"&gt;example&lt;/span&gt;.&lt;span class="n"&gt;localhost&lt;/span&gt;;

    &lt;span class="c"&gt;# Path to the root of your installation
&lt;/span&gt;    &lt;span class="n"&gt;root&lt;/span&gt; /&lt;span class="n"&gt;var&lt;/span&gt;/&lt;span class="n"&gt;www&lt;/span&gt;/&lt;span class="n"&gt;html&lt;/span&gt;;

    &lt;span class="c"&gt;# Use Mozilla's guidelines for SSL/TLS settings
&lt;/span&gt;    &lt;span class="c"&gt;# https://mozilla.github.io/server-side-tls/ssl-config-generator/
&lt;/span&gt;    &lt;span class="c"&gt;# ssl_certificate     /etc/nginx/ssl/live/cloud.example.localhost/fullchain.pem;
&lt;/span&gt;    &lt;span class="c"&gt;# ssl_certificate_key /etc/nginx/ssl/live/cloud.example.localhost/privkey.pem;
&lt;/span&gt;    &lt;span class="c"&gt;# ssl_protocols TLSv1.2 TLSv1.3;
&lt;/span&gt;
    &lt;span class="c"&gt;# Prevent nginx HTTP Server Detection
&lt;/span&gt;    &lt;span class="n"&gt;server_tokens&lt;/span&gt; &lt;span class="n"&gt;off&lt;/span&gt;;

    &lt;span class="c"&gt;# ECDHE forward secrecy
&lt;/span&gt;    &lt;span class="c"&gt;# ssl_ciphers "HIGH:!aNULL:!MD5:!ADH:!RC4:!DH";
&lt;/span&gt;    &lt;span class="c"&gt;# ssl_prefer_server_ciphers on;
&lt;/span&gt;
    &lt;span class="c"&gt;# HSTS settings
&lt;/span&gt;    &lt;span class="c"&gt;# WARNING: Only add the preload option once you read about
&lt;/span&gt;    &lt;span class="c"&gt;# the consequences in https://hstspreload.org/. This option
&lt;/span&gt;    &lt;span class="c"&gt;# will add the domain to a hardcoded list that is shipped
&lt;/span&gt;    &lt;span class="c"&gt;# in all major browsers and getting removed from this list
&lt;/span&gt;    &lt;span class="c"&gt;# could take several months.
&lt;/span&gt;    &lt;span class="c"&gt;# add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload" always;
&lt;/span&gt;
    &lt;span class="c"&gt;# set max upload size and increase upload timeout:
&lt;/span&gt;    &lt;span class="n"&gt;client_max_body_size&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="n"&gt;G&lt;/span&gt;;
    &lt;span class="n"&gt;client_body_timeout&lt;/span&gt; &lt;span class="m"&gt;300&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;;
    &lt;span class="n"&gt;fastcgi_buffers&lt;/span&gt; &lt;span class="m"&gt;64&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="n"&gt;K&lt;/span&gt;;

    &lt;span class="c"&gt;# Enable gzip but do not remove ETag headers
&lt;/span&gt;    &lt;span class="n"&gt;gzip&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt;;
    &lt;span class="n"&gt;gzip_vary&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt;;
    &lt;span class="n"&gt;gzip_comp_level&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;;
    &lt;span class="n"&gt;gzip_min_length&lt;/span&gt; &lt;span class="m"&gt;256&lt;/span&gt;;
    &lt;span class="n"&gt;gzip_proxied&lt;/span&gt; &lt;span class="n"&gt;expired&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt;-&lt;span class="n"&gt;cache&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt;-&lt;span class="n"&gt;store&lt;/span&gt; &lt;span class="n"&gt;private&lt;/span&gt; &lt;span class="n"&gt;no_last_modified&lt;/span&gt; &lt;span class="n"&gt;no_etag&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;;
    &lt;span class="n"&gt;gzip_types&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;/&lt;span class="n"&gt;atom&lt;/span&gt;+&lt;span class="n"&gt;xml&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;/&lt;span class="n"&gt;javascript&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;/&lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;/&lt;span class="n"&gt;ld&lt;/span&gt;+&lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;/&lt;span class="n"&gt;manifest&lt;/span&gt;+&lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;/&lt;span class="n"&gt;rss&lt;/span&gt;+&lt;span class="n"&gt;xml&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;/&lt;span class="n"&gt;vnd&lt;/span&gt;.&lt;span class="n"&gt;geo&lt;/span&gt;+&lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;/&lt;span class="n"&gt;vnd&lt;/span&gt;.&lt;span class="n"&gt;ms&lt;/span&gt;-&lt;span class="n"&gt;fontobject&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;/&lt;span class="n"&gt;wasm&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;/&lt;span class="n"&gt;x&lt;/span&gt;-&lt;span class="n"&gt;font&lt;/span&gt;-&lt;span class="n"&gt;ttf&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;/&lt;span class="n"&gt;x&lt;/span&gt;-&lt;span class="n"&gt;web&lt;/span&gt;-&lt;span class="n"&gt;app&lt;/span&gt;-&lt;span class="n"&gt;manifest&lt;/span&gt;+&lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;/&lt;span class="n"&gt;xhtml&lt;/span&gt;+&lt;span class="n"&gt;xml&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;/&lt;span class="n"&gt;xml&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt;/&lt;span class="n"&gt;opentype&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;/&lt;span class="n"&gt;bmp&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;/&lt;span class="n"&gt;svg&lt;/span&gt;+&lt;span class="n"&gt;xml&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;/&lt;span class="n"&gt;x&lt;/span&gt;-&lt;span class="n"&gt;icon&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;/&lt;span class="n"&gt;cache&lt;/span&gt;-&lt;span class="n"&gt;manifest&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;/&lt;span class="n"&gt;css&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;/&lt;span class="n"&gt;plain&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;/&lt;span class="n"&gt;vcard&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;/&lt;span class="n"&gt;vnd&lt;/span&gt;.&lt;span class="n"&gt;rim&lt;/span&gt;.&lt;span class="n"&gt;location&lt;/span&gt;.&lt;span class="n"&gt;xloc&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;/&lt;span class="n"&gt;vtt&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;/&lt;span class="n"&gt;x&lt;/span&gt;-&lt;span class="n"&gt;component&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;/&lt;span class="n"&gt;x&lt;/span&gt;-&lt;span class="n"&gt;cross&lt;/span&gt;-&lt;span class="n"&gt;domain&lt;/span&gt;-&lt;span class="n"&gt;policy&lt;/span&gt;;

    &lt;span class="c"&gt;# Pagespeed is not supported by Nextcloud, so if your server is built
&lt;/span&gt;    &lt;span class="c"&gt;# with the `ngx_pagespeed` module, uncomment this line to disable it.
&lt;/span&gt;    &lt;span class="c"&gt;#pagespeed off;
&lt;/span&gt;
    &lt;span class="c"&gt;# The settings allows you to optimize the HTTP2 bandwitdth.
&lt;/span&gt;    &lt;span class="c"&gt;# See https://blog.cloudflare.com/delivering-http-2-upload-speed-improvements/
&lt;/span&gt;    &lt;span class="c"&gt;# for tunning hints
&lt;/span&gt;    &lt;span class="n"&gt;client_body_buffer_size&lt;/span&gt; &lt;span class="m"&gt;512&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;;

    &lt;span class="c"&gt;# HTTP response headers borrowed from Nextcloud `.htaccess`
&lt;/span&gt;    &lt;span class="n"&gt;add_header&lt;/span&gt; &lt;span class="n"&gt;Referrer&lt;/span&gt;-&lt;span class="n"&gt;Policy&lt;/span&gt;                      &lt;span class="s2"&gt;"no-referrer"&lt;/span&gt;   &lt;span class="n"&gt;always&lt;/span&gt;;
    &lt;span class="n"&gt;add_header&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;-&lt;span class="n"&gt;Content&lt;/span&gt;-&lt;span class="n"&gt;Type&lt;/span&gt;-&lt;span class="n"&gt;Options&lt;/span&gt;               &lt;span class="s2"&gt;"nosniff"&lt;/span&gt;       &lt;span class="n"&gt;always&lt;/span&gt;;
    &lt;span class="n"&gt;add_header&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;-&lt;span class="n"&gt;Download&lt;/span&gt;-&lt;span class="n"&gt;Options&lt;/span&gt;                   &lt;span class="s2"&gt;"noopen"&lt;/span&gt;        &lt;span class="n"&gt;always&lt;/span&gt;;
    &lt;span class="n"&gt;add_header&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;-&lt;span class="n"&gt;Frame&lt;/span&gt;-&lt;span class="n"&gt;Options&lt;/span&gt;                      &lt;span class="s2"&gt;"SAMEORIGIN"&lt;/span&gt;    &lt;span class="n"&gt;always&lt;/span&gt;;
    &lt;span class="n"&gt;add_header&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;-&lt;span class="n"&gt;Permitted&lt;/span&gt;-&lt;span class="n"&gt;Cross&lt;/span&gt;-&lt;span class="n"&gt;Domain&lt;/span&gt;-&lt;span class="n"&gt;Policies&lt;/span&gt;    &lt;span class="s2"&gt;"none"&lt;/span&gt;          &lt;span class="n"&gt;always&lt;/span&gt;;
    &lt;span class="n"&gt;add_header&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;-&lt;span class="n"&gt;Robots&lt;/span&gt;-&lt;span class="n"&gt;Tag&lt;/span&gt;                         &lt;span class="s2"&gt;"none"&lt;/span&gt;          &lt;span class="n"&gt;always&lt;/span&gt;;
    &lt;span class="n"&gt;add_header&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;-&lt;span class="n"&gt;XSS&lt;/span&gt;-&lt;span class="n"&gt;Protection&lt;/span&gt;                     &lt;span class="s2"&gt;"1; mode=block"&lt;/span&gt; &lt;span class="n"&gt;always&lt;/span&gt;;

    &lt;span class="c"&gt;# Remove X-Powered-By, which is an information leak
&lt;/span&gt;    &lt;span class="n"&gt;fastcgi_hide_header&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;-&lt;span class="n"&gt;Powered&lt;/span&gt;-&lt;span class="n"&gt;By&lt;/span&gt;;

    &lt;span class="c"&gt;# Specify how to handle directories -- specifying `/index.php$request_uri`
&lt;/span&gt;    &lt;span class="c"&gt;# here as the fallback means that Nginx always exhibits the desired behaviour
&lt;/span&gt;    &lt;span class="c"&gt;# when a client requests a path that corresponds to a directory that exists
&lt;/span&gt;    &lt;span class="c"&gt;# on the server. In particular, if that directory contains an index.php file,
&lt;/span&gt;    &lt;span class="c"&gt;# that file is correctly served; if it doesn't, then the request is passed to
&lt;/span&gt;    &lt;span class="c"&gt;# the front-end controller. This consistent behaviour means that we don't need
&lt;/span&gt;    &lt;span class="c"&gt;# to specify custom rules for certain paths (e.g. images and other assets,
&lt;/span&gt;    &lt;span class="c"&gt;# `/updater`, `/ocm-provider`, `/ocs-provider`), and thus
&lt;/span&gt;    &lt;span class="c"&gt;# `try_files $uri $uri/ /index.php$request_uri`
&lt;/span&gt;    &lt;span class="c"&gt;# always provides the desired behaviour.
&lt;/span&gt;    &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;.&lt;span class="n"&gt;php&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;.&lt;span class="n"&gt;html&lt;/span&gt; /&lt;span class="n"&gt;index&lt;/span&gt;.&lt;span class="n"&gt;php&lt;/span&gt;$&lt;span class="n"&gt;request_uri&lt;/span&gt;;

    &lt;span class="c"&gt;# Rule borrowed from `.htaccess` to handle Microsoft DAV clients
&lt;/span&gt;    &lt;span class="n"&gt;location&lt;/span&gt; = / {
        &lt;span class="n"&gt;if&lt;/span&gt; ( $&lt;span class="n"&gt;http_user_agent&lt;/span&gt; ~ ^&lt;span class="n"&gt;DavClnt&lt;/span&gt; ) {
            &lt;span class="n"&gt;return&lt;/span&gt; &lt;span class="m"&gt;302&lt;/span&gt; /&lt;span class="n"&gt;remote&lt;/span&gt;.&lt;span class="n"&gt;php&lt;/span&gt;/&lt;span class="n"&gt;webdav&lt;/span&gt;/$&lt;span class="n"&gt;is_args&lt;/span&gt;$&lt;span class="n"&gt;args&lt;/span&gt;;
        }
    }

    &lt;span class="n"&gt;location&lt;/span&gt; = /&lt;span class="n"&gt;robots&lt;/span&gt;.&lt;span class="n"&gt;txt&lt;/span&gt; {
        &lt;span class="n"&gt;allow&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt;;
        &lt;span class="n"&gt;log_not_found&lt;/span&gt; &lt;span class="n"&gt;off&lt;/span&gt;;
        &lt;span class="n"&gt;access_log&lt;/span&gt; &lt;span class="n"&gt;off&lt;/span&gt;;
    }

    &lt;span class="c"&gt;# Make a regex exception for `/.well-known` so that clients can still
&lt;/span&gt;    &lt;span class="c"&gt;# access it despite the existence of the regex rule
&lt;/span&gt;    &lt;span class="c"&gt;# `location ~ /(\.|autotest|...)` which would otherwise handle requests
&lt;/span&gt;    &lt;span class="c"&gt;# for `/.well-known`.
&lt;/span&gt;    &lt;span class="n"&gt;location&lt;/span&gt; ^~ /.&lt;span class="n"&gt;well&lt;/span&gt;-&lt;span class="n"&gt;known&lt;/span&gt; {
        &lt;span class="c"&gt;# The rules in this block are an adaptation of the rules
&lt;/span&gt;        &lt;span class="c"&gt;# in `.htaccess` that concern `/.well-known`.
&lt;/span&gt;
        &lt;span class="n"&gt;location&lt;/span&gt; = /.&lt;span class="n"&gt;well&lt;/span&gt;-&lt;span class="n"&gt;known&lt;/span&gt;/&lt;span class="n"&gt;carddav&lt;/span&gt; { &lt;span class="n"&gt;return&lt;/span&gt; &lt;span class="m"&gt;301&lt;/span&gt; /&lt;span class="n"&gt;remote&lt;/span&gt;.&lt;span class="n"&gt;php&lt;/span&gt;/&lt;span class="n"&gt;dav&lt;/span&gt;/; }
        &lt;span class="n"&gt;location&lt;/span&gt; = /.&lt;span class="n"&gt;well&lt;/span&gt;-&lt;span class="n"&gt;known&lt;/span&gt;/&lt;span class="n"&gt;caldav&lt;/span&gt;  { &lt;span class="n"&gt;return&lt;/span&gt; &lt;span class="m"&gt;301&lt;/span&gt; /&lt;span class="n"&gt;remote&lt;/span&gt;.&lt;span class="n"&gt;php&lt;/span&gt;/&lt;span class="n"&gt;dav&lt;/span&gt;/; }

        &lt;span class="n"&gt;location&lt;/span&gt; /.&lt;span class="n"&gt;well&lt;/span&gt;-&lt;span class="n"&gt;known&lt;/span&gt;/&lt;span class="n"&gt;acme&lt;/span&gt;-&lt;span class="n"&gt;challenge&lt;/span&gt;    { &lt;span class="n"&gt;try_files&lt;/span&gt; $&lt;span class="n"&gt;uri&lt;/span&gt; $&lt;span class="n"&gt;uri&lt;/span&gt;/ =&lt;span class="m"&gt;404&lt;/span&gt;; }
        &lt;span class="n"&gt;location&lt;/span&gt; /.&lt;span class="n"&gt;well&lt;/span&gt;-&lt;span class="n"&gt;known&lt;/span&gt;/&lt;span class="n"&gt;pki&lt;/span&gt;-&lt;span class="n"&gt;validation&lt;/span&gt;    { &lt;span class="n"&gt;try_files&lt;/span&gt; $&lt;span class="n"&gt;uri&lt;/span&gt; $&lt;span class="n"&gt;uri&lt;/span&gt;/ =&lt;span class="m"&gt;404&lt;/span&gt;; }

        &lt;span class="c"&gt;# Let Nextcloud's API for `/.well-known` URIs handle all other
&lt;/span&gt;        &lt;span class="c"&gt;# requests by passing them to the front-end controller.
&lt;/span&gt;        &lt;span class="n"&gt;return&lt;/span&gt; &lt;span class="m"&gt;301&lt;/span&gt; /&lt;span class="n"&gt;index&lt;/span&gt;.&lt;span class="n"&gt;php&lt;/span&gt;$&lt;span class="n"&gt;request_uri&lt;/span&gt;;
    }

    &lt;span class="c"&gt;# Rules borrowed from `.htaccess` to hide certain paths from clients
&lt;/span&gt;    &lt;span class="n"&gt;location&lt;/span&gt; ~ ^/(?:&lt;span class="n"&gt;build&lt;/span&gt;|&lt;span class="n"&gt;tests&lt;/span&gt;|&lt;span class="n"&gt;config&lt;/span&gt;|&lt;span class="n"&gt;lib&lt;/span&gt;|&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="n"&gt;rdparty&lt;/span&gt;|&lt;span class="n"&gt;templates&lt;/span&gt;|&lt;span class="n"&gt;data&lt;/span&gt;)(?:$|/)  { &lt;span class="n"&gt;return&lt;/span&gt; &lt;span class="m"&gt;404&lt;/span&gt;; }
    &lt;span class="n"&gt;location&lt;/span&gt; ~ ^/(?:\.|&lt;span class="n"&gt;autotest&lt;/span&gt;|&lt;span class="n"&gt;occ&lt;/span&gt;|&lt;span class="n"&gt;issue&lt;/span&gt;|&lt;span class="n"&gt;indie&lt;/span&gt;|&lt;span class="n"&gt;db_&lt;/span&gt;|&lt;span class="n"&gt;console&lt;/span&gt;)                { &lt;span class="n"&gt;return&lt;/span&gt; &lt;span class="m"&gt;404&lt;/span&gt;; }

    &lt;span class="c"&gt;# Ensure this block, which passes PHP files to the PHP process, is above the blocks
&lt;/span&gt;    &lt;span class="c"&gt;# which handle static assets (as seen below). If this block is not declared first,
&lt;/span&gt;    &lt;span class="c"&gt;# then Nginx will encounter an infinite rewriting loop when it prepends `/index.php`
&lt;/span&gt;    &lt;span class="c"&gt;# to the URI, resulting in a HTTP 500 error response.
&lt;/span&gt;    &lt;span class="n"&gt;location&lt;/span&gt; ~ \.&lt;span class="n"&gt;php&lt;/span&gt;(?:$|/) {
        &lt;span class="c"&gt;# Required for legacy support
&lt;/span&gt;        &lt;span class="n"&gt;rewrite&lt;/span&gt; ^/(?!&lt;span class="n"&gt;index&lt;/span&gt;|&lt;span class="n"&gt;remote&lt;/span&gt;|&lt;span class="n"&gt;public&lt;/span&gt;|&lt;span class="n"&gt;cron&lt;/span&gt;|&lt;span class="n"&gt;core&lt;/span&gt;\/&lt;span class="n"&gt;ajax&lt;/span&gt;\/&lt;span class="n"&gt;update&lt;/span&gt;|&lt;span class="n"&gt;status&lt;/span&gt;|&lt;span class="n"&gt;ocs&lt;/span&gt;\/&lt;span class="n"&gt;v&lt;/span&gt;[&lt;span class="m"&gt;12&lt;/span&gt;]|&lt;span class="n"&gt;updater&lt;/span&gt;\/.+|&lt;span class="n"&gt;oc&lt;/span&gt;[&lt;span class="n"&gt;ms&lt;/span&gt;]-&lt;span class="n"&gt;provider&lt;/span&gt;\/.+|.+\/&lt;span class="n"&gt;richdocumentscode&lt;/span&gt;\/&lt;span class="n"&gt;proxy&lt;/span&gt;) /&lt;span class="n"&gt;index&lt;/span&gt;.&lt;span class="n"&gt;php&lt;/span&gt;$&lt;span class="n"&gt;request_uri&lt;/span&gt;;

        &lt;span class="n"&gt;fastcgi_split_path_info&lt;/span&gt; ^(.+?\.&lt;span class="n"&gt;php&lt;/span&gt;)(/.*)$;
        &lt;span class="n"&gt;set&lt;/span&gt; $&lt;span class="n"&gt;path_info&lt;/span&gt; $&lt;span class="n"&gt;fastcgi_path_info&lt;/span&gt;;

        &lt;span class="n"&gt;try_files&lt;/span&gt; $&lt;span class="n"&gt;fastcgi_script_name&lt;/span&gt; =&lt;span class="m"&gt;404&lt;/span&gt;;

        &lt;span class="n"&gt;include&lt;/span&gt; &lt;span class="n"&gt;fastcgi_params&lt;/span&gt;;
        &lt;span class="n"&gt;fastcgi_param&lt;/span&gt; &lt;span class="n"&gt;SCRIPT_FILENAME&lt;/span&gt; $&lt;span class="n"&gt;document_root&lt;/span&gt;$&lt;span class="n"&gt;fastcgi_script_name&lt;/span&gt;;
        &lt;span class="n"&gt;fastcgi_param&lt;/span&gt; &lt;span class="n"&gt;PATH_INFO&lt;/span&gt; $&lt;span class="n"&gt;path_info&lt;/span&gt;;
        &lt;span class="n"&gt;fastcgi_param&lt;/span&gt; &lt;span class="n"&gt;HTTPS&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt;;

        &lt;span class="n"&gt;fastcgi_param&lt;/span&gt; &lt;span class="n"&gt;modHeadersAvailable&lt;/span&gt; &lt;span class="n"&gt;true&lt;/span&gt;;         &lt;span class="c"&gt;# Avoid sending the security headers twice
&lt;/span&gt;        &lt;span class="n"&gt;fastcgi_param&lt;/span&gt; &lt;span class="n"&gt;front_controller_active&lt;/span&gt; &lt;span class="n"&gt;true&lt;/span&gt;;     &lt;span class="c"&gt;# Enable pretty urls
&lt;/span&gt;        &lt;span class="n"&gt;fastcgi_pass&lt;/span&gt; &lt;span class="n"&gt;php&lt;/span&gt;-&lt;span class="n"&gt;handler&lt;/span&gt;;

        &lt;span class="n"&gt;fastcgi_intercept_errors&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt;;
        &lt;span class="n"&gt;fastcgi_request_buffering&lt;/span&gt; &lt;span class="n"&gt;off&lt;/span&gt;;

        &lt;span class="n"&gt;fastcgi_max_temp_file_size&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;;
    }

    &lt;span class="n"&gt;location&lt;/span&gt; ~ \.(?:&lt;span class="n"&gt;css&lt;/span&gt;|&lt;span class="n"&gt;js&lt;/span&gt;|&lt;span class="n"&gt;svg&lt;/span&gt;|&lt;span class="n"&gt;gif&lt;/span&gt;|&lt;span class="n"&gt;png&lt;/span&gt;|&lt;span class="n"&gt;jpg&lt;/span&gt;|&lt;span class="n"&gt;ico&lt;/span&gt;|&lt;span class="n"&gt;wasm&lt;/span&gt;|&lt;span class="n"&gt;tflite&lt;/span&gt;|&lt;span class="n"&gt;map&lt;/span&gt;)$ {
        &lt;span class="n"&gt;try_files&lt;/span&gt; $&lt;span class="n"&gt;uri&lt;/span&gt; /&lt;span class="n"&gt;index&lt;/span&gt;.&lt;span class="n"&gt;php&lt;/span&gt;$&lt;span class="n"&gt;request_uri&lt;/span&gt;;
        &lt;span class="n"&gt;add_header&lt;/span&gt; &lt;span class="n"&gt;Cache&lt;/span&gt;-&lt;span class="n"&gt;Control&lt;/span&gt; &lt;span class="s2"&gt;"public, max-age=15778463, $asset_immutable"&lt;/span&gt;;
        &lt;span class="n"&gt;access_log&lt;/span&gt; &lt;span class="n"&gt;off&lt;/span&gt;;     &lt;span class="c"&gt;# Optional: Don't log access to assets
&lt;/span&gt;
        &lt;span class="n"&gt;location&lt;/span&gt; ~ \.&lt;span class="n"&gt;wasm&lt;/span&gt;$ {
            &lt;span class="n"&gt;default_type&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;/&lt;span class="n"&gt;wasm&lt;/span&gt;;
        }
    }

    &lt;span class="n"&gt;location&lt;/span&gt; ~ \.&lt;span class="n"&gt;woff2&lt;/span&gt;?$ {
        &lt;span class="n"&gt;try_files&lt;/span&gt; $&lt;span class="n"&gt;uri&lt;/span&gt; /&lt;span class="n"&gt;index&lt;/span&gt;.&lt;span class="n"&gt;php&lt;/span&gt;$&lt;span class="n"&gt;request_uri&lt;/span&gt;;
        &lt;span class="n"&gt;expires&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;;         &lt;span class="c"&gt;# Cache-Control policy borrowed from `.htaccess`
&lt;/span&gt;        &lt;span class="n"&gt;access_log&lt;/span&gt; &lt;span class="n"&gt;off&lt;/span&gt;;     &lt;span class="c"&gt;# Optional: Don't log access to assets
&lt;/span&gt;    }

    &lt;span class="c"&gt;# Rule borrowed from `.htaccess`
&lt;/span&gt;    &lt;span class="n"&gt;location&lt;/span&gt; /&lt;span class="n"&gt;remote&lt;/span&gt; {
        &lt;span class="n"&gt;return&lt;/span&gt; &lt;span class="m"&gt;301&lt;/span&gt; /&lt;span class="n"&gt;remote&lt;/span&gt;.&lt;span class="n"&gt;php&lt;/span&gt;$&lt;span class="n"&gt;request_uri&lt;/span&gt;;
    }

    &lt;span class="n"&gt;location&lt;/span&gt; / {
        &lt;span class="n"&gt;try_files&lt;/span&gt; $&lt;span class="n"&gt;uri&lt;/span&gt; $&lt;span class="n"&gt;uri&lt;/span&gt;/ /&lt;span class="n"&gt;index&lt;/span&gt;.&lt;span class="n"&gt;php&lt;/span&gt;$&lt;span class="n"&gt;request_uri&lt;/span&gt;;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Redis
&lt;/h2&gt;

&lt;p&gt;We just secure the instance using a password and use the latest &lt;code&gt;redis:alpine&lt;/code&gt; image.&lt;/p&gt;

&lt;h2&gt;
  
  
  Certbot
&lt;/h2&gt;

&lt;p&gt;We use the latest &lt;code&gt;certbot&lt;/code&gt; image, to easiky and freely generate SSL certificate for our cloud. The important point here is the certificate will be generated under the &lt;code&gt;certbot/conf&lt;/code&gt; and this same folder will be mapped to Nginx &lt;code&gt;nc-web&lt;/code&gt; container &lt;code&gt;/etc/nginx/ssl/&lt;/code&gt; path.&lt;/p&gt;

&lt;p&gt;To resolve LetsEncrypt host validation challenge we also need to serve &lt;code&gt;/var/www/certbot&lt;/code&gt; from &lt;code&gt;certbot&lt;/code&gt; container in the Nginx container &lt;code&gt;nc-web&lt;/code&gt; nad using the commented server block in &lt;code&gt;nginx/sites/default.conf&lt;/code&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Time to launch
&lt;/h1&gt;

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

&lt;p&gt;At this point we already have a fully functional dockerized Nextcloud instance, let's use docker-compose to build and run it in background.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;This is it! Let's install and configure your own Nextcloud instance on &lt;a href="http://cloud.example.localhost:8888" rel="noopener noreferrer"&gt;http://localhost:8888&lt;/a&gt; or &lt;a href="http://cloud.example.localhost:8888" rel="noopener noreferrer"&gt;http://cloud.example.localhost:8888&lt;/a&gt; if you added &lt;code&gt;cloud.example.localhost&lt;/code&gt; hostname to your local &lt;code&gt;etc/hosts&lt;/code&gt;. &lt;/p&gt;

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

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

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

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

&lt;p&gt;Et voila! keep in mind that this instance run without SSL, some errors can occur for instance while setup you'll be redirected to https:// url scheme, just remove the "s".&lt;/p&gt;

&lt;h1&gt;
  
  
  Security consideration
&lt;/h1&gt;

&lt;p&gt;Nextcloud offer a built in security diagnosis very useful to keep your instance secure.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  SSL
&lt;/h2&gt;

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

&lt;p&gt;First we need to generate an SSL certificate using letsencrypt then configure the nginx instance to only listen on 443 default https port. To do so we need a real domain name (at least subdomain) since &lt;code&gt;.localhost&lt;/code&gt; extension is not valid.&lt;/p&gt;

&lt;p&gt;You need to replace all project's occurence of &lt;code&gt;cloud.example.localhost&lt;/code&gt; with your own domain name.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generate your SSL certificate
&lt;/h3&gt;

&lt;p&gt;To use the certbot container and generate your domain name SSL certificate you first need to uncomment those lines in &lt;code&gt;nginx/sites/default.conf&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;server&lt;/span&gt; {
    &lt;span class="n"&gt;listen&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;;
    &lt;span class="n"&gt;listen&lt;/span&gt; [::]:&lt;span class="m"&gt;80&lt;/span&gt;;

    &lt;span class="n"&gt;server_name&lt;/span&gt; &lt;span class="n"&gt;cloud&lt;/span&gt;.&lt;span class="n"&gt;example&lt;/span&gt;.&lt;span class="n"&gt;localhost&lt;/span&gt;;
    &lt;span class="n"&gt;server_tokens&lt;/span&gt; &lt;span class="n"&gt;off&lt;/span&gt;;

    &lt;span class="n"&gt;location&lt;/span&gt; /.&lt;span class="n"&gt;well&lt;/span&gt;-&lt;span class="n"&gt;known&lt;/span&gt;/&lt;span class="n"&gt;acme&lt;/span&gt;-&lt;span class="n"&gt;challenge&lt;/span&gt;/ {
        &lt;span class="n"&gt;root&lt;/span&gt; /&lt;span class="n"&gt;var&lt;/span&gt;/&lt;span class="n"&gt;www&lt;/span&gt;/&lt;span class="n"&gt;certbot&lt;/span&gt;;
    }

    &lt;span class="n"&gt;location&lt;/span&gt; / {
        &lt;span class="n"&gt;return&lt;/span&gt; &lt;span class="m"&gt;301&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;://&lt;span class="n"&gt;cloud&lt;/span&gt;.&lt;span class="n"&gt;example&lt;/span&gt;.&lt;span class="n"&gt;localhost&lt;/span&gt;$&lt;span class="n"&gt;request_uri&lt;/span&gt;;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dont forget to replace &lt;code&gt;cloud.example.localhost&lt;/code&gt; by your real domain name too. This way we can validate our host via http challenge.&lt;/p&gt;

&lt;p&gt;And we need to use default http port 80 too rather than our previous custom 8888 in our nginx proxy container &lt;code&gt;nc-web&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;docker-compose.yml&lt;/code&gt;, replace&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8888:80"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;443:443"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;by&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;80:80"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;443:443"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dont forget to restart the stack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose restart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can generate it, first let's try in dry-run mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose run &lt;span class="nt"&gt;--rm&lt;/span&gt;  certbot certonly &lt;span class="nt"&gt;--webroot&lt;/span&gt; &lt;span class="nt"&gt;--webroot-path&lt;/span&gt; /var/www/certbot/ &lt;span class="nt"&gt;--dry-run&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; votredomaine.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If all went smooth the, directly generate them by removing the dry-run flag.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose run &lt;span class="nt"&gt;--rm&lt;/span&gt;  certbot certonly &lt;span class="nt"&gt;--webroot&lt;/span&gt; &lt;span class="nt"&gt;--webroot-path&lt;/span&gt; /var/www/certbot/ &lt;span class="nt"&gt;-d&lt;/span&gt; votredomaine.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally we need to update our &lt;code&gt;nginx/sites/default.conf&lt;/code&gt; to listen on SSL default port and also load SSL certificates and uncommented those lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;server &lt;span class="o"&gt;{&lt;/span&gt;
    listen 443 default_server ssl http2&lt;span class="p"&gt;;&lt;/span&gt;
    listen &lt;span class="o"&gt;[&lt;/span&gt;::]:443 ssl http2&lt;span class="p"&gt;;&lt;/span&gt;

    server_name yourdomainenamehere.com&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c"&gt;# Path to the root of your installation&lt;/span&gt;
    root /var/www/html&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c"&gt;# Use Mozilla's guidelines for SSL/TLS settings&lt;/span&gt;
    &lt;span class="c"&gt;# https://mozilla.github.io/server-side-tls/ssl-config-generator/&lt;/span&gt;
    ssl_certificate     /etc/nginx/ssl/live/yourdomainenamehere.com/fullchain.pem&lt;span class="p"&gt;;&lt;/span&gt;
    ssl_certificate_key /etc/nginx/ssl/live/yourdomainenamehere.com/privkey.pem&lt;span class="p"&gt;;&lt;/span&gt;
    ssl_protocols TLSv1.2 TLSv1.3&lt;span class="p"&gt;;&lt;/span&gt;


    &lt;span class="c"&gt;# Prevent nginx HTTP Server Detection&lt;/span&gt;
    server_tokens off&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c"&gt;# ECDHE forward secrecy&lt;/span&gt;
    ssl_ciphers &lt;span class="s2"&gt;"HIGH:!aNULL:!MD5:!ADH:!RC4:!DH"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    ssl_prefer_server_ciphers on&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c"&gt;# HSTS settings&lt;/span&gt;
    &lt;span class="c"&gt;# WARNING: Only add the preload option once you read about&lt;/span&gt;
    &lt;span class="c"&gt;# the consequences in https://hstspreload.org/. This option&lt;/span&gt;
    &lt;span class="c"&gt;# will add the domain to a hardcoded list that is shipped&lt;/span&gt;
    &lt;span class="c"&gt;# in all major browsers and getting removed from this list&lt;/span&gt;
    &lt;span class="c"&gt;# could take several months.&lt;/span&gt;
    add_header Strict-Transport-Security &lt;span class="s2"&gt;"max-age=15768000; includeSubDomains; preload"&lt;/span&gt; always&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way you'll end up with a A+ SSL grade according to &lt;a href="https://www.ssllabs.com/ssltest/" rel="noopener noreferrer"&gt;SSL Labs&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Backup
&lt;/h2&gt;

&lt;p&gt;Since we created two docker volumes, one for the PostgreSQL database and an antoher for the actual nextcloud script path also containing the &lt;code&gt;data&lt;/code&gt; folder. All you have to backup the nextcloud instance is to dump the database and backup the cloud files onto the &lt;code&gt;data&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;To backup or restore those two volumes, just refer to &lt;a href="https://docs.docker.com/storage/volumes/#back-up-restore-or-migrate-data-volumes" rel="noopener noreferrer"&gt;the official docker documentation about volumes&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;We built a cutting edge dockerized Nextcloud instance.&lt;/p&gt;

&lt;p&gt;You can find all the related code &lt;a href="https://github.com/nicolasbonnici/nextcloud-docker-postgresql-redis" rel="noopener noreferrer"&gt;on this Github repository&lt;/a&gt;, &lt;strong&gt;feel free to contribute&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Thank you for reading.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>nextcloud</category>
      <category>postgres</category>
      <category>redis</category>
    </item>
  </channel>
</rss>
