Building a REST API with Golang, Gin, and Pocketbase
21 janvier 2025
<h3>
Introduction
</h3>
<p>Building a REST API with modern technologies can be an engaging yet complex process, especially when you are working with unfamiliar stacks. As a Go developer, I decided to explore the combination of Golang with the Gin framework and Pocketbase for building a REST API. This choice was driven by a desire to experiment with a different tech stack while also considering the positive feedback I had heard from the developer community regarding Pocketbase's user-friendly features.</p>
<p>We’ll walk you through setting up the environment, configuring the API routes, and creating a service layer to interact with Pocketbase for user authentication and data management. By the end of this tutorial, you will have a fully functional REST API capable of user registration and login.</p>
<h3>
Why Choose Gin and Pocketbase?
</h3>
<h4>
Gin Framework:
</h4>
<ul>
<li>
<strong>Performance</strong>: Known for its speed and low latency, making it ideal for high-performance applications.</li>
<li>
<strong>Simplicity and Flexibility</strong>: Easy to set up, scale, and maintain.</li>
<li>
<strong>Middleware Support</strong>: Built-in middleware enables seamless integration of common features like logging and authentication.</li>
</ul>
<h4>
Pocketbase:
</h4>
<ul>
<li>
<strong>User Management</strong>: Out-of-the-box support for user registration, login, and password reset functionalities.</li>
<li>
<strong>Real-Time Database</strong>: Simplifies real-time data management with minimal configuration.</li>
<li>
<strong>Ease of Use</strong>: Pocketbase abstracts away many backend complexities, allowing developers to focus on building the core application.</li>
</ul>
<h4>
⚠️ <strong>Important Note About Pocketbase</strong>:
</h4>
<p><em><strong>Pocketbase</strong> is under active development and may undergo breaking changes before version 1.0.0. It is <strong>not recommended for production use</strong> unless you are comfortable with frequent updates and manual migrations.</em></p>
<h3>
Prerequisites
</h3>
<ol>
<li>
<strong>Golang</strong>: Install Go from <a href="https://golang.org/dl/" rel="noopener noreferrer">Go Downloads</a>.</li>
<li>
<strong>GitHub Account</strong>: Required for cloning repositories and managing code.</li>
<li>
<strong>Pocketbase</strong>: Download the latest release from <a href="https://github.com/pocketbase/pocketbase" rel="noopener noreferrer">Pocketbase GitHub</a>.</li>
</ol>
<h3>
Setting Up the Project Structure
</h3>
<p>The directory structure of the project is as follows:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code>walkit/
├── .github/
├── cmd/
│ └── server/
├── config/
├── internal/
│ ├── middleware/
│ ├── handler/
│ ├── model/
│ ├── repository/
│ ├── service/
│ ├── routes/
├── pb_data/
├── pkg/
│ ├── util/
│ └── logger/
├── air.toml
├── .env
├── Dockerfile
├── pocketbase
├── Makefile
├── go.mod
├── go.sum
└── README.md
</code></pre>
</div>
<h3>
Setting Up Pocketbase
</h3>
<h4>
Download and Install Pocketbase:
</h4>
<ol>
<li>Download the latest release of Pocketbase from the <a href="https://github.com/pocketbase/pocketbase" rel="noopener noreferrer">GitHub repository</a>.</li>
<li>Extract the files and place them in your project folder.</li>
<li>To start Pocketbase, run the following command:
</li>
</ol>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code> ./pocketbase serve
</code></pre>
</div>
<p>This will start Pocketbase at <code>http://127.0.0.1:8090</code>.</p>
<ol>
<li>Navigate to the Pocketbase dashboard at <code>http://127.0.0.1:8090/_/</code> and set up a root password for the admin interface.</li>
</ol>
<p>Once the server is running, Pocketbase provides two essential endpoints:</p>
<ul>
<li>REST API: <code>http://127.0.0.1:8090/api/</code>
</li>
<li>Dashboard: <code>http://127.0.0.1:8090/_/</code>
</li>
</ul>
<h3>
Configuring API URLs
</h3>
<p>In the <code>config.go</code> file, we'll use <strong>Viper</strong> to manage and load configuration settings, such as the Pocketbase API URL and JWT secret. This ensures that the Golang application can securely interact with Pocketbase for user authentication and management.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight go"><code><span class="k">package</span> <span class="n">config</span>
<span class="k">import</span> <span class="p">(</span>
<span class="s">"log"</span>
<span class="s">"github.com/spf13/viper"</span>
<span class="p">)</span>
<span class="k">type</span> <span class="n">Config</span> <span class="k">struct</span> <span class="p">{</span>
<span class="n">BaseURL</span> <span class="kt">string</span>
<span class="n">JWTSecret</span> <span class="kt">string</span>
<span class="n">Environment</span> <span class="kt">string</span>
<span class="n">CORSAllowedOrigins</span> <span class="p">[]</span><span class="kt">string</span>
<span class="n">Port</span> <span class="kt">string</span>
<span class="p">}</span>
<span class="k">func</span> <span class="n">LoadConfig</span><span class="p">()</span> <span class="o">*</span><span class="n">Config</span> <span class="p">{</span>
<span class="n">viper</span><span class="o">.</span><span class="n">SetConfigName</span><span class="p">(</span><span class="s">"config"</span><span class="p">)</span>
<span class="n">viper</span><span class="o">.</span><span class="n">SetConfigType</span><span class="p">(</span><span class="s">"yaml"</span><span class="p">)</span>
<span class="n">viper</span><span class="o">.</span><span class="n">AddConfigPath</span><span class="p">(</span><span class="s">"./config"</span><span class="p">)</span>
<span class="n">viper</span><span class="o">.</span><span class="n">AddConfigPath</span><span class="p">(</span><span class="s">"."</span><span class="p">)</span>
<span class="n">viper</span><span class="o">.</span><span class="n">AutomaticEnv</span><span class="p">()</span>
<span class="n">viper</span><span class="o">.</span><span class="n">SetDefault</span><span class="p">(</span><span class="s">"cors_allowed_origins"</span><span class="p">,</span> <span class="p">[]</span><span class="kt">string</span><span class="p">{</span><span class="s">"*"</span><span class="p">})</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">viper</span><span class="o">.</span><span class="n">ReadInConfig</span><span class="p">();</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">log</span><span class="o">.</span><span class="n">Printf</span><span class="p">(</span><span class="s">"Error reading config file, using defaults: %v"</span><span class="p">,</span> <span class="n">err</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">corsAllowedOrigins</span> <span class="o">:=</span> <span class="n">viper</span><span class="o">.</span><span class="n">GetStringSlice</span><span class="p">(</span><span class="s">"cors_allowed_origins"</span><span class="p">)</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">corsAllowedOrigins</span><span class="p">)</span> <span class="o">==</span> <span class="m">0</span> <span class="p">{</span>
<span class="n">corsAllowedOrigins</span> <span class="o">=</span> <span class="p">[]</span><span class="kt">string</span><span class="p">{</span><span class="s">"*"</span><span class="p">}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="o">&</span><span class="n">Config</span><span class="p">{</span>
<span class="n">BaseURL</span><span class="o">:</span> <span class="n">viper</span><span class="o">.</span><span class="n">GetString</span><span class="p">(</span><span class="s">"pocket_base_url"</span><span class="p">),</span>
<span class="n">JWTSecret</span><span class="o">:</span> <span class="n">viper</span><span class="o">.</span><span class="n">GetString</span><span class="p">(</span><span class="s">"jwt_secret"</span><span class="p">),</span>
<span class="n">Environment</span><span class="o">:</span> <span class="n">viper</span><span class="o">.</span><span class="n">GetString</span><span class="p">(</span><span class="s">"app_env"</span><span class="p">),</span>
<span class="n">CORSAllowedOrigins</span><span class="o">:</span> <span class="n">corsAllowedOrigins</span><span class="p">,</span>
<span class="n">Port</span><span class="o">:</span> <span class="n">viper</span><span class="o">.</span><span class="n">GetString</span><span class="p">(</span><span class="s">"port"</span><span class="p">),</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre>
</div>
<p>The <code>config.yml</code> file should be structured as follows:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight yaml"><code><span class="na">pocket_base_url</span><span class="pi">:</span> <span class="s2">"</span><span class="s">http://127.0.0.1:8090/api"</span>
<span class="na">jwt_secret</span><span class="pi">:</span> <span class="s2">"</span><span class="s">your_jwt_secret_key"</span>
<span class="na">app_env</span><span class="pi">:</span> <span class="s2">"</span><span class="s">development"</span>
<span class="na">cors_allowed_origins</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s2">"</span><span class="s">*"</span>
<span class="na">port</span><span class="pi">:</span> <span class="s2">"</span><span class="s">8080"</span>
</code></pre>
</div>
<h3>
Setting Up Golang with Gin
</h3>
<h4>
Install Gin:
</h4>
<p>To install the Gin framework in your Go project, run the following command:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>go get <span class="nt">-u</span> github.com/gin-gonic/gin
</code></pre>
</div>
<p>This will install Gin and its dependencies into your Go workspace.</p>
<h4>
API Endpoints and <code>main.go</code> Implementation
</h4>
<p>Here are the two primary API endpoints for registering and logging in users:</p>
<ul>
<li>
<code>POST /api/v1/auth/register</code> – Allows a new user to register.</li>
<li>
<code>POST /api/v1/auth/login</code> – Allows a registered user to log in and receive a JWT token.
</li>
</ul>
<div class="highlight js-code-highlight">
<pre class="highlight go"><code><span class="k">package</span> <span class="n">main</span>
<span class="k">import</span> <span class="p">(</span>
<span class="s">"context"</span>
<span class="s">"net/http"</span>
<span class="s">"os"</span>
<span class="s">"os/signal"</span>
<span class="s">"syscall"</span>
<span class="s">"time"</span>
<span class="s">"github.com/gin-contrib/cors"</span>
<span class="s">"github.com/gin-gonic/gin"</span>
<span class="s">"github.com/rowjay007/walkit/config"</span>
<span class="s">"github.com/rowjay007/walkit/internal/routes"</span>
<span class="s">"github.com/rowjay007/walkit/pkg/logger"</span>
<span class="p">)</span>
<span class="k">func</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
<span class="n">logger</span> <span class="o">:=</span> <span class="n">logger</span><span class="o">.</span><span class="n">New</span><span class="p">()</span>
<span class="n">cfg</span> <span class="o">:=</span> <span class="n">config</span><span class="o">.</span><span class="n">LoadConfig</span><span class="p">()</span>
<span class="k">if</span> <span class="n">cfg</span><span class="o">.</span><span class="n">Environment</span> <span class="o">==</span> <span class="s">"production"</span> <span class="p">{</span>
<span class="n">gin</span><span class="o">.</span><span class="n">SetMode</span><span class="p">(</span><span class="n">gin</span><span class="o">.</span><span class="n">ReleaseMode</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">router</span> <span class="o">:=</span> <span class="n">gin</span><span class="o">.</span><span class="n">New</span><span class="p">()</span>
<span class="n">router</span><span class="o">.</span><span class="n">Use</span><span class="p">(</span><span class="n">gin</span><span class="o">.</span><span class="n">Recovery</span><span class="p">())</span>
<span class="n">corsConfig</span> <span class="o">:=</span> <span class="n">cors</span><span class="o">.</span><span class="n">DefaultConfig</span><span class="p">()</span>
<span class="n">corsConfig</span><span class="o">.</span><span class="n">AllowOrigins</span> <span class="o">=</span> <span class="n">cfg</span><span class="o">.</span><span class="n">CORSAllowedOrigins</span>
<span class="n">router</span><span class="o">.</span><span class="n">Use</span><span class="p">(</span><span class="n">cors</span><span class="o">.</span><span class="n">New</span><span class="p">(</span><span class="n">corsConfig</span><span class="p">))</span>
<span class="n">routes</span><span class="o">.</span><span class="n">LoadRoutes</span><span class="p">(</span><span class="n">router</span><span class="p">)</span>
<span class="n">srv</span> <span class="o">:=</span> <span class="o">&</span><span class="n">http</span><span class="o">.</span><span class="n">Server</span><span class="p">{</span>
<span class="n">Addr</span><span class="o">:</span> <span class="s">":"</span> <span class="o">+</span> <span class="n">cfg</span><span class="o">.</span><span class="n">Port</span><span class="p">,</span>
<span class="n">Handler</span><span class="o">:</span> <span class="n">router</span><span class="p">,</span>
<span class="n">ReadTimeout</span><span class="o">:</span> <span class="m">15</span> <span class="o">*</span> <span class="n">time</span><span class="o">.</span><span class="n">Second</span><span class="p">,</span>
<span class="n">WriteTimeout</span><span class="o">:</span> <span class="m">15</span> <span class="o">*</span> <span class="n">time</span><span class="o">.</span><span class="n">Second</span><span class="p">,</span>
<span class="n">IdleTimeout</span><span class="o">:</span> <span class="m">60</span> <span class="o">*</span> <span class="n">time</span><span class="o">.</span><span class="n">Second</span><span class="p">,</span>
<span class="p">}</span>
<span class="k">go</span> <span class="k">func</span><span class="p">()</span> <span class="p">{</span>
<span class="n">logger</span><span class="o">.</span><span class="n">Info</span><span class="p">(</span><span class="s">"Starting server on port "</span> <span class="o">+</span> <span class="n">cfg</span><span class="o">.</span><span class="n">Port</span><span class="p">)</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">srv</span><span class="o">.</span><span class="n">ListenAndServe</span><span class="p">();</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="o">&&</span> <span class="n">err</span> <span class="o">!=</span> <span class="n">http</span><span class="o">.</span><span class="n">ErrServerClosed</span> <span class="p">{</span>
<span class="n">logger</span><span class="o">.</span><span class="n">Fatal</span><span class="p">(</span><span class="s">"Failed to start server: "</span> <span class="o">+</span> <span class="n">err</span><span class="o">.</span><span class="n">Error</span><span class="p">())</span>
<span class="p">}</span>
<span class="p">}()</span>
<span class="n">quit</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">(</span><span class="k">chan</span> <span class="n">os</span><span class="o">.</span><span class="n">Signal</span><span class="p">,</span> <span class="m">1</span><span class="p">)</span>
<span class="n">signal</span><span class="o">.</span><span class="n">Notify</span><span class="p">(</span><span class="n">quit</span><span class="p">,</span> <span class="n">syscall</span><span class="o">.</span><span class="n">SIGINT</span><span class="p">,</span> <span class="n">syscall</span><span class="o">.</span><span class="n">SIGTERM</span><span class="p">)</span>
<span class="o"><-</span><span class="n">quit</span>
<span class="n">logger</span><span class="o">.</span><span class="n">Info</span><span class="p">(</span><span class="s">"Shutting down server..."</span><span class="p">)</span>
<span class="n">ctx</span><span class="p">,</span> <span class="n">cancel</span> <span class="o">:=</span> <span class="n">context</span><span class="o">.</span><span class="n">WithTimeout</span><span class="p">(</span><span class="n">context</span><span class="o">.</span><span class="n">Background</span><span class="p">(),</span> <span class="m">5</span><span class="o">*</span><span class="n">time</span><span class="o">.</span><span class="n">Second</span><span class="p">)</span>
<span class="k">defer</span> <span class="n">cancel</span><span class="p">()</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">srv</span><span class="o">.</span><span class="n">Shutdown</span><span class="p">(</span><span class="n">ctx</span><span class="p">);</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">logger</span><span class="o">.</span><span class="n">Fatal</span><span class="p">(</span><span class="s">"Server forced to shutdown: "</span> <span class="o">+</span> <span class="n">err</span><span class="o">.</span><span class="n">Error</span><span class="p">())</span>
<span class="p">}</span>
<span class="n">logger</span><span class="o">.</span><span class="n">Info</span><span class="p">(</span><span class="s">"Server exiting"</span><span class="p">)</span>
<span class="p">}</span>
</code></pre>
</div>
<h3>
Integrating Repository, Service, and Handler
</h3>
<h4>
Repository Layer (<code>repository.go</code>):
</h4>
<p>This layer interacts directly with Pocketbase for data management, such as registering users or logging them in.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight go"><code><span class="k">package</span> <span class="n">repository</span>
<span class="k">import</span> <span class="p">(</span>
<span class="s">"bytes"</span>
<span class="s">"encoding/json"</span>
<span class="s">"fmt"</span>
<span class="s">"net/http"</span>
<span class="s">"github.com/rowjay007/walkit/config"</span>
<span class="s">"github.com/rowjay007/walkit/internal/model"</span>
<span class="p">)</span>
<span class="k">func</span> <span class="n">AuthAPI</span><span class="p">()</span> <span class="kt">string</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">config</span><span class="o">.</span><span class="n">LoadConfig</span><span class="p">()</span><span class="o">.</span><span class="n">BaseURL</span> <span class="o">+</span> <span class="s">"/collections/users"</span>
<span class="p">}</span>
<span class="k">func</span> <span class="n">RegisterUser</span><span class="p">(</span><span class="n">user</span> <span class="n">model</span><span class="o">.</span><span class="n">User</span><span class="p">)</span> <span class="kt">error</span> <span class="p">{</span>
<span class="n">userJSON</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">json</span><span class="o">.</span><span class="n">Marshal</span><span class="p">(</span><span class="n">user</span><span class="p">)</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Errorf</span><span class="p">(</span><span class="s">"error marshaling user data: %w"</span><span class="p">,</span> <span class="n">err</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">resp</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">http</span><span class="o">.</span><span class="n">Post</span><span class="p">(</span><span class="n">AuthAPI</span><span class="p">(),</span> <span class="s">"application/json"</span><span class="p">,</span> <span class="n">bytes</span><span class="o">.</span><span class="n">NewBuffer</span><span class="p">(</span><span class="n">userJSON</span><span class="p">))</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Errorf</span><span class="p">(</span><span class="s">"error making request: %w"</span><span class="p">,</span> <span class="n">err</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">defer</span> <span class="n">resp</span><span class="o">.</span><span class="n">Body</span><span class="o">.</span><span class="n">Close</span><span class="p">()</span>
<span class="k">if</span> <span class="n">resp</span><span class="o">.</span><span class="n">StatusCode</span> <span class="o">!=</span> <span class="n">http</span><span class="o">.</span><span class="n">StatusOK</span> <span class="o">&&</span> <span class="n">resp</span><span class="o">.</span><span class="n">StatusCode</span> <span class="o">!=</span> <span class="n">http</span><span class="o">.</span><span class="n">StatusCreated</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Errorf</span><span class="p">(</span><span class="s">"registration failed: %v"</span><span class="p">,</span> <span class="n">resp</span><span class="o">.</span><span class="n">Status</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">return</span> <span class="no">nil</span>
<span class="p">}</span>
<span class="k">func</span> <span class="n">LoginUser</span><span class="p">(</span><span class="n">login</span> <span class="n">model</span><span class="o">.</span><span class="n">LoginRequest</span><span class="p">)</span> <span class="p">(</span><span class="o">*</span><span class="n">model</span><span class="o">.</span><span class="n">LoginResponse</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
<span class="n">loginJSON</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">json</span><span class="o">.</span><span class="n">Marshal</span><span class="p">(</span><span class="n">login</span><span class="p">)</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="k">return</span> <span class="no">nil</span><span class="p">,</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Errorf</span><span class="p">(</span><span class="s">"error marshaling login data: %w"</span><span class="p">,</span> <span class="n">err</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">req</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">http</span><span class="o">.</span><span class="n">NewRequest</span><span class="p">(</span><span class="s">"POST"</span><span class="p">,</span> <span class="n">AuthAPI</span><span class="p">()</span><span class="o">+</span><span class="s">"/auth-with-password"</span><span class="p">,</span> <span class="n">bytes</span><span class="o">.</span><span class="n">NewBuffer</span><span class="p">(</span><span class="n">loginJSON</span><span class="p">))</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="k">return</span>
<span class="no">nil</span><span class="p">,</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Errorf</span><span class="p">(</span><span class="s">"error creating request: %w"</span><span class="p">,</span> <span class="n">err</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">req</span><span class="o">.</span><span class="n">Header</span><span class="o">.</span><span class="n">Set</span><span class="p">(</span><span class="s">"Content-Type"</span><span class="p">,</span> <span class="s">"application/json"</span><span class="p">)</span>
<span class="n">client</span> <span class="o">:=</span> <span class="o">&</span><span class="n">http</span><span class="o">.</span><span class="n">Client</span><span class="p">{}</span>
<span class="n">resp</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">client</span><span class="o">.</span><span class="n">Do</span><span class="p">(</span><span class="n">req</span><span class="p">)</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="k">return</span> <span class="no">nil</span><span class="p">,</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Errorf</span><span class="p">(</span><span class="s">"error making request: %w"</span><span class="p">,</span> <span class="n">err</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">defer</span> <span class="n">resp</span><span class="o">.</span><span class="n">Body</span><span class="o">.</span><span class="n">Close</span><span class="p">()</span>
<span class="k">var</span> <span class="n">response</span> <span class="n">model</span><span class="o">.</span><span class="n">LoginResponse</span>
<span class="c">// Handle response decoding here</span>
<span class="k">return</span> <span class="o">&</span><span class="n">response</span><span class="p">,</span> <span class="no">nil</span>
<span class="p">}</span>
</code></pre>
</div>
<h4>
Service Layer (<code>service.go</code>):
</h4>
<p>This layer handles business logic and interacts with the repository.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight go"><code><span class="k">package</span> <span class="n">service</span>
<span class="k">import</span> <span class="p">(</span>
<span class="s">"github.com/rowjay007/walkit/internal/model"</span>
<span class="s">"github.com/rowjay007/walkit/internal/repository"</span>
<span class="p">)</span>
<span class="k">func</span> <span class="n">LoginUser</span><span class="p">(</span><span class="n">login</span> <span class="n">model</span><span class="o">.</span><span class="n">LoginRequest</span><span class="p">)</span> <span class="p">(</span><span class="o">*</span><span class="n">model</span><span class="o">.</span><span class="n">LoginResponse</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">repository</span><span class="o">.</span><span class="n">LoginUser</span><span class="p">(</span><span class="n">login</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">func</span> <span class="n">RegisterUser</span><span class="p">(</span><span class="n">user</span> <span class="n">model</span><span class="o">.</span><span class="n">User</span><span class="p">)</span> <span class="kt">error</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">repository</span><span class="o">.</span><span class="n">RegisterUser</span><span class="p">(</span><span class="n">user</span><span class="p">)</span>
<span class="p">}</span>
</code></pre>
</div>
<h4>
Handler Layer (<code>handler.go</code>):
</h4>
<p>This layer handles HTTP requests and responses.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight go"><code><span class="k">package</span> <span class="n">handler</span>
<span class="k">import</span> <span class="p">(</span>
<span class="s">"fmt"</span>
<span class="s">"net/http"</span>
<span class="s">"strings"</span>
<span class="s">"github.com/gin-gonic/gin"</span>
<span class="s">"github.com/rowjay007/walkit/internal/model"</span>
<span class="s">"github.com/rowjay007/walkit/internal/service"</span>
<span class="s">"github.com/rowjay007/walkit/pkg/util"</span>
<span class="p">)</span>
<span class="k">func</span> <span class="n">LoginUser</span><span class="p">(</span><span class="n">c</span> <span class="o">*</span><span class="n">gin</span><span class="o">.</span><span class="n">Context</span><span class="p">)</span> <span class="p">{</span>
<span class="k">var</span> <span class="n">login</span> <span class="n">model</span><span class="o">.</span><span class="n">LoginRequest</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">c</span><span class="o">.</span><span class="n">ShouldBindJSON</span><span class="p">(</span><span class="o">&</span><span class="n">login</span><span class="p">);</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">util</span><span class="o">.</span><span class="n">RespondWithError</span><span class="p">(</span><span class="n">c</span><span class="p">,</span> <span class="n">http</span><span class="o">.</span><span class="n">StatusBadRequest</span><span class="p">,</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Sprintf</span><span class="p">(</span><span class="s">"Invalid request payload: %v"</span><span class="p">,</span> <span class="n">err</span><span class="p">))</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">validateLoginInput</span><span class="p">(</span><span class="n">login</span><span class="p">);</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">util</span><span class="o">.</span><span class="n">RespondWithError</span><span class="p">(</span><span class="n">c</span><span class="p">,</span> <span class="n">http</span><span class="o">.</span><span class="n">StatusBadRequest</span><span class="p">,</span> <span class="n">err</span><span class="o">.</span><span class="n">Error</span><span class="p">())</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="n">response</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">service</span><span class="o">.</span><span class="n">LoginUser</span><span class="p">(</span><span class="n">login</span><span class="p">)</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">statusCode</span> <span class="o">:=</span> <span class="n">determineStatusCode</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
<span class="n">util</span><span class="o">.</span><span class="n">RespondWithError</span><span class="p">(</span><span class="n">c</span><span class="p">,</span> <span class="n">statusCode</span><span class="p">,</span> <span class="s">"Login failed. Please check your credentials."</span><span class="p">)</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="n">util</span><span class="o">.</span><span class="n">RespondWithJSON</span><span class="p">(</span><span class="n">c</span><span class="p">,</span> <span class="n">http</span><span class="o">.</span><span class="n">StatusOK</span><span class="p">,</span> <span class="n">response</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">func</span> <span class="n">validateLoginInput</span><span class="p">(</span><span class="n">login</span> <span class="n">model</span><span class="o">.</span><span class="n">LoginRequest</span><span class="p">)</span> <span class="kt">error</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">strings</span><span class="o">.</span><span class="n">TrimSpace</span><span class="p">(</span><span class="n">login</span><span class="o">.</span><span class="n">Identity</span><span class="p">)</span> <span class="o">==</span> <span class="s">""</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Errorf</span><span class="p">(</span><span class="s">"identity cannot be empty"</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">login</span><span class="o">.</span><span class="n">Password</span><span class="p">)</span> <span class="o"><</span> <span class="m">8</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Errorf</span><span class="p">(</span><span class="s">"password must be at least 8 characters"</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">return</span> <span class="no">nil</span>
<span class="p">}</span>
<span class="k">func</span> <span class="n">determineStatusCode</span><span class="p">(</span><span class="n">err</span> <span class="kt">error</span><span class="p">)</span> <span class="kt">int</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">strings</span><span class="o">.</span><span class="n">Contains</span><span class="p">(</span><span class="n">strings</span><span class="o">.</span><span class="n">ToLower</span><span class="p">(</span><span class="n">err</span><span class="o">.</span><span class="n">Error</span><span class="p">()),</span> <span class="s">"invalid credentials"</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">http</span><span class="o">.</span><span class="n">StatusUnauthorized</span>
<span class="p">}</span>
<span class="k">if</span> <span class="n">strings</span><span class="o">.</span><span class="n">Contains</span><span class="p">(</span><span class="n">strings</span><span class="o">.</span><span class="n">ToLower</span><span class="p">(</span><span class="n">err</span><span class="o">.</span><span class="n">Error</span><span class="p">()),</span> <span class="s">"not found"</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">http</span><span class="o">.</span><span class="n">StatusNotFound</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">http</span><span class="o">.</span><span class="n">StatusInternalServerError</span>
<span class="p">}</span>
</code></pre>
</div>
<h4>
Response Utilities (<code>util.go</code>):
</h4>
<p>This file provides functions to send consistent responses.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight go"><code><span class="k">package</span> <span class="n">util</span>
<span class="k">import</span> <span class="p">(</span>
<span class="s">"github.com/gin-gonic/gin"</span>
<span class="p">)</span>
<span class="k">func</span> <span class="n">RespondWithError</span><span class="p">(</span><span class="n">c</span> <span class="o">*</span><span class="n">gin</span><span class="o">.</span><span class="n">Context</span><span class="p">,</span> <span class="n">code</span> <span class="kt">int</span><span class="p">,</span> <span class="n">message</span> <span class="kt">string</span><span class="p">)</span> <span class="p">{</span>
<span class="n">c</span><span class="o">.</span><span class="n">JSON</span><span class="p">(</span><span class="n">code</span><span class="p">,</span> <span class="n">gin</span><span class="o">.</span><span class="n">H</span><span class="p">{</span><span class="s">"error"</span><span class="o">:</span> <span class="n">message</span><span class="p">})</span>
<span class="p">}</span>
<span class="k">func</span> <span class="n">RespondWithJSON</span><span class="p">(</span><span class="n">c</span> <span class="o">*</span><span class="n">gin</span><span class="o">.</span><span class="n">Context</span><span class="p">,</span> <span class="n">code</span> <span class="kt">int</span><span class="p">,</span> <span class="n">payload</span> <span class="k">interface</span><span class="p">{})</span> <span class="p">{</span>
<span class="n">c</span><span class="o">.</span><span class="n">JSON</span><span class="p">(</span><span class="n">code</span><span class="p">,</span> <span class="n">payload</span><span class="p">)</span>
<span class="p">}</span>
</code></pre>
</div>
<h3>
JWT Middleware (<code>middleware.go</code>):
</h3>
<p>This middleware ensures that a valid JWT token is included in the request header.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight go"><code><span class="k">package</span> <span class="n">middleware</span>
<span class="k">import</span> <span class="p">(</span>
<span class="s">"fmt"</span>
<span class="s">"net/http"</span>
<span class="s">"strings"</span>
<span class="s">"github.com/dgrijalva/jwt-go"</span>
<span class="s">"github.com/gin-gonic/gin"</span>
<span class="s">"github.com/rowjay007/walkit/config"</span>
<span class="p">)</span>
<span class="k">func</span> <span class="n">JWTAuthMiddleware</span><span class="p">(</span><span class="n">c</span> <span class="o">*</span><span class="n">gin</span><span class="o">.</span><span class="n">Context</span><span class="p">)</span> <span class="p">{</span>
<span class="n">tokenString</span> <span class="o">:=</span> <span class="n">c</span><span class="o">.</span><span class="n">GetHeader</span><span class="p">(</span><span class="s">"Authorization"</span><span class="p">)</span>
<span class="k">if</span> <span class="n">tokenString</span> <span class="o">==</span> <span class="s">""</span> <span class="p">{</span>
<span class="n">c</span><span class="o">.</span><span class="n">JSON</span><span class="p">(</span><span class="n">http</span><span class="o">.</span><span class="n">StatusUnauthorized</span><span class="p">,</span> <span class="n">gin</span><span class="o">.</span><span class="n">H</span><span class="p">{</span><span class="s">"error"</span><span class="o">:</span> <span class="s">"Authorization token is required"</span><span class="p">})</span>
<span class="n">c</span><span class="o">.</span><span class="n">Abort</span><span class="p">()</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="n">tokenString</span> <span class="o">=</span> <span class="n">strings</span><span class="o">.</span><span class="n">TrimPrefix</span><span class="p">(</span><span class="n">tokenString</span><span class="p">,</span> <span class="s">"Bearer "</span><span class="p">)</span>
<span class="n">token</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">jwt</span><span class="o">.</span><span class="n">Parse</span><span class="p">(</span><span class="n">tokenString</span><span class="p">,</span> <span class="k">func</span><span class="p">(</span><span class="n">token</span> <span class="o">*</span><span class="n">jwt</span><span class="o">.</span><span class="n">Token</span><span class="p">)</span> <span class="p">(</span><span class="k">interface</span><span class="p">{},</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">_</span><span class="p">,</span> <span class="n">ok</span> <span class="o">:=</span> <span class="n">token</span><span class="o">.</span><span class="n">Method</span><span class="o">.</span><span class="p">(</span><span class="o">*</span><span class="n">jwt</span><span class="o">.</span><span class="n">SigningMethodHMAC</span><span class="p">);</span> <span class="o">!</span><span class="n">ok</span> <span class="p">{</span>
<span class="k">return</span> <span class="no">nil</span><span class="p">,</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Errorf</span><span class="p">(</span><span class="s">"unexpected signing method: %v"</span><span class="p">,</span> <span class="n">token</span><span class="o">.</span><span class="n">Header</span><span class="p">[</span><span class="s">"alg"</span><span class="p">])</span>
<span class="p">}</span>
<span class="k">return</span> <span class="p">[]</span><span class="kt">byte</span><span class="p">(</span><span class="n">config</span><span class="o">.</span><span class="n">LoadConfig</span><span class="p">()</span><span class="o">.</span><span class="n">JWTSecret</span><span class="p">),</span> <span class="no">nil</span>
<span class="p">})</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="o">||</span> <span class="o">!</span><span class="n">token</span><span class="o">.</span><span class="n">Valid</span> <span class="p">{</span>
<span class="n">c</span><span class="o">.</span><span class="n">JSON</span><span class="p">(</span><span class="n">http</span><span class="o">.</span><span class="n">StatusUnauthorized</span><span class="p">,</span> <span class="n">gin</span><span class="o">.</span><span class="n">H</span><span class="p">{</span><span class="s">"error"</span><span class="o">:</span> <span class="s">"Invalid or expired token"</span><span class="p">})</span>
<span class="n">c</span><span class="o">.</span><span class="n">Abort</span><span class="p">()</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="n">c</span><span class="o">.</span><span class="n">Next</span><span class="p">()</span>
<span class="p">}</span>
</code></pre>
</div>
<p>Here’s the updated conclusion with a link to the complete code:</p>
<h3>
Conclusion
</h3>
<p>In this tutorial, we’ve built a REST API using <strong>Golang</strong>, <strong>Gin</strong>, and <strong>Pocketbase</strong>. The guide covered everything from project structure to implementing user authentication with JWT tokens. We also demonstrated how to integrate multiple layers (repository, service, handler) for better modularity and scalability.</p>
<p>With this setup, you can start extending your API with more features, such as user profile management, JWT token refresh, and other advanced functionalities.</p>
<h3>
Next Steps
</h3>
<ul>
<li>
<strong>Add Password Reset</strong>: Implement a password reset flow using JWT for authentication.</li>
<li>
<strong>Use More Pocketbase Features</strong>: Pocketbase offers additional real-time features that can be useful for interactive applications.</li>
<li>
<strong>Test the API</strong>: Write tests for API endpoints to ensure robustness.</li>
<li>
<strong>Deploy</strong>: Deploy the application on platforms like AWS, Google Cloud, or DigitalOcean.</li>
</ul>
<p>By following this tutorial, you've established a solid foundation for building secure and scalable APIs using <strong>Golang</strong>, <strong>Gin</strong>, and <strong>Pocketbase</strong>.</p>
<p>You can find the complete code for this project on <a href="https://github.com/rowjay007/walkit" rel="noopener noreferrer">GitHub here</a>.</p>
Lire l'article
Ouvrir Building a REST API with Golang, Gin, and Pocketbase dans un nouvel onglet