<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Dhruv Nakum]]></title><description><![CDATA[I'm a mobile/web developer 👨‍💻 who loves to build projects and share valuable tips for programmers.
Follow me for Flutter, React/Next.js, and other awesome te]]></description><link>https://dhruvnakum.xyz</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1637753624766/fZ6HW1zPF.png</url><title>Dhruv Nakum</title><link>https://dhruvnakum.xyz</link></image><generator>RSS for Node</generator><lastBuildDate>Tue, 19 May 2026 18:48:22 GMT</lastBuildDate><atom:link href="https://dhruvnakum.xyz/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[A Practical Journey from Application to Distributed Systems - Part 6]]></title><description><![CDATA[In Part 5, we changed QuickBite to an event-driven design. Orders now writes an order and publishes an OrderCreatedevent, and Inventory processes that event later. This is a better design than direct ]]></description><link>https://dhruvnakum.xyz/a-practical-journey-from-application-to-distributed-systems-part-6</link><guid isPermaLink="true">https://dhruvnakum.xyz/a-practical-journey-from-application-to-distributed-systems-part-6</guid><category><![CDATA[kafka]]></category><category><![CDATA[distributed system]]></category><category><![CDATA[Cloud Computing]]></category><category><![CDATA[outbox pattern]]></category><category><![CDATA[Beginner Developers]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Developer]]></category><category><![CDATA[Go Language]]></category><category><![CDATA[event-driven-architecture]]></category><dc:creator><![CDATA[Dhruv Nakum]]></dc:creator><pubDate>Mon, 18 May 2026 20:40:25 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/49853b4a-fb8d-46d6-a5f9-1c701382c15d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In Part 5, we changed QuickBite to an event-driven design. Orders now writes an order and publishes an <code>OrderCreated</code>event, and Inventory processes that event later. This is a better design than direct synchronous calls, but it still has an important reliability problem.</p>
<p>Right now, Orders does two separate things:</p>
<ul>
<li><p>save the order in Postgres as <code>PENDING</code></p>
</li>
<li><p>publish <code>OrderCreated</code> to Kafka</p>
</li>
</ul>
<p>If saving to Postgres works but publishing to Kafka fails, the order gets stuck. It exists in the database, but no other service knows about it. That means the workflow stops halfway through. This is a classic reliability problem in distributed systems. In this part, we will solve it using the <strong>Outbox pattern</strong>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/7da8fbd0-8fef-466b-9ffc-7b7ac159610a.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h2>What the Outbox pattern does</h2>
<p>The Outbox pattern changes the workflow so that saving data and saving the event happen together.</p>
<h3>Before (current flow)</h3>
<p>Right now, the flow looks like this:</p>
<ul>
<li><p>write the order to the database</p>
</li>
<li><p>publish the event to Kafka</p>
</li>
</ul>
<p>That sounds fine at first, but there is a dangerous gap between those two steps.</p>
<p>If the database write succeeds but Kafka publish fails, the order is saved, but the event is lost. That leaves the system in an inconsistent state.</p>
<h3>After (with the Outbox pattern)</h3>
<p>With the Outbox pattern, the flow changes to this:</p>
<ul>
<li><p>write the order to the database</p>
</li>
<li><p>write the event into a database table called <code>outbox_events</code></p>
</li>
<li><p>do both of those in the same database transaction</p>
</li>
<li><p>a background worker later reads from <code>outbox_events</code> and publishes to Kafka</p>
</li>
</ul>
<p>This is the key idea:</p>
<blockquote>
<p>instead of publishing directly to Kafka inside the request flow, we first save the event safely in the database</p>
</blockquote>
<p>We are not changing Inventory in this part. Inventory already works fine once it receives the event. The problem is “how to guarantee the event gets published”.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/77d23278-c5c8-45ce-9155-a9e2300e3ecd.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h2>Step 1: Add migrations for the outbox table</h2>
<h5>The first thing we need for the Outbox pattern is a new database table to store events before they are published to Kafka.</h5>
<p>Create this migration file:</p>
<pre><code class="language-go">services/orders/migrations/000002_outbox.up.sql
</code></pre>
<pre><code class="language-sql">CREATE TABLE IF NOT EXISTS outbox_events (
  id BIGSERIAL PRIMARY KEY,
  event_id TEXT NOT NULL UNIQUE,
  topic TEXT NOT NULL,
  key TEXT NOT NULL,
  payload BYTEA NOT NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  published_at TIMESTAMPTZ NULL
);

CREATE INDEX IF NOT EXISTS idx_outbox_unpublished
  ON outbox_events(id)
  WHERE published_at IS NULL;
</code></pre>
<p>Now create the down migration:</p>
<pre><code class="language-plaintext">services/orders/migrations/000002_outbox.down.sql
</code></pre>
<pre><code class="language-sql">DROP TABLE IF EXISTS outbox_events;
</code></pre>
<h3>What this table is for</h3>
<p>This table stores events that still need to be published to Kafka.</p>
<p>Instead of trying to publish directly inside the request flow, we first write the event into <code>outbox_events</code>. Later, a background worker will read from this table and publish those events to Kafka.</p>
<p>So this table is basically a safe holding area for messages waiting to be published.</p>
<p>After this step, the Orders service has a place to store events safely in the database before they are sent to Kafka. That is the foundation of the Outbox pattern.</p>
<p>We have not changed the request flow yet, but now we have the table that makes the rest of the pattern possible.</p>
<hr />
<h2>Step 2: Add transaction support in Orders store</h2>
<p>Right now, the Orders store probably inserts rows directly using the database pool. That works fine when we only insert one thing at a time. But for the Outbox pattern, we now need to do <strong>two inserts together</strong>:</p>
<ul>
<li><p>insert the order row</p>
</li>
<li><p>insert the outbox event row</p>
</li>
</ul>
<p>And we need both of those to happen inside the <strong>same database transaction</strong>.</p>
<p>That way:</p>
<ul>
<li><p>either both inserts succeed</p>
</li>
<li><p>or neither one is saved</p>
</li>
</ul>
<p>This is what makes the Outbox pattern reliable.</p>
<p>Open:</p>
<pre><code class="language-plaintext">services/orders/internal/store/orders.go
</code></pre>
<p>Add these methods:</p>
<pre><code class="language-go">import "github.com/jackc/pgx/v5"

// Begin starts a DB transaction.
func (s *OrdersStore) Begin(ctx context.Context) (pgx.Tx, error) {
	return s.db.Begin(ctx)
}

// CreatePendingTx inserts an order inside a transaction as PENDING.
func (s *OrdersStore) CreatePendingTx(ctx context.Context, tx pgx.Tx, userID, note, sku string, quantity int) (int64, error) {
	var id int64
	err := tx.QueryRow(ctx,
		`INSERT INTO orders (user_id, note, status, sku, quantity)
		 VALUES (\(1, \)2, \(3, \)4, $5)
		 RETURNING id`,
		userID, note, StatusPending, sku, quantity,
	).Scan(&amp;id)
	return id, err
}
</code></pre>
<p>Keep your existing <code>Create</code> and <code>GetByID</code> methods. We are not replacing them. We are only adding transaction-aware versions.</p>
<h3>Why we need transactions here</h3>
<p>The Outbox pattern depends on one important guarantee:</p>
<blockquote>
<p>the order row and the outbox event row must be saved together</p>
</blockquote>
<p>Without a transaction, this could happen:</p>
<ol>
<li><p>order insert succeeds</p>
</li>
<li><p>outbox insert fails</p>
</li>
</ol>
<p>Now the order exists, but the event does not. That is exactly the kind of broken state we are trying to avoid. With a transaction, the database treats both inserts as one unit of work.</p>
<p>So the result is:</p>
<ul>
<li><p>both are committed together</p>
</li>
<li><p>or both are rolled back together</p>
</li>
</ul>
<hr />
<h2>Step 3: Create an outbox package in Orders</h2>
<h5>Now we will create a small package to manage outbox events inside the Orders service.</h5>
<p>Open a new folder:</p>
<pre><code class="language-plaintext">mkdir -p services/orders/internal/outbox
</code></pre>
<p>Then create this file:</p>
<pre><code class="language-plaintext">services/orders/internal/outbox/outbox.go
</code></pre>
<pre><code class="language-go">package outbox

import (
	"context"
	"time"

	"github.com/jackc/pgx/v5"
)

//This struct represents one row in the outbox_events table.
type Event struct {
	ID      int64 // database row ID
	EventID string // unique ID of the event
	Topic   string // Kafka topic to publish to
	Key     string // Kafka message key
	Payload []byte // raw event bytes, usually JSON in our case
}

//This function inserts a new row into outbox_events.
// The important part is that it takes a `pgx.Tx`, which means it runs inside an existing database transaction.
func InsertTx(ctx context.Context, tx pgx.Tx, evt Event) error {
	_, err := tx.Exec(ctx,
		`INSERT INTO outbox_events (event_id, topic, key, payload)
		 VALUES (\(1, \)2, \(3, \)4)`,
		evt.EventID, evt.Topic, evt.Key, evt.Payload,
	)
	return err
}

// This function fetches a batch of unpublished outbox rows.
func FetchUnpublishedForUpdate(ctx context.Context, tx pgx.Tx, limit int) ([]Event, error) {
	rows, err := tx.Query(ctx,
		`SELECT id, event_id, topic, key, payload
		 FROM outbox_events
		 WHERE published_at IS NULL
		 ORDER BY id
		 LIMIT $1
		 FOR UPDATE SKIP LOCKED`, // This locks the selected rows so another worker cannot take the same rows at the same time. If some rows are already locked by another worker, PostgreSQL just skips them and returns other available rows.
		limit,
	)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var out []Event
	for rows.Next() {
		var e Event
		if err := rows.Scan(&amp;e.ID, &amp;e.EventID, &amp;e.Topic, &amp;e.Key, &amp;e.Payload); err != nil {
			return nil, err
		}
		out = append(out, e)
	}
	return out, rows.Err()
}

//This function updates the outbox row after Kafka publishing succeeds.
func MarkPublishedTx(ctx context.Context, tx pgx.Tx, id int64) error {
	_, err := tx.Exec(ctx,
		`UPDATE outbox_events SET published_at = \(2 WHERE id = \)1`,
		id, time.Now().UTC(),
	)
	return err
}
</code></pre>
<h5>This package is the database helper for the Outbox pattern. It gives us three main operations:</h5>
<ul>
<li><p>insert a new outbox event inside a transaction</p>
</li>
<li><p>fetch unpublished outbox events</p>
</li>
<li><p>mark an event as published after Kafka publish succeeds</p>
</li>
</ul>
<p>So instead of writing raw outbox SQL in many places, we keep it in one focused package.</p>
<hr />
<h2>Step 4: Add outbox publisher loop</h2>
<h5>Now that Orders can store unpublished events in the <code>outbox_events</code> table, we need something that actually reads those rows and publishes them to Kafka.</h5>
<p>That job is handled by a background publisher loop.</p>
<p>Create this file:</p>
<pre><code class="language-plaintext">services/orders/internal/outbox/publisher.go
</code></pre>
<pre><code class="language-go">package outbox

import (
	"context"
	"log"
	"time"

	"github.com/jackc/pgx/v5/pgxpool"
	"github.com/segmentio/kafka-go"
)

type Publisher struct {
	DB     *pgxpool.Pool // to read and update rows in the outbox table
	Writer *kafka.Writer //to publish messages to Kafka
}

// Every 300 milliseconds, the publisher wakes up and checks whether there are any unpublished events waiting in the outbox.
// Because outbox publishing is a background job. It should keep running while the service is running, and keep retrying unpublished events until they are successfully sent.
func (p *Publisher) Run(ctx context.Context) {
	ticker := time.NewTicker(300 * time.Millisecond)
	defer ticker.Stop()

	for {
		select {
		case &lt;-ctx.Done():
			return
		case &lt;-ticker.C:
			p.publishBatch(ctx)
		}
	}
}

// This function does one batch of outbox publishing work.
//You can think of it as: “Try to publish up to 25 unpublished events right now.”
func (p *Publisher) publishBatch(ctx context.Context) {
    // The publisher starts a database transaction before fetching rows.
	tx, err := p.DB.Begin(ctx)
	if err != nil {
		log.Printf("outbox begin tx failed: %v", err)
		return
	}
	defer func() { _ = tx.Rollback(ctx) }()
    
    // This reads up to 25 unpublished outbox rows.
	events, err := FetchUnpublishedForUpdate(ctx, tx, 25)
	if err != nil {
		log.Printf("outbox fetch failed: %v", err)
		return
	}
	if len(events) == 0 {
		_ = tx.Commit(ctx)
		return
	}

	for _, e := range events {
        // For every fetched outbox row. This sends the event to Kafka
		err := p.Writer.WriteMessages(ctx, kafka.Message{
			Topic: e.Topic,
			Key:   []byte(e.Key),
			Value: e.Payload,
		})
		if err != nil {
			// rollback =&gt; events stay unpublished, retry later
			log.Printf("outbox kafka publish failed: %v", err)
			return
		}
        // If Kafka publish succeeds, we then do. This updates published_at in the database.
		if err := MarkPublishedTx(ctx, tx, e.ID); err != nil {
			log.Printf("outbox mark published failed: %v", err)
			return
		}
	}
    
    // This final commit makes all those changes permanent.
	if err := tx.Commit(ctx); err != nil {
		log.Printf("outbox commit failed: %v", err)
	}
}
</code></pre>
<h5>This file adds the background worker that makes the Outbox pattern actually work.</h5>
<p>Its job is simple:</p>
<ul>
<li><p>look for unpublished events in <code>outbox_events</code></p>
</li>
<li><p>publish them to Kafka</p>
</li>
<li><p>mark them as published</p>
</li>
</ul>
<p>So instead of publishing directly during the HTTP request, Orders now has a dedicated background process for publishing safely.</p>
<h2>Step 5: Update create-order handler to write to Outbox instead of Kafka directly</h2>
<h5>Where: <code>services/orders/internal/http/http.go</code></h5>
<p>Right now, the create-order handler publishes the event to Kafka directly using:</p>
<pre><code class="language-plaintext">kafkabus.PublishOrderCreated(...)
</code></pre>
<p>In this part, we replace that direct publish with the Outbox flow.</p>
<p>The new sequence becomes:</p>
<ul>
<li><p>start a database transaction</p>
</li>
<li><p>insert the order row</p>
</li>
<li><p>insert the outbox event row</p>
</li>
<li><p>commit the transaction</p>
</li>
<li><p>return the response</p>
</li>
</ul>
<p>That means the HTTP request no longer depends on Kafka being available immediately.</p>
<p>Now update the “create order” logic to:</p>
<pre><code class="language-go">// We begin a database transaction
tx, err := s.store.Begin(ctx)
if err != nil {
	http.Error(w, "db error", http.StatusInternalServerError)
	return
}
defer func() { _ = tx.Rollback(ctx) }()

// This creates the order row inside the transaction. At this point, we have a real order ID that we can use in the event.
id, err := s.store.CreatePendingTx(ctx, tx, req.UserID, req.Note, req.Sku, req.Quantity)
if err != nil {
	http.Error(w, "db error", http.StatusInternalServerError)
	return
}

// This creates the event payload we eventually want to publish to Kafka.
evt := kafkabus.OrderCreated{
	EventID:  uuid.New().String(),
	Type:     "OrderCreated",
	Time:     time.Now().UTC(),
	OrderID:  id,
	UserID:   req.UserID,
	Sku:      req.Sku,
	Quantity: int32(req.Quantity),
}

// This turns the event struct into bytes so it can be stored in the outbox_events table.
payload, _ := json.Marshal(evt)

// This stores the event inside the outbox_events table in the same transaction as the order row
if err := outbox.InsertTx(ctx, tx, outbox.Event{
	EventID: evt.EventID,
	Topic:   "orders.v1",
	Key:     strconv.FormatInt(id, 10),
	Payload: payload,
}); err != nil {
	http.Error(w, "db error", http.StatusInternalServerError)
	return
}

// Only after both inserts succeed do we commit the transaction.
if err := tx.Commit(ctx); err != nil {
	http.Error(w, "db error", http.StatusInternalServerError)
	return
}

writeJSON(w, http.StatusAccepted, createOrderResponse{ID: id})
</code></pre>
<p>In summary, we remove the direct Kafka publish from the create-order handler. Instead, we store the order row and the event row together inside one transaction, then return <code>202 Accepted</code>. The actual Kafka publish is now handled later by the outbox worker.</p>
<hr />
<h2>Step 6: Start the outbox publisher from Orders main.go</h2>
<h5>Now that Orders can store events in the <code>outbox_events</code> table, we need to start the background publisher when the service boots.</h5>
<p>Open: <code>services/orders/cmd/orders/main.go</code></p>
<p>After creating the Kafka bus:</p>
<pre><code class="language-go">bus := kafkabus.New(...)
</code></pre>
<p>start the Outbox publisher:</p>
<pre><code class="language-go">pub := &amp;outbox.Publisher{
	DB:     pool,
	Writer: bus.OrdersWriter,
}
go pub.Run(ctx)
</code></pre>
<p>This creates the background worker that continuously checks the <code>outbox_events</code> table for unpublished events.</p>
<p>Once it finds them, it:</p>
<ul>
<li><p>publishes them to Kafka</p>
</li>
<li><p>marks them as published in the database</p>
</li>
</ul>
<hr />
<h2>How to test Outbox</h2>
<p>I encountered few problems which you might also. So there are few fixed which we need to do:</p>
<ol>
<li>We need to remove <code>Topic</code> from the outbox message (the writer already targets <code>orders.v1</code>) in <code>/orders/internal/outbox/publisher.go</code></li>
</ol>
<pre><code class="language-go">err := p.Writer.WriteMessages(ctx, kafka.Message{
    // Topic: -- removed
	Key:   []byte(e.Key),
	Value: e.Payload,
})
</code></pre>
<ol>
<li>Add red panda dependency in <code>inventory</code> service</li>
</ol>
<pre><code class="language-go">inventory: 
  ...
    depends_on:
      redpanda:
        condition: service_healthy
</code></pre>
<p>That's it. Now lets go ahead and test it</p>
<h5>This is the most important part of this section, because it proves that the Outbox pattern is actually solving the reliability problem we introduced earlier.</h5>
<p>We want to verify two things:</p>
<ul>
<li><p>the normal flow still works</p>
</li>
<li><p>events are not lost even when Kafka is temporarily down</p>
</li>
</ul>
<h3>Test 1: Normal flow still works</h3>
<p>First, make sure the normal event-driven flow still behaves as expected.</p>
<p>Create an order and confirm that it eventually becomes:</p>
<ul>
<li><p><code>CONFIRMED</code></p>
</li>
<li><p>or <code>CANCELLED</code></p>
</li>
</ul>
<p>This tells us that adding the Outbox pattern did not break the existing async workflow.</p>
<h3>Test 2: Kafka-down test</h3>
<p>This is the test that really proves the Outbox pattern is working.</p>
<h4>Step 1: Stop Redpanda</h4>
<pre><code class="language-plaintext">docker compose stop redpanda
</code></pre>
<p>Now Kafka is unavailable.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/d88060c3-f12e-4ad7-a66f-33c74b9c9f28.png" alt="" style="display:block;margin:0 auto" />

<h4>Step 2: Create an order</h4>
<pre><code class="language-plaintext">curl -X POST http://localhost:8080/v1/orders \                                             
  -H 'Content-Type: application/json' \
  -d '{"userId":"u1","sku":"burger","quantity":1}'
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/2a50e424-aec9-42ec-b9f7-c1dd6cd9cad8.png" alt="" style="display:block;margin:0 auto" />

<p>The request should still succeed. That means:</p>
<ul>
<li><p>the order is created in Postgres</p>
</li>
<li><p>the outbox event is also saved in Postgres</p>
</li>
<li><p>but the event cannot be published yet because Kafka is down</p>
</li>
</ul>
<p>So at this point, the order should exist in the database as:</p>
<ul>
<li><code>PENDING</code></li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/34c85a1f-25a8-4910-a479-611380e95915.png" alt="" style="display:block;margin:0 auto" />

<p>And Inventory will not process it yet, because it never received the event.</p>
<p>This is the key difference from the old design.</p>
<h4>Step 3: Start Redpanda again</h4>
<pre><code class="language-go">docker compose start redpanda
</code></pre>
<p>Once Kafka comes back:</p>
<ul>
<li>The outbox publisher wakes up, it reads the unpublished event from <code>outbox_events</code>, it publishes that event to Kafka. Inventory consumes it. Inventory processes the stock reservation. Orders later receives the result and updates the order status</li>
</ul>
<p>So the order should eventually move from:</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/fdbc8e30-24dd-4130-9b29-3f2e970c15b8.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-plaintext">PENDING -&gt; CONFIRMED/ CANCELLED
</code></pre>
<hr />
<h2>Conclusion</h2>
<p>At this point, QuickBite is starting to feel much more like a real production-style backend.</p>
<p>We solved one of the most common reliability problems in event-driven systems: what happens when Kafka is unavailable at the wrong time. With the Outbox pattern in place, Orders no longer risks losing the event just because the broker is temporarily down. The order and the event are stored safely together, and publishing can happen later. That is a big step forward.</p>
<p>But reliability is not finished yet. The next challenges are duplicates, retries, and bad messages. In the next part, we will introduce idempotency so repeated deliveries do not corrupt state, and we will also start making Inventory durable so it can survive restarts properly.</p>
]]></content:encoded></item><item><title><![CDATA[A Practical Journey from Application to Distributed Systems - Part 5]]></title><description><![CDATA[In Part 4, we made Orders call Inventory directly using gRPC. That gave us a working flow, but it also showed a common problem like Orders has to wait for Inventory every time. If Inventory is slow, O]]></description><link>https://dhruvnakum.xyz/a-practical-journey-from-application-to-distributed-systems-part-5</link><guid isPermaLink="true">https://dhruvnakum.xyz/a-practical-journey-from-application-to-distributed-systems-part-5</guid><category><![CDATA[kafka]]></category><category><![CDATA[distributed system]]></category><category><![CDATA[Docker]]></category><category><![CDATA[System Design]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Developer]]></category><category><![CDATA[Beginner Developers]]></category><category><![CDATA[event-driven-architecture]]></category><category><![CDATA[gRPC]]></category><category><![CDATA[redpanda]]></category><dc:creator><![CDATA[Dhruv Nakum]]></dc:creator><pubDate>Sat, 09 May 2026 20:16:11 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/8286dc81-cfdb-488f-b4ca-5dd44aff080f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In Part 4, we made Orders call Inventory directly using <strong>gRPC</strong>. That gave us a working flow, but it also showed a common problem like Orders has to wait for Inventory every time. If Inventory is slow, Orders becomes slow. If Inventory is down, Orders fails too.</p>
<p>In this part, we improve that design by introducing <strong>Kafka</strong>. We will use Redpanda locally since it is Kafka-compatible and simple to run. We will understand what Kafka and Redpanda is.</p>
<p>Instead of making Orders wait for Inventory, we will switch to an event-driven flow. So, Orders will publish an event, Inventory will consume it, and then Inventory will publish the result.</p>
<p>We keep the gRPC server running in Inventory because it is still useful for learning and future steps, but in Part 5 the main workflow is Kafka-based.S</p>
<p>This is the point where the project starts to feel like a real distributed system.</p>
<hr />
<h2>What we are building in this part</h2>
<p>In this part, we will change the order flow from <strong>synchronous</strong> to <strong>asynchronous</strong>.</p>
<h3>Before (Part 4)</h3>
<p>The flow looked like this:</p>
<ul>
<li><p>Orders creates the order as <code>PENDING</code></p>
</li>
<li><p>Orders calls Inventory directly using gRPC</p>
</li>
<li><p>Orders immediately updates the order to <code>CONFIRMED</code> or <code>CANCELLED</code></p>
</li>
</ul>
<blockquote>
<p>That means Orders has to wait for Inventory before it can finish the request.</p>
</blockquote>
<h3>After (Part 5)</h3>
<p>Now the flow will look like this:</p>
<ul>
<li><p>Orders creates the order as <code>PENDING</code></p>
</li>
<li><p>Orders publishes an event to Kafka: <code>OrderCreated</code></p>
</li>
<li><p>Inventory consumes that event and makes a stock decision</p>
</li>
<li><p>Inventory publishes a result event: <code>InventoryReserved</code> or <code>InventoryRejected</code></p>
</li>
<li><p>Orders consumes the result event and updates the order status</p>
</li>
</ul>
<blockquote>
<p>So instead of waiting directly for Inventory, Orders hands off the work through Kafka.</p>
</blockquote>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/70578b40-6c5b-4740-91df-7add5b748dd0.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h2>What Kafka is</h2>
<p>Kafka is a system for sending and storing messages, often called <strong>events</strong>.</p>
<p>Instead of one service calling another service directly and waiting for a response, a service can publish an event to Kafka. Other services can then read that event and react to it.</p>
<p>You can think of Kafka as a shared event pipeline between services.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/e2453a4f-b289-4245-a8a2-e2031184f0fd.png" alt="" style="display:block;margin:0 auto" />

<h2>Some simple terms to understand</h2>
<h3>Topic</h3>
<p>A <strong>topic</strong> is a named stream of events.</p>
<p>Example:</p>
<pre><code class="language-plaintext">orders.v1
</code></pre>
<p>If the Orders service publishes an <code>OrderCreated</code> event, it might send it to the <code>orders.v1</code> topic.</p>
<p>You can think of a topic like a category or channel where related messages are stored.</p>
<h3>Producer</h3>
<p>A <strong>producer</strong> is a service that publishes messages to a topic.</p>
<p>For example:</p>
<ul>
<li>Orders can be a producer when it publishes <code>OrderCreated</code></li>
</ul>
<h3>Consumer</h3>
<p>A <strong>consumer</strong> is a service that reads messages from a topic.</p>
<p>For example:</p>
<ul>
<li>Inventory can be a consumer when it reads <code>OrderCreated</code></li>
</ul>
<h3>Consumer group</h3>
<p>A <strong>consumer group</strong> allows multiple consumers to share the work of reading messages.</p>
<p>This is useful when you want to scale processing across multiple instances of the same service.</p>
<p>You can think of a consumer group as a team of workers splitting the same queue.</p>
<h3>Partition</h3>
<p>A topic is split into <strong>partitions</strong>.</p>
<p>Partitions are what allow Kafka to scale. Instead of one big single stream, a topic can be divided into multiple smaller streams that can be processed in parallel.</p>
<p>So:</p>
<ul>
<li><p>one topic</p>
</li>
<li><p>multiple partitions</p>
</li>
<li><p>more throughput and more scaling</p>
</li>
</ul>
<h3>Offset</h3>
<p>An <strong>offset</strong> is the position of a message inside a partition.</p>
<p>You can think of it like a message number.</p>
<p>So if a consumer reads up to offset <code>42</code>, it means it has processed messages up to that point in that partition.</p>
<h2>Why Kafka is useful</h2>
<p>One of Kafka’s biggest strengths is that consumers can crash, restart, and continue from where they left off. That means the system is more resilient. A service does not have to stay alive all the time to avoid losing work.</p>
<p>This is one of the reasons Kafka is so useful in distributed systems. In our case, Kafka lets us stop making Orders wait directly on Inventory.</p>
<p>Instead:</p>
<ul>
<li><p>Orders publishes an event</p>
</li>
<li><p>Inventory reads it and processes it</p>
</li>
<li><p>Inventory publishes the result</p>
</li>
<li><p>Orders reads that result later</p>
</li>
</ul>
<p>That makes the system more asynchronous and less tightly coupled.</p>
<hr />
<h2>Why Redpanda?</h2>
<p>For this project, we will use <strong>Redpanda</strong> locally.</p>
<p>Redpanda is <strong>Kafka-compatible</strong>, which means our code can talk to it the same way it would talk to Kafka. The big advantage is that Redpanda is much easier to run in local development. We can start it as a single container, without needing a more complex setup.</p>
<p>It also comes with a command-line tool called <code>rpk</code>, which is very useful for:</p>
<ul>
<li><p>creating topics</p>
</li>
<li><p>checking cluster state</p>
</li>
<li><p>debugging message flow</p>
</li>
</ul>
<p>That makes it a great choice for learning and local experimentation.</p>
<p>So for this project, Redpanda gives us the Kafka model and APIs we want, but with a much simpler developer experience.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/82e754a2-3db5-400f-b868-37c5b5512fac.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h2>Step 1: Add Redpanda to Docker Compose</h2>
<p>In your root <code>docker-compose.yml</code>, add this service:</p>
<pre><code class="language-dockerfile">  redpanda:
    image: redpandadata/redpanda:latest
    healthcheck:
      test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:9644/v1/status/ready &gt;/dev/null"]
      interval: 5s
      timeout: 5s
      retries: 12
      start_period: 30s
    command:
      - redpanda
      - start
      - --overprovisioned
      - --smp
      - "1"
      - --memory
      - 1G
      - --reserve-memory
      - 0M
      - --node-id
      - "0"
      - --check=false
      - --kafka-addr
      - internal://0.0.0.0:9092,external://0.0.0.0:19092
      - --advertise-kafka-addr
      - internal://redpanda:9092,external://localhost:19092
    ports:
      - "19092:19092"
      - "9644:9644"
</code></pre>
<p>This adds Redpanda as a local Kafka-compatible broker for our project.</p>
<h2>What this service does</h2>
<p>This container runs Redpanda, which will act as our event system.</p>
<p>It will store topics such as:</p>
<ul>
<li><p><code>orders.v1</code></p>
</li>
<li><p><code>inventory.v1</code></p>
</li>
</ul>
<p>and later our services will publish and consume events through it.</p>
<h2>You might ask Why there are so many command options?</h2>
<p>Don't worry, most of these options are there to make Redpanda easier to run on a local machine.</p>
<p>For example:</p>
<ul>
<li><p><code>--overprovisioned</code></p>
</li>
<li><p><code>--smp "1"</code></p>
</li>
<li><p><code>--memory 1G</code></p>
</li>
<li><p><code>--reserve-memory 0M</code></p>
</li>
<li><p><code>--check=false</code></p>
</li>
</ul>
<p>These settings reduce the resource requirements and make Redpanda friendlier for local development.</p>
<p>You do not need to memorize all of them. The main thing to understand is that they help us run a lightweight single-node Redpanda setup on our laptop.</p>
<h2>Why we configure both <code>internal</code> and <code>external</code> addresses</h2>
<p>This is the most important part of the setup. Redpanda needs to be reachable from <strong>two different places</strong>:</p>
<h3>1. From inside Docker Compose</h3>
<p>Other containers, like Orders and Inventory, need to connect to Redpanda using the Docker service name:</p>
<pre><code class="language-plaintext">redpanda:9092
</code></pre>
<p>That is the <strong>internal</strong> address.</p>
<h3>2. From your laptop</h3>
<p>If you want to connect from your machine using tools like <code>rpk</code>, you cannot use the Docker service name <code>redpanda</code>.</p>
<p>From your laptop, you need to connect using:</p>
<pre><code class="language-plaintext">localhost:19092
</code></pre>
<p>That is the <strong>external</strong> address.</p>
<p>So here we are saying:</p>
<ul>
<li><p>containers should use <code>redpanda:9092</code></p>
</li>
<li><p>your laptop should use <code>localhost:19092</code></p>
</li>
</ul>
<blockquote>
<p>It was all configuration part, so you don't need to memorize it, just understand what it is doing and why. That's it.</p>
</blockquote>
<hr />
<h2>Step 2: Create topics</h2>
<p>First, start the containers:</p>
<pre><code class="language-plaintext">docker compose up -d --build
</code></pre>
<p>Once everything is running, create the Kafka topics:</p>
<pre><code class="language-plaintext">docker compose exec redpanda rpk topic create orders.v1 -p 3
docker compose exec redpanda rpk topic create inventory.v1 -p 3
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/f5e97ae0-70e9-44b2-ad5b-042a1cda9962.png" alt="" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/c2459730-e72b-46b1-b224-5332e65e0dca.png" alt="" style="display:block;margin:0 auto" />

<p>These commands create two topics:</p>
<ul>
<li><p><code>orders.v1</code></p>
<ul>
<li><p>This topic will carry events produced by the Orders service.</p>
</li>
<li><p>For example:</p>
<ul>
<li><code>OrderCreated</code></li>
</ul>
<p>Inventory will later consume events from this topic.</p>
</li>
</ul>
</li>
<li><p><code>inventory.v1</code></p>
<ul>
<li><p>This topic will carry events produced by the Inventory service.</p>
</li>
<li><p>For example:</p>
<ul>
<li><p><code>InventoryReserved</code></p>
</li>
<li><p><code>InventoryRejected</code></p>
</li>
</ul>
<p>Orders will later consume these result events.</p>
</li>
</ul>
</li>
</ul>
<p>To check if the topics are created successfully run below command:</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/45d5614b-fc15-489b-8421-419eaab63b58.png" alt="" style="display:block;margin:0 auto" />

<h3>What are partitions?</h3>
<p>A Kafka topic is split into <strong>partitions</strong>. You can think of a partition as one separate ordered lane of messages inside a topic.</p>
<p>So when we create a topic with:</p>
<pre><code class="language-plaintext">rpk topic create orders.v1 -p 3
</code></pre>
<p>we are saying:</p>
<blockquote>
<p>create the <code>orders.v1</code> topic with 3 partitions</p>
</blockquote>
<p>That gives us a few benefits.</p>
<h5><strong>Parallel processing:</strong> Multiple consumers can process different partitions at the same time, which helps the system scale.</h5>
<p><strong>Better throughput</strong>: Instead of all messages going through one single lane, Kafka can spread work across multiple partitions.</p>
<p><strong>Ordering by key:</strong> If we publish messages with the same key, such as <code>orderId</code>, Kafka will consistently send those messages to the same partition.</p>
<p>That is useful because Kafka guarantees ordering <strong>within a partition</strong>. So if all events for order <code>123</code> use the same key, Kafka keeps those events in order for that order.</p>
<hr />
<h3>Step 3: Add Kafka library in Go services</h3>
<p>To talk to Redpanda from Go, we need a Kafka client library.</p>
<p>For this project, we will use <code>kafka-go</code>, which is a popular Go library for working with Kafka-compatible systems.</p>
<p>We need to add it to both services, because:</p>
<ul>
<li><p><strong>Orders</strong> will publish events</p>
</li>
<li><p><strong>Inventory</strong> will consume events and publish results</p>
</li>
</ul>
<h3>Add it to Orders:</h3>
<pre><code class="language-plaintext">cd services/orders
go get github.com/segmentio/kafka-go
go mod tidy
cd ../..
</code></pre>
<h3>Add it to Inventory:</h3>
<pre><code class="language-dockerfile">cd services/inventory
go get github.com/segmentio/kafka-go
go mod tidy
cd ../..
</code></pre>
<p>This adds the <code>kafka-go</code> library as a dependency in the service’s <code>go.mod</code> file. That means the service can now use Kafka producers and consumers in Go code.</p>
<p>You might ask why to add it for both the service. At first, it might seem like only one service needs Kafka, but both do.</p>
<h4>Orders will use Kafka to:</h4>
<ul>
<li><p>publish <code>OrderCreated</code> events</p>
</li>
<li><p>later consume inventory result events</p>
</li>
</ul>
<h4>Inventory will use Kafka to:</h4>
<ul>
<li><p>consume <code>OrderCreated</code></p>
</li>
<li><p>publish <code>InventoryReserved</code> or <code>InventoryRejected</code></p>
</li>
</ul>
<p>So both services need Kafka support.</p>
<hr />
<h2>Step 4: Define event formats (simple JSON for now)</h2>
<p>In a production system, you could also use protobuf for Kafka events. But while learning, JSON is easier to work with because you can print the payload directly and understand what is being sent.</p>
<p>So for now, we will keep the event format simple and use JSON.</p>
<p>We will use two main kinds of events:</p>
<ul>
<li><p><code>OrderCreated</code></p>
</li>
<li><p><code>InventoryReserved</code> / <code>InventoryRejected</code></p>
</li>
</ul>
<h2><code>OrderCreated</code></h2>
<p>This event will be published by the Orders service when a new order is created.</p>
<p>It contains:</p>
<ul>
<li><p><code>orderId</code> - which order this event belongs to</p>
</li>
<li><p><code>userId</code> - which user placed the order</p>
</li>
<li><p><code>sku</code> - which product is being ordered, such as <code>burger</code></p>
</li>
<li><p><code>quantity</code> - how many items are requested</p>
</li>
<li><p><code>eventId</code> - a unique ID for this event</p>
</li>
<li><p><code>time</code> - when the event was created</p>
</li>
</ul>
<p>This event tells the rest of the system:</p>
<blockquote>
<p>“A new order was created, and inventory should now decide whether stock can be reserved.”</p>
</blockquote>
<h2><code>InventoryReserved</code> / <code>InventoryRejected</code></h2>
<p>These events will be published by the Inventory service after it processes an order.</p>
<p>They contain:</p>
<ul>
<li><p><code>orderId</code> → which order this result belongs to</p>
</li>
<li><p><code>reserved</code> → <code>true</code> if stock was reserved, <code>false</code> if it was rejected</p>
</li>
<li><p><code>reason</code> → an optional explanation, usually used when reservation fails</p>
</li>
<li><p><code>eventId</code> → a unique ID for this event</p>
</li>
<li><p><code>time</code> → when the result event was created</p>
</li>
</ul>
<p>So these events tell Orders:</p>
<ul>
<li><p>whether stock reservation succeeded</p>
</li>
<li><p>and, if it failed, why</p>
</li>
</ul>
<p>The <code>eventId</code> is important because later we will use it for <strong>idempotency</strong> and <strong>de-duplication</strong>. In event-driven systems, the same message can sometimes be delivered more than once. A unique event ID helps the service recognize whether it has already processed that event before.</p>
<hr />
<h2>Step 5: Orders publishes OrderCreated instead of calling gRPC</h2>
<p>This is the first major design change in this part. Until now, Orders was calling Inventory directly using gRPC. Now we are changing that flow. Instead of waiting for Inventory immediately, Orders will publish an event to Kafka.</p>
<p>That means Orders will no longer ask:</p>
<blockquote>
<p>“Can you reserve stock right now?”</p>
</blockquote>
<p>Instead, it will say:</p>
<blockquote>
<p>“An order was created. Someone should process it.”</p>
</blockquote>
<p>That “someone” will be the Inventory service.</p>
<h3>5.1 Create a Kafka “bus” in Orders</h3>
<p>To keep Kafka setup in one place, we will create a small helper package.</p>
<p>Create: <code>services/orders/internal/kafkabus/bus.go</code></p>
<pre><code class="language-go">package kafkabus

import (
	"strings"

	"github.com/segmentio/kafka-go"
)

// This struct holds the Kafka clients that Orders needs.
type Bus struct {
    // This is the Kafka producer for Orders.
	OrdersWriter    *kafka.Writer
    // This is the Kafka consumer for Orders.
	InventoryReader *kafka.Reader
}

// This creates the Kafka writer and reader.
func New(brokers []string) *Bus {
	return &amp;Bus{
        /*
            1. connect to the given Kafka brokers
            2. publish messages to orders.v1
            3. use kafka.Hash{} to choose partitions based on message key
        */
		OrdersWriter: &amp;kafka.Writer{
			Addr:                   kafka.TCP(brokers...),
			Topic:                  "orders.v1",
			Balancer:               &amp;kafka.Hash{},
			AllowAutoTopicCreation: true,
		},
        
        // This creates a Kafka consumer for inventory.v1.
		InventoryReader: kafka.NewReader(kafka.ReaderConfig{
			Brokers:  brokers,
			Topic:    "inventory.v1",
            // This is the most important part:
            // This means the Orders service reads inventory results as part of the consumer group named orders-service.
			GroupID:  "orders-service",
			MinBytes: 1,
			MaxBytes: 10e6,
		}),
	}
}

// This closes both the reader and the writer when the Orders service shuts down.
func (b *Bus) Close() error {
	_ = b.InventoryReader.Close()
	return b.OrdersWriter.Close()
}

// This helper takes a comma-separated string of broker addresses and turns it into a Go slice.
func BrokersFromEnv(s string) []string {
	parts := strings.Split(s, ",")
	out := make([]string, 0, len(parts))
	for _, p := range parts {
		p = strings.TrimSpace(p)
		if p != "" {
			out = append(out, p)
		}
	}
	return out
}
</code></pre>
<h2>What this file is doing</h2>
<p>This file creates a small Kafka wrapper for the Orders service.</p>
<p>It gives Orders two things:</p>
<ul>
<li><p>a <strong>writer</strong> for publishing order events</p>
</li>
<li><p>a <strong>reader</strong> for consuming inventory result events</p>
</li>
</ul>
<p>So instead of putting Kafka setup directly inside <code>main.go</code> or inside HTTP handlers, we keep it in one dedicated place.</p>
<p>That makes the code cleaner and easier to extend later.</p>
<h3>5.2 Define events and publish function</h3>
<p>Now we need to define the actual event structures that Orders will use when talking through Kafka.</p>
<p>Create: <code>services/orders/internal/kafkabus/events.go</code></p>
<pre><code class="language-go">package kafkabus

import (
	"context"
	"encoding/json"
	"strconv"
	"time"

	"github.com/segmentio/kafka-go"
)

// This is the event that the Orders service will publish when a new order is created.
type OrderCreated struct {
	EventID  string    `json:"eventId"`
	Type     string    `json:"type"`
	Time     time.Time `json:"time"`
	OrderID  int64     `json:"orderId"`
	UserID   string    `json:"userId"`
	Sku      string    `json:"sku"`
	Quantity int32     `json:"quantity"`
}

// This is the event that Inventory will publish after processing the order.
type InventoryResult struct {
	EventID   string    `json:"eventId"`
	Type      string    `json:"type"`
	Time      time.Time `json:"time"`
	OrderID   int64     `json:"orderId"`
	Reserved  bool      `json:"reserved"`
    // The omitempty tag on Reason means that if the reason is empty, it will be left out of the JSON.
	Reason    string    `json:"reason,omitempty"`
}

// This helper takes an OrderCreated struct and publishes it to Kafka.
func PublishOrderCreated(ctx context.Context, w *kafka.Writer, evt OrderCreated) error {
    // Since we are using JSON for events right now, this turns the Go struct into a JSON byte payload.
	val, err := json.Marshal(evt)
	if err != nil {
		return err
	}

    // This writes the event into Kafka.
	return w.WriteMessages(ctx, kafka.Message{
		Key:   []byte(strconv.FormatInt(evt.OrderID, 10)),
		Value: val,
	})
}
</code></pre>
<p>This file gives us two things:</p>
<ul>
<li><p>the Go structs that represent our Kafka events</p>
</li>
<li><p>a helper function <code>PublishOrderCreated</code> to publish an <code>OrderCreated</code> event</p>
</li>
</ul>
<p>So instead of building Kafka messages manually all over the codebase, we define the event format once and reuse it.</p>
<h3>5.3 Update Orders HTTP handler: create order + publish event</h3>
<p>In this part of the blog series, we are keeping the flow simple.</p>
<p>For now, when a new order is created, we will:</p>
<ul>
<li><p>create the order in Postgres as <code>PENDING</code></p>
</li>
<li><p>publish an <code>OrderCreated</code> event to Kafka</p>
</li>
<li><p>return <code>202 Accepted</code></p>
</li>
</ul>
<p>Later, when we introduce the Outbox pattern, we will make this flow safer even if Kafka is temporarily down. But for now, this version is enough to teach the event-driven flow clearly.</p>
<p>Open:<code>services/orders/internal/http/http.go</code></p>
<p>Since the HTTP handler now needs to publish Kafka events, the Orders HTTP server needs access to the Kafka bus.</p>
<p>Update the <code>Server</code> struct like this:</p>
<pre><code class="language-go">type Server struct {
	store *store.OrdersStore
	inv   *inv.Client
	bus   *kafkabus.Bus
}

func NewServer(store *store.OrdersStore, inv *inv.Client, bus *kafkabus.Bus) *Server {
	return &amp;Server{store: store, inv: inv, bus: bus}
}
</code></pre>
<p>Earlier, the HTTP server only needed access to the database store. Now it also needs access to Kafka, because after creating an order it must publish an <code>OrderCreated</code> event.</p>
<p>So the server now depends on two things:</p>
<ul>
<li><p><strong>the store</strong>, for database operations</p>
</li>
<li><p><strong>the bus</strong>, for Kafka operations</p>
</li>
</ul>
<p>Now inside the <code>handleCreateOrder</code> function in <code>http.go</code> After creating the order as <code>PENDING</code>, build the event like this:</p>
<pre><code class="language-go">// Create order as PENDING
// ...

evt := kafkabus.OrderCreated{
	EventID:  uuid.New().String(),
	Type:     "OrderCreated",
	Time:     time.Now().UTC(),
	OrderID:  id,
	UserID:   req.UserID,
	Sku:      req.Sku,
	Quantity: int32(req.Quantity),
}
</code></pre>
<p>This creates the event payload that will be sent to Kafka.</p>
<p>It tells the rest of the system:</p>
<blockquote>
<p>“A new order was created, and Inventory should now process it.”</p>
</blockquote>
<p>Now publish it using the Kafka bus:</p>
<pre><code class="language-go">
if err := kafkabus.PublishOrderCreated(ctx, s.bus.OrdersWriter, evt); err != nil {
	http.Error(w, "kafka publish failed", http.StatusServiceUnavailable)
	return
}

writeJSON(w, http.StatusAccepted, createOrderResponse{ID: id})
</code></pre>
<p>If publishing fails, we return:</p>
<ul>
<li><code>503 Service Unavailable</code></li>
</ul>
<p>That means the order was created in the database, but the event could not be published yet. Again, this is a simplification for now. Later, the Outbox pattern will solve this more safely.</p>
<p>Also, We use <code>202 Accepted</code> instead of <code>201 Created</code> because the full order processing is no longer finished immediately.</p>
<blockquote>
<p>One important detail: publishing an event does not automatically update the order status. The status changes only if Inventory consumes the event and sends a result back, and if Orders is also consuming those result events.</p>
<p>That means we must start <strong>two background consumer loops</strong> in the next steps.</p>
</blockquote>
<p><strong>IMPORTANT:</strong> Make sure you comment out the previous code that we wrote in the Part 4 because no we are no longer updating status directly:</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/ea3c39c8-91de-401e-918f-7609128c2042.png" alt="" style="display:block;margin:0 auto" />

<h2>Step 6: Inventory consumes OrderCreated and publishes result</h2>
<p>Now Inventory stops being just a gRPC server and starts acting as a <strong>Kafka consumer</strong> too.</p>
<p>That means Inventory will now:</p>
<ul>
<li><p>read <code>OrderCreated</code> events from Kafka</p>
</li>
<li><p>try to reserve stock</p>
</li>
<li><p>publish a result event back to Kafka</p>
</li>
</ul>
<p>So Inventory becomes the service that reacts to order events and decides whether stock can actually be reserved.</p>
<p>Let's now also create a Kafka bus helper for Inventory service.</p>
<h3>6.1 Create a Kafka bus helper for Inventory</h3>
<p>To keep the Kafka setup clean, we will create a small bus helper for the Inventory service.</p>
<p>Create: <code>services/inventory/internal/kafkabus/bus.go</code></p>
<pre><code class="language-go">package kafkabus

import (
	"strings"

	"github.com/segmentio/kafka-go"
)

type Bus struct {
    // This reads from: order.v1
	OrdersReader     *kafka.Reader
    // This writes to: inventory.v1
	InventoryWriter  *kafka.Writer
}

func New(brokers []string) *Bus {
	return &amp;Bus{
		OrdersReader: kafka.NewReader(kafka.ReaderConfig{
			Brokers:  brokers,
			Topic:    "orders.v1",
			GroupID:  "inventory-service",
			MinBytes: 1,
			MaxBytes: 10e6,
		}),
		InventoryWriter: &amp;kafka.Writer{
			Addr:                   kafka.TCP(brokers...),
			Topic:                  "inventory.v1",
			Balancer:               &amp;kafka.Hash{},
			AllowAutoTopicCreation: true,
		},
	}
}

func (b *Bus) Close() error {
	_ = b.OrdersReader.Close()
	return b.InventoryWriter.Close()
}

func BrokersFromEnv(s string) []string {
	parts := strings.Split(s, ",")
	out := make([]string, 0, len(parts))
	for _, p := range parts {
		p = strings.TrimSpace(p)
		if p != "" {
			out = append(out, p)
		}
	}
	return out
}
</code></pre>
<h4>What this bus means</h4>
<p>This Kafka bus gives Inventory two things:</p>
<ul>
<li><p>a <strong>reader</strong> for consuming order events</p>
</li>
<li><p>a <strong>writer</strong> for publishing inventory result events</p>
</li>
</ul>
<p>The <code>OrdersReader</code> reads from: <code>orders.v1</code> . That is where the Orders service publishes <code>OrderCreated</code> events. The <code>InventoryWriter</code> writes to: <code>inventory.v1</code>. That is where Inventory will publish the result after checking stock.</p>
<h3>6.2 Start the Inventory Kafka consumer loop</h3>
<p>Inventory will run a background goroutine that continuously reads from <code>orders.v1</code>.</p>
<p>For each <code>OrderCreated</code> event, it will:</p>
<ol>
<li><p>decode the event</p>
</li>
<li><p>try to reserve stock</p>
</li>
<li><p>publish an inventory result event to <code>inventory.v1</code></p>
</li>
</ol>
<p>Create: <code>services/inventory/internal/kafkabus/consume.go</code></p>
<pre><code class="language-go">package kafkabus

import (
	"context"
	"encoding/json"
	"log"

	"github.com/segmentio/kafka-go"
)

func ConsumeOrders(ctx context.Context, r *kafka.Reader, handle func(OrderCreated) error) {
	for {
		msg, err := r.FetchMessage(ctx)
		if err != nil {
			log.Printf("inventory consumer stopped: %v", err)
			return
		}

		var evt OrderCreated
		if err := json.Unmarshal(msg.Value, &amp;evt); err != nil {
			log.Printf("bad order event json: %v", err)
			_ = r.CommitMessages(ctx, msg) // poison =&gt; commit and move on
			continue
		}

		if err := handle(evt); err != nil {
			log.Printf("handle order event failed: %v", err)
			// no commit =&gt; retry later
			continue
		}

		_ = r.CommitMessages(ctx, msg)
	}
}
</code></pre>
<p>What this is doing? This is the core Kafka consumer loop for Inventory. It keeps reading messages from <code>orders.v1</code>. If the JSON is bad, It logs the error, commits the message, and moves on. And if the handling succeed, It commits the message, which means Inventory is done with it.</p>
<h3>6.3 Define the event structs and publish helper</h3>
<p>Now we define the event structures Inventory will use.</p>
<p>Create <code>services/inventory/internal/kafkabus/events.go</code></p>
<pre><code class="language-go">package kafkabus

import (
	"context"
	"encoding/json"
	"strconv"
	"time"

	"github.com/segmentio/kafka-go"
)

type OrderCreated struct {
	EventID   string    `json:"eventId"`
	Type      string    `json:"type"`
	Time      time.Time `json:"time"`
	OrderID   int64     `json:"orderId"`
	UserID    string    `json:"userId"`
	Sku       string    `json:"sku"`
	Quantity  int32     `json:"quantity"`
}

type InventoryResult struct {
	EventID  string    `json:"eventId"`
	Type     string    `json:"type"` // InventoryReserved | InventoryRejected
	Time     time.Time `json:"time"`
	OrderID  int64     `json:"orderId"`
	Reserved bool      `json:"reserved"`
	Reason   string    `json:"reason,omitempty"`
}

func PublishInventoryResult(ctx context.Context, w *kafka.Writer, evt InventoryResult) error {
	val, err := json.Marshal(evt)
	if err != nil {
		return err
	}
	return w.WriteMessages(ctx, kafka.Message{
		Key:   []byte(strconv.FormatInt(evt.OrderID, 10)),
		Value: val,
	})
}
</code></pre>
<p>These structs define the JSON payloads Inventory reads and writes.</p>
<h3><code>OrderCreated</code></h3>
<p>This is the event Inventory receives from Orders.</p>
<h3><code>InventoryResult</code></h3>
<p>This is the event Inventory publishes after checking stock.</p>
<p>Using <code>orderId</code> as the Kafka key is important because it helps keep all events for the same order in the same partition, which preserves ordering for that order.</p>
<h3>6.4 Update Inventory <code>main.go</code></h3>
<p>Now wire Kafka into the Inventory service.</p>
<p>First, add a helper method outside <code>main()</code>:</p>
<pre><code class="language-go">// outside of main
func (s *inventoryServer) tryReserve(sku string, qty int32) error {
	if sku == "" {
		return errors.New("sku is required")
	}
	if qty &lt;= 0 {
		return errors.New("quantity must be &gt; 0")
	}

	s.mu.Lock()
	defer s.mu.Unlock()

	available := s.stock[sku]
	if available &lt; qty {
		return errors.New("insufficient stock")
	}

	s.stock[sku] = available - qty
	return nil
}
</code></pre>
<p>This reuses the same stock-reservation logic, but now for Kafka events instead of only gRPC requests.</p>
<p>Then inside <code>main()</code>:</p>
<pre><code class="language-go">brokers := kafkabus.BrokersFromEnv(getenv("KAFKA_BROKERS", "localhost:19092"))
bus := kafkabus.New(brokers)
defer bus.Close()

srv := newInventoryServer()
inventoryv1.RegisterInventoryServiceServer(grpcServer, srv)

ctx := context.Background()

go kafkabus.ConsumeOrders(ctx, bus.OrdersReader, func(evt kafkabus.OrderCreated) error {
	err := srv.tryReserve(evt.Sku, evt.Quantity)

	out := kafkabus.InventoryResult{
		EventID: uuid.New().String(),
		Time:    time.Now().UTC(),
		OrderID: evt.OrderID,
	}

	if err != nil {
		out.Type = "InventoryRejected"
		out.Reason = err.Error()
		out.Reserved = false
	} else {
		out.Type = "InventoryReserved"
		out.Reserved = true
	}

	return kafkabus.PublishInventoryResult(ctx, bus.InventoryWriter, out)
})
</code></pre>
<p>This is where Inventory becomes part of the async workflow. First, it connects to Kafka It reads broker addresses from <code>KAFKA_BROKERS</code>, creates the Kafka bus, and keeps it open for the lifetime of the service.</p>
<h5>Then, it starts a background goroutine. That goroutine continuously consumes <code>OrderCreated</code> events from <code>orders.v1</code>.</h5>
<p>For each event, Inventory:</p>
<ul>
<li><p>checks stock using <code>tryReserve</code></p>
</li>
<li><p>creates an <code>InventoryResult</code></p>
</li>
<li><p>publishes that result to <code>inventory.v1</code></p>
</li>
</ul>
<p>If stock is available:</p>
<ul>
<li><p>type = <code>InventoryReserved</code></p>
</li>
<li><p>reserved = <code>true</code></p>
</li>
</ul>
<p>If stock is not available:</p>
<ul>
<li><p>type = <code>InventoryRejected</code></p>
</li>
<li><p>reserved = <code>false</code></p>
</li>
<li><p>reason = error message</p>
</li>
</ul>
<h2>Step 7: Orders consumes Inventory results and updates status</h2>
<p>Now we complete the async loop.</p>
<p>Inventory is already publishing result events into <code>inventory.v1</code>, but that alone does not change anything in the Orders database. Orders must also read those result events and update the stored order status.</p>
<p>So Orders will now start a background goroutine that continuously reads messages from <code>inventory.v1</code></p>
<p>These messages are the result events published by Inventory after it processes an order.</p>
<p>For each <code>InventoryResult</code>, Orders will update the order status in the database:</p>
<ul>
<li><p>if <code>reserved</code> is <code>true</code> → set status to <code>CONFIRMED</code></p>
</li>
<li><p>if <code>reserved</code> is <code>false</code> → set status to <code>CANCELLED</code></p>
</li>
</ul>
<p>At this point, the flow is split into two stages.</p>
<h3>Stage 1</h3>
<p>Orders accepts the request, stores the order as <code>PENDING</code>, and publishes an event.</p>
<h3>Stage 2</h3>
<p>Later, after Inventory processes the event, Orders receives the result and updates the final order status.</p>
<p>So the final state change now happens in the background, not during the original HTTP request.</p>
<p>This is the async update pattern in action.</p>
<p>Create <code>services/orders/internal/kafkabus/consume.go</code> to consume <code>inventory.v1</code> continuously.</p>
<pre><code class="language-go">package kafkabus

import (
	"context"
	"encoding/json"
	"log"

	"github.com/segmentio/kafka-go"
)

// ConsumeInventoryResults reads messages from inventory.v1 and passes them to handle().
// If handle returns error, we do not commit, so Kafka will retry.
func ConsumeInventoryResults(ctx context.Context, r *kafka.Reader, handle func(InventoryResult) error) {
	for {
		msg, err := r.FetchMessage(ctx)
		if err != nil {
			log.Printf("orders: inventory consumer stopped: %v", err)
			return
		}

		var evt InventoryResult
		if err := json.Unmarshal(msg.Value, &amp;evt); err != nil {
			log.Printf("orders: bad inventory event json: %v", err)
			_ = r.CommitMessages(ctx, msg) // poison =&gt; commit and move on
			continue
		}

		if err := handle(evt); err != nil {
			log.Printf("orders: handle inventory event failed: %v", err)
			continue // no commit =&gt; retry
		}

		_ = r.CommitMessages(ctx, msg)
	}
}
</code></pre>
<p>This loop keeps reading messages from <code>inventory.v1</code>. If the JSON is invalid It logs the error, commits the message, and moves on. That is because a poison message will not become valid just by retrying. If handling fails It does <strong>not</strong> commit the message. That means Kafka can deliver it again later.</p>
<p>If handling succeeds, It commits the message, which means Orders has processed it successfully. This gives us an at-least-once processing model.</p>
<h3>Start the background consumer in <code>main.go</code></h3>
<p>Now inside <code>services/orders/cmd/orders/main.go</code> 's main() function, after creating the HTTP server, start the consumer goroutine:</p>
<pre><code class="language-go">ctx := context.Background()

go kafkabus.ConsumeInventoryResults(ctx, bus.InventoryReader, func(evt kafkabus.InventoryResult) error {
  switch evt.Type {
  case "InventoryReserved":
    return orderStore.UpdateStatus(ctx, evt.OrderID, store.StatusConfirmed)
  case "InventoryRejected":
    return orderStore.UpdateStatus(ctx, evt.OrderID, store.StatusCancelled)
  default:
    return nil
  }
})
</code></pre>
<p>This starts a background worker inside Orders. Whenever Inventory publishes a result event, Orders reads it and applies the final state change. If the result is <code>InventoryReserved</code> Orders updates the order to <code>CONFIRMED</code>. If the result is <code>InventoryRejected</code> Orders updates the order to <code>CANCELLED</code>.</p>
<p>So this is the final step that closes the async workflow.</p>
<h2>Step 8: Update <code>docker-compose</code></h2>
<p>Now both services need to know how to reach Kafka.</p>
<p>Add <code>KAFKA_BROKERS</code> to the <code>orders</code> and <code>inventory</code> services in <code>docker-compose.yml</code>:</p>
<pre><code class="language-plaintext">  orders:
    depends_on:
        ...
     redpanda:
       condition: service_healthy
    environment:
      ...
      KAFKA_BROKERS: "redpanda:9092"

  inventory:
    environment:
      ...
      KAFKA_BROKERS: "redpanda:9092"
</code></pre>
<p>Both services now use Kafka.</p>
<hr />
<h2>How to test</h2>
<p>Now let’s test the new Kafka-based flow end to end.</p>
<p>Start fresh with:</p>
<pre><code class="language-plaintext">docker compose down -v
docker compose up --build
</code></pre>
<p>This removes old containers and volumes, then rebuilds and starts the full system again.</p>
<p><strong>Create an order:</strong></p>
<p>Send a request to the Orders API:</p>
<pre><code class="language-plaintext">curl -s -X POST localhost:8080/v1/orders \
  -H "Content-Type: application/json" \
  -d '{"userId":"u1","sku":"burger","quantity":2,"note":"kafka test"}'
</code></pre>
<p>If the request is accepted, you should get back an order ID.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/d18fe9c9-d65e-4d7a-aced-1de3cb4b0ab1.png" alt="" style="display:block;margin:0 auto" />

<p>Because the flow is now asynchronous, this does <strong>not</strong> mean the order is fully processed yet. It only means Orders accepted the request, stored the order, and published an event.</p>
<p>Now fetch the order:</p>
<pre><code class="language-plaintext">curl -s localhost:8080/v1/orders/1
</code></pre>
<p>At first, you should see the order in: <code>PENDING</code>. Then, after Inventory consumes the event and publishes the result, the order should become: <code>CONFIRMED</code> or <code>CANCELLED</code>.</p>
<p>To see the flow happening across both services, run:</p>
<pre><code class="language-plaintext">docker compose logs -f orders inventory
</code></pre>
<hr />
<h2>What we learned in Part 5</h2>
<p>In this part, we introduced Kafka and changed the order flow from synchronous processing to an event-driven design.</p>
<p>We learned:</p>
<ul>
<li><p>how Kafka topics and consumer groups work</p>
</li>
<li><p>how to publish events with a key</p>
</li>
<li><p>how to consume messages and commit offsets</p>
</li>
<li><p>why asynchronous processing leads to a <code>PENDING</code> state before the final result</p>
</li>
<li><p>how services become more independent and resilient when they communicate through events</p>
</li>
</ul>
<p>This part is important because it changes how the system behaves. Orders no longer has to wait directly for Inventory. Instead, the two services communicate through Kafka, which makes the system more decoupled and better suited for scaling and failure handling.</p>
<p>It also introduces an important idea that shows up in many real systems: work is often accepted first, then completed later. That is why a <code>PENDING</code> state becomes a normal part of the design.</p>
<hr />
<h2>Conclusion</h2>
<p>We now have our first event-driven order flow.</p>
<p>Orders no longer waits directly for Inventory. Instead, it accepts the request quickly and lets inventory processing happen asynchronously through Kafka. That makes the system feel much closer to how real distributed backends are designed.</p>
<p>At the same time, this version is not fully safe yet.</p>
<p>There is still an important failure case: if Kafka is down at the moment we create an order, the event can be lost. That can leave an order stuck in <code>PENDING</code> with no way to finish the workflow.</p>
<p>So in the next part, we will focus on reliability. We will move toward a Saga-style workflow and then introduce the <strong>Outbox pattern</strong>, which will make sure important events are not lost.</p>
]]></content:encoded></item><item><title><![CDATA[A Practical Journey from Application to Distributed Systems - Part 4]]></title><description><![CDATA[In Part 3, we created the Inventory service and defined its API using gRPC and a .proto file. Both services were running locally, but they were still separate pieces.
In this part, we connect them for]]></description><link>https://dhruvnakum.xyz/a-practical-journey-from-application-to-distributed-systems-part-4</link><guid isPermaLink="true">https://dhruvnakum.xyz/a-practical-journey-from-application-to-distributed-systems-part-4</guid><category><![CDATA[distributed system]]></category><category><![CDATA[kafka]]></category><category><![CDATA[Go Language]]></category><category><![CDATA[PostgreSQL]]></category><category><![CDATA[Cloud Computing]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Developer]]></category><category><![CDATA[Beginner Developers]]></category><category><![CDATA[Docker]]></category><dc:creator><![CDATA[Dhruv Nakum]]></dc:creator><pubDate>Wed, 06 May 2026 18:31:27 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/bfc4fa8b-cc9c-456f-adfd-4fe542fbd4b3.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In Part 3, we created the Inventory service and defined its API using gRPC and a <code>.proto</code> file. Both services were running locally, but they were still separate pieces.</p>
<p>In this part, we connect them for the first time.</p>
<p>The goal is simple: when a new order is created, the Orders service will call the Inventory service to reserve stock. If stock is available, the order can move forward. If not, the order will be cancelled.</p>
<p>This is an exciting step because QuickBite now starts behaving like a real multi-service backend, where one service depends on another to complete a request.</p>
<hr />
<h2>What we are building in this part</h2>
<p>We will keep the same public API for the app:</p>
<ul>
<li><p><code>POST /v1/orders</code></p>
</li>
<li><p><code>GET /v1/orders/{id}</code></p>
</li>
</ul>
<p>So from the client’s point of view, nothing changes.</p>
<p>But now <code>POST /v1/orders</code> will do more work behind the scenes:</p>
<ol>
<li><p>create an order in Postgres with status <code>PENDING</code></p>
</li>
<li><p>call the Inventory service over gRPC: <code>ReserveStock(sku, quantity)</code></p>
</li>
<li><p>if Inventory reserves the stock, update the order to <code>CONFIRMED</code></p>
</li>
<li><p>if Inventory rejects the request, update the order to <code>CANCELLED</code></p>
</li>
</ol>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/c681db83-6268-45c0-b70a-5b4c5d21e857.png" alt="" style="display:block;margin:0 auto" />

<p>This means the order status is no longer just stored data. It now reflects real business logic and the result of communication between two services.</p>
<hr />
<h2>Why we connect with gRPC first (before Kafka)</h2>
<p>Later in the project, we will move this flow to Kafka and make it asynchronous. But before jumping there, it helps to first build a simple synchronous version that works.</p>
<p>That way, we can understand the basic interaction clearly:</p>
<ul>
<li><p>Orders calls Inventory</p>
</li>
<li><p>Inventory responds</p>
</li>
<li><p>Orders updates its state based on the result</p>
</li>
</ul>
<p>gRPC works well for this because it gives us a strict shared contract, typed request and response objects, and built-in support for deadlines and timeouts.</p>
<p>This part is really about learning how two services communicate directly, and how to handle success and failure in that setup, before moving to a more advanced event-driven design.</p>
<hr />
<h2>Step 1: Add "UpdateStatus" to the Orders store</h2>
<p>The Orders service already knows how to create an order. Now it also needs a way to update the order status after the Inventory service responds.</p>
<p>Open:</p>
<p><code>services/orders/internal/store/orders.go</code></p>
<p>Add this method:</p>
<pre><code class="language-go">func (s *OrdersStore) UpdateStatus(ctx context.Context, id int64, status string) error {
	_, err := s.db.Exec(ctx, `UPDATE orders SET status = \(2 WHERE id = \)1`, id, status)
	return err
}
</code></pre>
<h3>What this method does</h3>
<p>This method updates the <code>status</code> column of an existing order.</p>
<p>It takes:</p>
<ul>
<li><p><code>ctx</code> - the request context</p>
</li>
<li><p><code>id</code> - the order ID</p>
</li>
<li><p><code>status</code> - the new status value, such as <code>CONFIRMED</code> or <code>CANCELLED</code></p>
</li>
</ul>
<p>Internally, it runs this SQL:</p>
<pre><code class="language-sql">UPDATE orders SET status = \(2 WHERE id = \)1
</code></pre>
<p>So if the order ID is <code>1</code> and the new status is <code>CONFIRMED</code>, it updates that row in the database.</p>
<hr />
<h2>Step 2: Create an Inventory gRPC client inside Orders</h2>
<p>Now the Orders service needs a way to talk to the Inventory service. To keep the rest of the code clean, we will create a small client wrapper.</p>
<p>First, create the folder:</p>
<pre><code class="language-plaintext">mkdir -p services/orders/internal/inventory
</code></pre>
<p>Now create the file:</p>
<p><code>services/orders/internal/inventory/client.go</code></p>
<pre><code class="language-go">package inventory

import (
	"context"
	"time"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"

	inventoryv1 "example.com/quickbite/proto/gen/go/inventory/v1"
)

// this struct keeps two things: conn, api
type Client struct {
	conn *grpc.ClientConn // the underlying gRPC connection
	api  inventoryv1.InventoryServiceClient // the generated gRPC client from the proto package
}

// this function creates a new Inventory client.
func New(addr string) (*Client, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()

    // it connects to the Inventory service using DialContext
    // we wrap it in timeout so that if Inventory is down, Order will not hand forever whole trying to connect.
	conn, err := grpc.DialContext(
		ctx,
		addr,
    
    // for local development, we use insecure transport cuz the services are communicating inside Docker Compose on an internal network. Layer we could switch to TLS for production.
	grpc.WithTransportCredentials(insecure.NewCredentials()),
		grpc.WithBlock(), // by default gRPC may return immediately and connect in the background so we use WithBlock so the call waits until the connection is actually ready.
	)
	if err != nil {
		return nil, err
	}

	return &amp;Client{
		conn: conn,
		api:  inventoryv1.NewInventoryServiceClient(conn),
	}, nil
}

// this closes the gRPC connection when the orders service shuts down.
func (c *Client) Close() error {
	return c.conn.Close()
}

// This method will be used by Order service.
func (c *Client) Reserve(ctx context.Context, sku string, qty int32) error {
    //creates the short request timeout
	ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
	defer cancel()
    
    // Then it calls the inventory gRPC method
	_, err := c.api.ReserveStock(ctx, &amp;inventoryv1.ReserveStockRequest{
		Sku:      sku,
		Quantity: qty,
	})
	return err
}
</code></pre>
<p>This file creates a small wrapper around the generated gRPC client.</p>
<p>Instead of letting the HTTP handler deal with low-level gRPC setup, we move that logic into a dedicated package. That way, the rest of the Orders service can use a simple method like:</p>
<pre><code class="language-go">client.Reserve(ctx, "burger", 2)
</code></pre>
<h3>What to understand here</h3>
<h4>1) We use <code>DialContext</code> with a timeout</h4>
<p>If the Inventory service is down, we do not want the Orders service to wait forever. The timeout makes failure happen quickly and clearly.</p>
<h4>2) We use insecure transport for local development</h4>
<p>Since the services are talking to each other inside Docker Compose, we keep it simple for now. Later, we can add TLS when we move to more production-like environments.</p>
<h4>3) We treat success as “no error”</h4>
<p>Notice that <code>Reserve(...)</code> does not return <code>true</code> or <code>false</code>.</p>
<p>Instead:</p>
<ul>
<li><p>if stock is reserved successfully - return <code>nil</code></p>
</li>
<li><p>if Inventory rejects the request - return an error</p>
</li>
</ul>
<p>This keeps the Orders-side logic simple. The handler only needs to check:</p>
<ul>
<li><p>no error - proceed</p>
</li>
<li><p>error - handle failure</p>
</li>
</ul>
<h2>Step 3: Update Orders main.go to create the Inventory client</h2>
<p>Now that we have a small gRPC client wrapper for Inventory, the Orders service needs to create that client when it starts.</p>
<p>Open:</p>
<p><code>services/orders/cmd/orders/main.go</code></p>
<p>Add this import:</p>
<pre><code class="language-go">inventory "example.com/quickbite/orders/internal/inventory"
</code></pre>
<p>Then create the client using an address from the environment:</p>
<pre><code class="language-go">invAddr := getenv("INVENTORY_GRPC_ADDR", "localhost:9090")

// ...db logic

// This creates gRPC client connection to the Inventory service.
invClient, err := inv.New(invAddr)
if err != nil {
	log.Fatalf("inventory client connect failed: %v", err)
}
defer invClient.Close()
</code></pre>
<p>This code creates the Inventory gRPC client when the Orders service starts. <code>getenv("INVENTORY_GRPC_ADDR", "localhost:9090")</code> reads the Inventory service address from an environment variable. If the variable is not set, it falls back to <code>localhost:9090</code>.</p>
<p>That makes local development easier, because the Orders service knows where to find Inventory by default. Later, in Docker Compose, this value will usually be something like:</p>
<pre><code class="language-plaintext">inventory:9090
</code></pre>
<p>because services talk to each other using service names inside the Docker network.</p>
<h3>Why do this in <code>main()</code>?</h3>
<p>We create the Inventory client in <code>main()</code> because startup is the place where we usually create long-lived dependencies such as db connections, external clients, configuration, HTTP servers.</p>
<p>Then we pass those dependencies into the parts of the app that need them.</p>
<p>That keeps the code organized and makes the application easier to test and maintain.</p>
<p>Now the Orders service has an Inventory client, but the HTTP handlers still do not know about it yet. So the next step is to update the HTTP server constructor and pass this client into it. That way, when someone creates an order, the handler can call Inventory through gRPC.</p>
<h2>Step 4: Update Orders HTTP server to use Inventory</h2>
<p>Now we connect the Orders HTTP layer to the Inventory gRPC client.</p>
<p>Open:</p>
<p><code>services/orders/internal/http/http.go</code></p>
<h3>4.1 Update the Server struct and constructor</h3>
<p>Add imports:</p>
<pre><code class="language-go">inv "example.com/quickbite/orders/internal/inventory"
</code></pre>
<p>Then update the <code>Server</code> struct so it holds both:</p>
<ul>
<li><p>the Orders store</p>
</li>
<li><p>the Inventory client</p>
</li>
</ul>
<pre><code class="language-go">type Server struct {
	store *store.OrdersStore
	inv   *inv.Client
}

func NewServer(store *store.OrdersStore, invClient *inv.Client) *Server {
	return &amp;Server{store: store, inv: invClient}
}
</code></pre>
<p>This means the HTTP server can now talk to both:</p>
<ul>
<li><p>Postgres, through the store</p>
</li>
<li><p>Inventory, through the gRPC client</p>
</li>
</ul>
<h3>4.2 Update <code>handleCreateOrder</code> to call Inventory</h3>
<p>Now we update the create-order handler so it does real business work.</p>
<p>The new flow is:</p>
<ol>
<li><p>create the order as <code>PENDING</code></p>
</li>
<li><p>call Inventory to reserve stock</p>
</li>
<li><p>update the order status based on the result</p>
</li>
</ol>
<p>Replace your create handler with this version:</p>
<pre><code class="language-go">func (s *Server) handleCreateOrder(w http.ResponseWriter, r *http.Request) {
	var req createOrderRequest
    // first we decode the JSON and check the fields.
	if err := json.NewDecoder(r.Body).Decode(&amp;req); err != nil {
		http.Error(w, "invalid json", http.StatusBadRequest)
		return
	}

	req.UserID = strings.TrimSpace(req.UserID)
	req.Sku = strings.TrimSpace(req.Sku)

	if req.UserID == "" {
		http.Error(w, "userId is required", http.StatusBadRequest)
		return
	}
	if req.Sku == "" {
		http.Error(w, "sku is required", http.StatusBadRequest)
		return
	}
	if req.Quantity &lt;= 0 {
		http.Error(w, "quantity must be &gt; 0", http.StatusBadRequest)
		return
	}

	ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
	defer cancel()

	// 1) Create order as PENDING
    // At this point, the order is stored in Postgres with a PENDING status.
	id, err := s.store.Create(ctx, req.UserID, req.Note, req.Sku, req.Quantity)
	if err != nil {
		http.Error(w, "db error", http.StatusInternalServerError)
		return
	}

	// 2) Ask Inventory to reserve stock
	err = s.inv.Reserve(ctx, req.Sku, int32(req.Quantity))
	if err != nil {
		// If reserve fails, mark order cancelled
		_ = s.store.UpdateStatus(ctx, id, store.StatusCancelled)

		// We handle "insufficient stock" differently from "inventory down"
		st, ok := status.FromError(err)
		if ok &amp;&amp; st.Code() == codes.FailedPrecondition {
			http.Error(w, "insufficient stock", http.StatusConflict) // 409
			return
		}

		http.Error(w, "inventory unavailable", http.StatusServiceUnavailable) // 503
		return
	}

	// 3) Success -&gt; CONFIRMED
	_ = s.store.UpdateStatus(ctx, id, store.StatusConfirmed)

	writeJSON(w, http.StatusCreated, createOrderResponse{ID: id})
}
</code></pre>
<p>You will need these extra imports in <code>http.go</code>:</p>
<pre><code class="language-go">"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
</code></pre>
<h3>Why we create the order first</h3>
<p>You might wonder why we insert the order before reserving stock. The reason is that we want to create a real record as early as possible. That gives us:</p>
<ul>
<li><p>a real order ID</p>
</li>
<li><p>a traceable record in the database</p>
</li>
<li><p>a history of what happened, even if Inventory fails</p>
</li>
</ul>
<p>So if stock reservation fails, we still know, the order was attempted and it was later cancelled.</p>
<p>This becomes even more useful later when we move to Kafka and event-driven processing, because keeping a record early fits naturally with async workflows.</p>
<h2>Step 5: Update Orders main.go server creation</h2>
<p>Back in:</p>
<p><code>services/orders/cmd/orders/main.go</code></p>
<p>you previously created the HTTP server like this:</p>
<pre><code class="language-go">srv := httpapi.NewServer(orderStore)
</code></pre>
<p>Now that the Orders HTTP server also needs access to the Inventory gRPC client, update it to:</p>
<pre><code class="language-go">srv := httpapi.NewServer(orderStore, invClient)
</code></pre>
<p>Earlier, the HTTP server only needed the Orders store because it was only talking to Postgres. Now the create-order handler also needs to call the Inventory service through gRPC.</p>
<p>That means the HTTP server needs access to two dependencies: <code>orderStore</code> and <code>invClient</code>.</p>
<p>So this line is simply passing both of those dependencies into the server when it is created.</p>
<h2>Step 6: Update Docker Compose (connect containers using service name)</h2>
<p>Now we need to make sure the Orders service can reach the Inventory service when both are running inside Docker Compose.</p>
<p>In the root <code>docker-compose.yml</code>, make sure the Inventory service exists, like this:</p>
<pre><code class="language-yaml">inventory:
    build:
      context: ./services/inventory
    ports:
      - "9090:9090"
</code></pre>
<p>Then update the <code>orders</code> service environment to include:</p>
<pre><code class="language-yaml">orders:
    build:
        ...
    environment:
        ...
        INVENTORY_GRPC_ADDR: "inventory:9090"
</code></pre>
<h3>Why <code>inventory:9090</code> works</h3>
<p>Inside Docker Compose, each service name becomes a hostname. So inventory resolves to the inventory container automatically. This is one of the most useful Compose features.</p>
<p>That means the service named <code>inventory:</code> can be reached by other containers using <code>inventory</code>. So when Orders connects to <code>inventory:9090</code> it is connecting to the inventory container on port <code>9090</code>,</p>
<h2>Step 7: Run the full system</h2>
<p>Now it is time to run the full system and see both services working together.</p>
<p>From the repo root, start fresh with:</p>
<pre><code class="language-shell">docker compose down -v
</code></pre>
<h3>ISSUES You might run into while running the container:</h3>
<ol>
<li><strong>Missing Go module dependencies</strong></li>
</ol>
<ul>
<li><p>The Orders service started importing:</p>
<ul>
<li><p><code>example.com/quickbite/proto/...</code></p>
</li>
<li><p><code>google.golang.org/grpc</code></p>
</li>
</ul>
<p>but those dependencies were not listed in <code>services/orders/go.mod</code>.</p>
</li>
<li><p>That meant the Orders module knew about the import paths in code, but its module file did not yet declare them properly.</p>
</li>
</ul>
<blockquote>
<p>To fix this: I added the missing dependencies:</p>
<ul>
<li><p><code>example.com/quickbite/proto</code></p>
</li>
<li><p><code>google.golang.org/grpc</code></p>
</li>
</ul>
<p>In <code>services/orders/go.mod</code> file</p>
<p>I also added</p>
<p><code>replace example.com/quickbite/proto =&gt; ../../proto</code></p>
<p>This follows the same local-development pattern used by Inventory. It tells Go to use the local <code>proto</code> folder instead of looking for that module somewhere remote.</p>
</blockquote>
<p>After that, I ran: <code>go mod tidy</code> so <code>go.mod</code> and <code>go.sum</code> were updated correctly.</p>
<ol>
<li><strong>Docker build could not see the shared Proto module</strong></li>
</ol>
<ul>
<li><p>The Orders Docker build was using:</p>
<pre><code class="language-plaintext">context: ./services/orders
</code></pre>
</li>
<li><p>That meant Docker only saw files inside the <code>services/orders</code> directory.</p>
</li>
<li><p>But the Orders service also depends on the shared <code>proto</code> module, which lives outside that folder. So during the build, Docker could not access the <code>proto</code> directory. That is why the build failed.</p>
</li>
</ul>
<blockquote>
<p>To fix that:</p>
<p>I updated the Orders Dockerfile to match the Inventory Dockerfile structure.</p>
</blockquote>
<p>The main changes were:</p>
<ul>
<li><p>use repo-root paths</p>
</li>
<li><p>set <code>WORKDIR</code> to <code>/src/services/orders</code></p>
</li>
<li><p>copy the shared Proto module into <code>/src/proto</code></p>
</li>
<li><p>build the binary with: <code>go build -d /orders</code></p>
</li>
</ul>
<p>Update the <code>/orders/Dockerfile</code> with this:</p>
<pre><code class="language-dockerfile"># Build stage: compiles the Go library.
# Start from the image that has the Go compiler. Name this stage as "builder".
FROM golang:1.26.1 AS builder
# Set the working directory in the container to /app.
WORKDIR /src/services/orders
# Copy go.mod and go.sum in that dir
COPY services/orders/go.mod services/orders/go.sum ./
COPY proto/go.mod proto/go.sum /src/proto/
# Downloads the dependencies.
RUN go mod download 
# Copy the remaining files
COPY proto /src/proto
COPY services/orders .
# Compile your go app into a single executable file.
RUN CGO_ENABLED=0 GOOS=linux go build -o /orders ./cmd/orders

# Run stage: runs image with the compiled binary.
# gcr.io/distroless/static:nonroot -&gt; minimal Linux image without any additional packages.
FROM gcr.io/distroless/static:nonroot 
# Set the working directory in the container to /.
WORKDIR / 
# Copt the built file from stage 1 (builder) and copy it into this final image. So now the final image contains basically /orders (your executable)
COPY --from=builder /orders /orders
# run the program as non-root user and not as admin/root for better security. If someone gains access to the container, they won't have root access.
USER nonroot:nonroot 
# And the a label saying 8080, this container will listen on port 8080. You still need -p 8080:8080 to map the container port to the host port.
EXPOSE 8080 
# When the container starts, runs "/orders"
ENTRYPOINT ["/orders"]
</code></pre>
<p>Also in <code>docker-compose.yml</code> change the Order build section to use the repo root as the build context:</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/6a5dbcc3-6fc9-495f-a54e-b426dd0ece4f.png" alt="" style="display:block;margin:0 auto" />

<p>This is important because now the Docker build can see both: <code>services/orders</code> and <code>proto</code>. Without that, the Dockerfile would still not be able to access the shared Proto module.</p>
<p>Now finally run:</p>
<pre><code class="language-dockerfile">docker compose up --build
</code></pre>
<p>Make sure everything is running:</p>
<pre><code class="language-shell">docker compose ps
</code></pre>
<p>You should see your main containers running, such as: <code>orders</code>, <code>inventory</code>, <code>db</code>, <code>redpanda</code>.</p>
<h2>Step 8: Test it end-to-end</h2>
<p>Now let’s test the full flow and make sure Orders and Inventory are actually working together.</p>
<h3>8.1 Successful order</h3>
<p>Create an order:</p>
<pre><code class="language-plaintext">curl -s -X POST localhost:8080/v1/orders \
  -H "Content-Type: application/json" \
  -d '{"userId":"u1","sku":"burger","quantity":2,"note":"grpc test"}'
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/b6bb9fbe-0e3e-40f2-84f4-9aefc3435118.png" alt="" style="display:block;margin:0 auto" />

<p>If everything works, you should get a response like:</p>
<pre><code class="language-go">{"id":1}
</code></pre>
<p>Now fetch that order:</p>
<pre><code class="language-plaintext">curl -s localhost:8080/v1/orders/1
</code></pre>
<p>You should see:</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/d137aa8e-4ba3-4633-ba9e-4719a72a7f64.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><code>status: CONFIRMED</code></li>
</ul>
<h3>8.2 Insufficient stock (should cancel)</h3>
<p>Now try creating an order with a quantity that is too large:</p>
<pre><code class="language-plaintext">curl -i -X POST localhost:8080/v1/orders \
  -H "Content-Type: application/json" \
  -d '{"userId":"u2","sku":"burger","quantity":99,"note":"too many"}'
</code></pre>
<p>This time, Inventory should reject the reservation because there is not enough stock. You should get: <code>409 Conflict</code> and message <code>insufficient stock</code>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/08850438-1263-457e-a2fd-ccd9686dd915.png" alt="" style="display:block;margin:0 auto" />

<h3>8.3 Inventory down (should return 503)</h3>
<p>Now let’s test what happens when one service is unavailable.</p>
<p>In another terminal, stop Inventory.</p>
<pre><code class="language-plaintext">docker compose stop inventory
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/ef8ff2f2-6749-4dcc-b258-c548b7c3591a.png" alt="" style="display:block;margin:0 auto" />

<p>Then try creating another order:</p>
<pre><code class="language-plaintext">curl -i -X POST localhost:8080/v1/orders \
  -H "Content-Type: application/json" \
  -d '{"userId":"u3","sku":"pizza","quantity":1,"note":"inventory down"}'
</code></pre>
<p>This time, Orders cannot reach Inventory at all. You should see <code>503 Service Unavailable</code>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/da37bb12-6ff0-4b2c-bc32-d5d5675a05be.png" alt="" style="display:block;margin:0 auto" />

<p>That means the Orders service handled the failure in a controlled way instead of hanging forever or crashing.</p>
<h2>Why this test matters</h2>
<p>This is an important moment because it introduces a core idea in distributed systems:</p>
<blockquote>
<p>services can fail, and your application must handle that cleanly</p>
</blockquote>
<p>In a single-service app, failures are already important. In a multi-service system, they become even more important because one service may be healthy while another is down or unreachable.</p>
<p>That is why this step matters so much. It teaches us how to think about:</p>
<ul>
<li><p>success</p>
</li>
<li><p>business rejection, such as insufficient stock</p>
</li>
<li><p>infrastructure failure, such as an unavailable service</p>
</li>
</ul>
<h2>What we learned in Part 4</h2>
<p>In this part, we connected two services and handled real business outcomes.</p>
<p>We learned how to:</p>
<ul>
<li><p>create a gRPC client in Go</p>
</li>
<li><p>call another service with timeouts</p>
</li>
<li><p>handle gRPC errors and map them to HTTP responses</p>
</li>
<li><p>use Docker Compose networking for service discovery</p>
</li>
<li><p>update order state based on service-to-service results</p>
</li>
</ul>
<p>This is the first truly distributed step in the project. Orders is no longer working as a standalone service. It now depends on Inventory to complete part of its workflow.</p>
<h2>Conclusion</h2>
<p>We now have a working flow where creating an order actually checks inventory and updates the order status. This is a big milestone because it introduces service-to-service communication and real failure cases.</p>
<p>But this approach still has one big limitation: it is <strong>synchronous</strong>. If Inventory is slow or down, Orders is directly affected. In real systems, this often becomes a bottleneck.</p>
<p>That is exactly why the next part matters.</p>
<p>In <strong>Part 5</strong>, we will introduce <strong>Kafka</strong> and move this flow to an <strong>event-driven</strong> approach. That allows Orders to accept requests quickly, and Inventory to process them asynchronously, with better reliability and scaling options.</p>
]]></content:encoded></item><item><title><![CDATA[A Practical Journey from Application to Distributed Systems - Part 3]]></title><description><![CDATA[In Part 2, we built the Orders service with proper folder structure, Postgres, migrations, and Docker Compose. That gave us a solid foundation.
In this part, we will add the second service: Inventory.]]></description><link>https://dhruvnakum.xyz/a-practical-journey-from-application-to-distributed-systems-part-3</link><guid isPermaLink="true">https://dhruvnakum.xyz/a-practical-journey-from-application-to-distributed-systems-part-3</guid><category><![CDATA[gRPC]]></category><category><![CDATA[Cloud Computing]]></category><category><![CDATA[Developer]]></category><category><![CDATA[distributed system]]></category><category><![CDATA[REST API]]></category><category><![CDATA[kafka]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Go Language]]></category><category><![CDATA[Kubernetes]]></category><category><![CDATA[event-driven-architecture]]></category><dc:creator><![CDATA[Dhruv Nakum]]></dc:creator><pubDate>Mon, 04 May 2026 17:39:24 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/bffed2f2-4d37-4fa6-b981-e7628badbab3.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In Part 2, we built the Orders service with proper folder structure, Postgres, migrations, and Docker Compose. That gave us a solid foundation.</p>
<p>In this part, we will add the second service: <strong>Inventory</strong>. We will also introduce <strong>gRPC</strong> and <strong>protobuf</strong> (proto files). This is the first time we connect services in a more structured way.</p>
<p>Even if you haven’t used gRPC before, don’t worry. I’ll explain the purpose and the moving parts in a simple way. This is going to be a really interesting one. Let's jump right into it</p>
<hr />
<h2>Why add Inventory as a separate service?</h2>
<p>In the real world, <strong>inventory</strong> is not just a function. It has its own data and its own rules. For example:</p>
<ul>
<li><p>Inventory needs to know how many items are left.</p>
</li>
<li><p>Inventory must stop two orders from reserving the same last item at the same time.</p>
</li>
<li><p>Inventory might be slow or unavailable, and Orders should not crash because of it.</p>
</li>
</ul>
<p>By separating Inventory into its own service, we learn service boundaries early. This is important because later we will add Kafka and make the workflow async. But before we go async, it helps to first understand a simple service-to-service call.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/f1efb991-6e52-4e2a-a75e-268ac966844f.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h2>What gRPC is and why we use it?</h2>
<p>Most apps start with REST because it is simple and easy to understand. REST usually sends JSON, which is human readable and works really well for client apps like mobile and web apps. In our project, we are still using REST for the client side.</p>
<p>But inside the backend, services also need to talk to each other. For example, the Orders service needs to ask the Inventory service to reserve stock. For this kind of service-to-service communication, we want something more strict and reliable than manually writing JSON requests and responses.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/6fafe5c4-2f11-45e9-b21a-e21e6c72af40.png" alt="" style="display:block;margin:0 auto" />

<p>That is where gRPC helps.</p>
<ol>
<li>With gRPC, we first define the API in a <code>.proto</code> file. This file describes what function a service provides, what input it expects, and what output it returns. Both services then generate code from that file.</li>
</ol>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/aadb9163-b6c1-429f-9a80-f9e004e0d8b2.png" alt="" style="display:block;margin:0 auto" />

<ol>
<li>Because both sides use code generated from the same <code>.proto</code> file, they both follow the exact same request and response structure. This reduces mistakes and keeps communication clear.</li>
</ol>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/885554ea-7431-4cd0-9562-78073655ffc1.png" alt="" style="display:block;margin:0 auto" />

<p>3. gRPC is fast and efficient, but the biggest reason we use it here is not speed. The biggest benefit is that both services follow one shared definition, so they always agree on how to talk to each other.</p>
<p>You can think of a <code>.proto</code> file as a shared agreement between services.</p>
<h2>What will we build in this part?</h2>
<p>In this part, We will add:</p>
<ol>
<li><p>A new service: <strong>inventory</strong></p>
</li>
<li><p>A shared <strong>proto</strong> module that contains the contract</p>
</li>
<li><p>A gRPC server in Inventory</p>
</li>
<li><p>(For now) Inventory will keep stock in memory so we can focus on gRPC</p>
</li>
<li><p>We will run Orders + Inventory with Docker Compose</p>
</li>
</ol>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/e57cc78f-a3e3-434d-a8cc-4c0ca36a6a5e.png" alt="" style="display:block;margin:0 auto" />

<p>In later parts, Inventory will get its own Postgres and become durable. But here we start simple.</p>
<h2>Step 1: Create the proto module</h2>
<p>I already explained the overall concept of protobuf file above but if you still want to know more about it, I would recommend watching this video to understand why we need .proto file and what its advantages are over JSON and XML: <a href="https://youtu.be/BywIOD_Y3CE?si=XBn4eqUGhQ9-rQwZ">What is Protocol Buffer</a></p>
<p>Now, once you are done watching it, from the repo root run:</p>
<pre><code class="language-shell">mkdir -p proto/inventory/v1
cd proto
go mod init quickbite/proto
cd ..
</code></pre>
<p>This creates a separate Go module for proto-generated code. It helps because both services can depend on it without copying files around.</p>
<h2>Step 2: Define the Inventory gRPC contract</h2>
<p>This file defines how the Inventory service and any other service will communicate with each other using gRPC.</p>
<p>This <code>.proto</code> file is acting like a shared agreement between services. It tells both sides:</p>
<ul>
<li><p>what service exists</p>
</li>
<li><p>what function it provides</p>
</li>
<li><p>what data should be sent</p>
</li>
<li><p>what data should be returned</p>
</li>
</ul>
<p>Instead of manually guessing request and response formats, both services follow the same definition.</p>
<p>Create:</p>
<p><code>proto/inventory/v1/inventory.proto</code></p>
<pre><code class="language-java">syntax = "proto3"; // This tells Protobuf which version of the language you are using.

// This is the namespace of this proto. It helps organize things and avoid naming conflicts. Here its referring like inventory.v1 belongs to inventory version 1.
package inventory.v1;

// This is specifically for Go code generation. When you generate Go code from this .proto file, this tells the generator where the Go package should live
option go_package = "quickbite/proto/gen/go/inventory/v1;inventoryv1";

// This defines a gRPC service. Inside it, you define one RPC method: ReserveStock meaning now the InventoryService has one function called ReserveStock.
service InventoryService {
  // This is the actual function definition. It says input type = ReserveStockRequest and output type = ReserveStockResponse.
  // In plain language: Client sends a ReserveStockRequest, and server sends back a ReserveStockResponse.
  rpc ReserveStock(ReserveStockRequest) returns (ReserveStockResponse);
}

// A message is like a data structure. It has two fields: sku and quantity
message ReserveStockRequest {
  string sku = 1; // These numbers are field numbers.
  int32 quantity = 2; // Protobuf uses these numbers internally when encoding/decoding data.
}

// This is the response structure.
message ReserveStockResponse {
  bool reserved = 1;
}
</code></pre>
<p>This file is basically saying:</p>
<ul>
<li><p>We have an <strong>Inventory</strong> service</p>
</li>
<li><p>It provides a function called <code>ReserveStock</code></p>
</li>
<li><p>That function takes a request with <code>sku</code> and <code>quantity</code></p>
</li>
<li><p>It returns a response telling us whether the reservation worked</p>
</li>
</ul>
<h2>Step 3: Generate Go code from the proto file (using Buf)</h2>
<p>Buf is a tool for working with <code>.proto</code> files. We use Buf because it keeps protobuf tooling consistent across machines. It also avoids “it works on my laptop” problems.</p>
<p>Instead of everyone using different local setups, Buf makes the code generation process predictable and consistent.</p>
<p>Create: <code>proto/buf.yaml</code></p>
<pre><code class="language-go">version: v1
</code></pre>
<ul>
<li>This tells Buf, this folder is a Buf module. You can think of it as a small config file that marks this folder as the place where our protobuf definitions live.</li>
</ul>
<p>Create: <code>proto/buf.gen.yaml</code></p>
<pre><code class="language-go">version: v1 
plugins:
    - plugin: buf.build/protocolbuffers/go 
    - out: gen/go 
    - opt: paths=source_relative

    - plugin: buf.build/grpc/go 
    - out: gen/go 
    - opt: paths=source_relative
</code></pre>
<ul>
<li><p>This file tells Buf <strong>how to generate code</strong>.</p>
</li>
<li><p>It uses two plugins:</p>
<ul>
<li><p><code>buf.build/protocolbuffers/go</code><br />This generates normal Go code for protobuf messages.</p>
</li>
<li><p><code>buf.build/grpc/go</code><br />This generates gRPC-specific Go code, such as the client and server interfaces.</p>
</li>
</ul>
</li>
<li><p>The generated files will be written inside:</p>
<pre><code class="language-shell">proto/gen/go
</code></pre>
<p>The option <code>paths=source_relative</code> means Buf will keep the generated folder structure similar to the original <code>.proto</code> file structure.</p>
</li>
</ul>
<p>Now run below command to generate the code:</p>
<pre><code class="language-go">docker run --rm -v "$(pwd)":/workspace -w /workspace/proto bufbuild/buf:1.34.0 generate
</code></pre>
<p>This command runs Buf inside Docker, so you do not need to install Buf locally on your machine. That makes the setup easier and keeps the generation process the same for everyone.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/bf499122-ffc7-4558-8aba-48941d169942.png" alt="" style="display:block;margin:0 auto" />

<h2>Step 4: Create a Go workspace (<a href="http://go.work"><code>go.work</code></a>)</h2>
<p>So <a href="http://go.work"><code>go.work</code></a> is a file that helps Go work with <strong>multiple local modules together</strong> during development.</p>
<p>You might ask why we need it?</p>
<ul>
<li>At this point in the project, we have separate modules like: <code>services/orders, proto</code></li>
</ul>
<p>Each one has its own <code>go.mod</code> file, so by default Go treats them as separate projects. But our Orders service wants to import generated code from the Proto module <strong>locally</strong>.</p>
<p>Without a <a href="http://go.work"><code>go.work</code></a> file, Go may think:</p>
<blockquote>
<p>“Where do I download this module from?”</p>
</blockquote>
<p>and it may expect that module to exist on GitHub or somewhere else online.</p>
<p>That is not what we want during local development. We want Go to understand that both modules already exist inside the same repo.</p>
<p>So let's create one. From the repo root run:</p>
<pre><code class="language-shell">go work init ./services/orders ./proto
</code></pre>
<p>It creates a <a href="http://go.work"><code>go.work</code></a> file at the repo root. What it basically tells Go is:</p>
<blockquote>
<p>“These local folders are Go modules. While I am developing, treat them as part of one workspace.”</p>
</blockquote>
<h2>Step 5: Make Orders depend on the proto module</h2>
<p>Now we need to tell the Orders module that it depends on the Proto module.</p>
<p>From repo root:</p>
<pre><code class="language-plaintext">cd services/orders
go mod edit -require=quickbite/proto@v0.0.0
go mod tidy
cd ../..
</code></pre>
<p>This adds the Proto module as a dependency of the Orders module.</p>
<p>In simple words, this tells Go:</p>
<blockquote>
<p>“The Orders service needs code from the Proto module.”</p>
</blockquote>
<p>That is important because later the Orders service will import the generated gRPC and protobuf code from the Proto module.</p>
<h3>Why <code>@v0.0.0</code>?</h3>
<p>We are still working locally, and this module is not published anywhere yet. So <code>v0.0.0</code> is just used as a placeholder version.</p>
<p>Sometimes when you run this step, Go may show an error about the module path being invalid. This happens because names like:</p>
<pre><code class="language-shell">quickbite/proto
quickbite/orders
quickbite/inventory
</code></pre>
<p>are not valid full Go module paths. Go module paths usually need a proper domain-style prefix, such as:</p>
<pre><code class="language-shell">example.com/quickbite/proto
example.com/quickbite/orders
example.com/quickbite/inventory
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/996c57f0-58b3-454d-97dd-65d3e2440aac.png" alt="" style="display:block;margin:0 auto" />

<p>To fix this issue, we need to update module paths to valid <code>example.com/quickbite/...</code> paths and regenerate protos.</p>
<p>Inside: <code>inventory.proto</code>, all <code>go.mod</code>, <code>main.go</code>, <code>http.go</code> wherever you see:</p>
<p><code>quickbite/proto</code>, <code>quickbite/orders</code>, <code>quickbite/inventory</code> update it with <code>example.com/quickbite/proto</code>, <code>example.com/quickbite/orders</code> and <code>example.com/quickbite/inventory</code></p>
<p>Also update any old Go imports to use the new <code>example.com/...</code> paths.</p>
<p>After changing the module paths, regenerate the protobuf Go code so the generated files use the updated import paths.</p>
<pre><code class="language-shell">cd proto
</code></pre>
<pre><code class="language-shell">docker run --rm -v "$(pwd)/..":/workspace -w /workspace/proto bufbuild/buf:1.34.0 generate
</code></pre>
<p>This regenerates the Go code from your <code>.proto</code> file with the new package paths.</p>
<p>In short, In this step, we made Orders depend on the Proto module so it can use the generated gRPC code. If Go complains about invalid module paths, we fix that by switching to proper <code>example.com/quickbite/...</code> module names and regenerating the protobuf code.</p>
<h2>Step 6: Create the Inventory service module</h2>
<p>Now we will create the Inventory service, just like we created the Orders service.</p>
<p>First, create the folder for the Inventory service:</p>
<pre><code class="language-shell">mkdir -p services/inventory/cmd/inventory
cd services/inventory
</code></pre>
<p>This gives us a standard Go service structure. The <code>cmd/inventory</code> folder will later contain the <code>main.go</code> file that starts the Inventory service.</p>
<p>Now initialize the Inventory module:</p>
<pre><code class="language-shell">go mod init example.com/quickbite/inventory
</code></pre>
<p>This creates a <code>go.mod</code> file for the Inventory service.</p>
<p>In simple words, this tells Go:</p>
<blockquote>
<p>“This folder is its own Go module.”</p>
</blockquote>
<p>Next, add the gRPC library:</p>
<pre><code class="language-shell">go get google.golang.org/grpc
</code></pre>
<p>This adds the Go gRPC runtime, which we need because the Inventory service will expose a gRPC server.</p>
<p>Now add protobuf support:</p>
<pre><code class="language-shell">go get google.golang.org/protobuf
</code></pre>
<p>This is needed because the Go code generated from the <code>.proto</code> file depends on the protobuf library.</p>
<p>Now tell the Inventory module that it depends on the shared Proto module:</p>
<pre><code class="language-shell">go mod edit -require=example.com/quickbite/proto@v0.0.0
</code></pre>
<p>Why we need it? Because the generated gRPC and protobuf code lives inside the Proto module. So the Inventory service needs to declare:</p>
<blockquote>
<p>“I depend on the Proto module.”</p>
</blockquote>
<p>That way, Inventory can import and use the generated code later.</p>
<p>Again, <code>v0.0.0</code> is just a placeholder version because we are working locally.</p>
<p>Finally run <code>go mod tidy</code> and add Inventory to the workspace</p>
<pre><code class="language-shell">go work use ./services/inventory
</code></pre>
<p>This updates <a href="http://go.work"><code>go.work</code></a> so Go treats Inventory as part of the same local workspace as: Order, Proto</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/92a0ea67-fb39-4ecf-943c-26414d8d134d.png" alt="" style="display:block;margin:0 auto" />

<p>To conclude, In this step, we created the Inventory service as a separate Go module, added the dependencies it needs for gRPC and protobuf, connected it to the shared Proto module, and added it to the local Go workspace.</p>
<h2>Step 7: Write the Inventory gRPC server</h2>
<p>Now we will create the Inventory service itself. This service will:</p>
<ul>
<li><p>listen on port <code>9090</code></p>
</li>
<li><p>expose the gRPC method <code>ReserveStock</code></p>
</li>
<li><p>keep stock in memory for now</p>
</li>
<li><p>safely update stock when requests come in</p>
</li>
</ul>
<p>Create: <code>services/inventory/cmd/inventory/main.go</code></p>
<pre><code class="language-go">package main

import (
	"context"
	"log"
	"net"
	"sync"

	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	inventoryv1 "quickbite/proto/gen/go/inventory/v1" // generated Go code in proto
)

// This is our Actual gRPC server
type inventoryServer struct {
	inventoryv1.UnimplementedInventoryServiceServer

	mu    sync.Mutex // lock for handling multiple requests at the same time
	stock map[string]int32 // inventory data. Its just an in memory Go map for now.
}

// creates the server with starting stock
func newInventoryServer() *inventoryServer {
	return &amp;inventoryServer{
		stock: map[string]int32{
			"burger": 5,
			"pizza":  10,
		},
	}
}

// The gRPC method
// This is the server implementation of the method defined in the proto:
// So whenever a client calls ReserveStock, this Go function runs.
func (s *inventoryServer) ReserveStock(ctx context.Context, req *inventoryv1.ReserveStockRequest) (*inventoryv1.ReserveStockResponse, error) {
    
    // First this checks the request is valid.
	if req.GetSku() == "" {
		return nil, status.Error(codes.InvalidArgument, "sku is required")
	}
	if req.GetQuantity() &lt;= 0 {
		return nil, status.Error(codes.InvalidArgument, "quantity must be &gt; 0")
	}

    // Then it locks the mutex, checks how much stock is available, and compares it to the requested quantity.
	s.mu.Lock()
	defer s.mu.Unlock()

    // check availability
	available := s.stock[req.Sku]
	if available &lt; req.Quantity {
		return nil, status.Error(codes.FailedPrecondition, "insufficient stock")
	}

    // deduct stock
	s.stock[req.Sku] = available - req.Quantity
	return &amp;inventoryv1.ReserveStockResponse{Reserved: true}, nil
}

// This main function starts the server
// It opens port 9090, creates a new gRPC server, registers our inventoryServer, and starts listening for requests.
// So once this program is running, the Inventory service is ready to accept gRPC calls on port 9090.
func main() {
	lis, err := net.Listen("tcp", ":9090")
	if err != nil {
		log.Fatalf("listen failed: %v", err)
	}

	grpcServer := grpc.NewServer()
	inventoryv1.RegisterInventoryServiceServer(grpcServer, newInventoryServer())

	log.Println("inventory gRPC listening on :9090")
	log.Fatal(grpcServer.Serve(lis))
}
</code></pre>
<p>This file creates a simple gRPC server for the Inventory service. It uses the Go code generated from our <code>.proto</code> file, which is why we import:</p>
<pre><code class="language-shell">inventoryv1 "example.com/quickbite/proto/gen/go/inventory/v1"
</code></pre>
<p>That generated package contains the request and response types, along with the gRPC server interface we need to implement.</p>
<h2>Step 8: Add Inventory Dockerfile</h2>
<p>Now we need a Dockerfile for the Inventory service so we can package it into a container image.</p>
<p>Create: <code>services/inventory/Dockerfile</code></p>
<pre><code class="language-dockerfile">FROM golang:1.26.1 AS builder // This starts from an image that already has Go installed.

// sets the working directory inside the container.
WORKDIR /src/services/inventory
// copies only the Inventory module dependency files first.
COPY services/inventory/go.mod services/inventory/go.sum ./
// copies the Proto module’s go.mod and go.sum into:
COPY proto/go.mod proto/go.sum /src/proto/
// downloads Go dependencies. At this point, Docker only has the module files, not all source code yet.
RUN go mod download
// Now copy the full Proto module source into the container.
COPY proto /src/proto
// copy the full Inventory service source code into the current working directory.
COPY services/inventory .
// This compiles the app.
RUN CGO_ENABLED=0 GOOS=linux go build -o /inventory ./cmd/inventory

// Now you start a completely new image.
FROM gcr.io/distroless/static:nonroot
// Set working directory to root.
WORKDIR /
// copies the compiled binary from the builder stage into the final runtime image.
COPY --from=builder /inventory /inventory
// Run the container as a non-root user.
USER nonroot:nonroot
EXPOSE 9090
// tells Docker what to run when the container starts.
ENTRYPOINT ["/inventory"]
</code></pre>
<p>This Dockerfile builds the Inventory service into a container image.</p>
<p>It uses a <strong>two-stage build</strong>:</p>
<ul>
<li><p><strong>builder stage</strong> - compiles the Go application</p>
</li>
<li><p><strong>runtime stage</strong> - runs only the compiled binary in a very small image</p>
</li>
</ul>
<p>This keeps the final image smaller, cleaner, and more secure.</p>
<h2>Step 9: Update the <code>docker-compose.yml</code></h2>
<p>Now we add the Inventory service to <code>docker-compose.yml</code> so Docker Compose can build and run it for us.</p>
<pre><code class="language-dockerfile">    inventory:
      build:
        context: .
        dockerfile: services/inventory/Dockerfile
      ports:
        - "9090:9090"
</code></pre>
<p>This tells Docker Compose:</p>
<ul>
<li><p>where to find the Dockerfile for Inventory</p>
</li>
<li><p>how to build the Inventory image</p>
</li>
<li><p>which port should be exposed</p>
</li>
</ul>
<h2>Step 10: Testing</h2>
<p>Now let’s test whether the Inventory service is working correctly.</p>
<p>First, start the project with:</p>
<pre><code class="language-shell">docker-compose up --build
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/e12eebe5-b22d-4056-9278-e5c2273fa36b.png" alt="" style="display:block;margin:0 auto" />

<p>If everything starts correctly, you should see a log message like this:</p>
<pre><code class="language-plaintext">inventory gRPC listening on :9090
</code></pre>
<p>That tells us the Inventory service is running and listening for gRPC requests on port <code>9090</code>.</p>
<p>And because it is gRPC, you can’t easily test it with curl.</p>
<h2>Why can we not easily use <code>curl</code> with gRPC</h2>
<p>Since this service uses <strong>gRPC</strong>, we cannot test it as easily as a normal REST API with <code>curl</code>.</p>
<p>REST usually works with: HTTP, JSON, and normal endpoints like <code>/orders</code>.</p>
<p>gRPC is different. It uses:</p>
<ul>
<li><p>protobuf messages</p>
</li>
<li><p>a stricter contract</p>
</li>
<li><p>a different communication format</p>
</li>
</ul>
<p>So for testing gRPC services, we usually use a tool like <strong>grpcurl</strong>.</p>
<p>On Mac, install it with: <code>brew install grpcurl</code></p>
<p>Then run the command below:</p>
<pre><code class="language-dockerfile">grpcurl \
  -plaintext \
  -import-path proto \
  -proto inventory/v1/inventory.proto \
  -d '{"sku":"burger","quantity":2}' \
  localhost:9090 \
  inventory.v1.InventoryService/ReserveStock
</code></pre>
<p>Output: You should see reserved</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/82a3d356-ef01-4934-b0a2-25f228d3b451.png" alt="" style="display:block;margin:0 auto" />

<h2>What we learned</h2>
<p>In this part, we added a second service and introduced the idea of strict service contracts.</p>
<p>We learned:</p>
<ul>
<li><p>why <code>.proto</code> files are useful for service-to-service APIs</p>
</li>
<li><p>how code generation works, from <code>.proto</code> files to Go code</p>
</li>
<li><p>how to run a gRPC server in Go</p>
</li>
<li><p>how to keep multiple services aligned by sharing a Proto module</p>
</li>
<li><p>how a <a href="http://go.work"><code>go.work</code></a> workspace makes local development easier</p>
</li>
</ul>
<p>At first, this can feel like a lot of moving parts. But once everything is connected, the benefit becomes clear. Instead of each service guessing request and response shapes, both services follow one shared contract. That makes communication safer, cleaner, and easier to maintain.</p>
<h2>Conclusion</h2>
<p>At this point, we now have two services running locally:</p>
<ul>
<li><p><strong>Orders</strong>, which uses REST</p>
</li>
<li><p><strong>Inventory</strong>, which uses gRPC</p>
</li>
</ul>
<p>We also have a shared contract that defines the Inventory API in a clean and strict way.</p>
<p>This is an important step because it introduces a real pattern used in many backend systems: public APIs are often kept simple and client-friendly with REST, while internal service-to-service communication uses stronger contracts with gRPC.</p>
<p>In the next part, we will connect Orders and Inventory together. Once they start talking to each other, we will quickly run into the kinds of problems that make distributed systems interesting: timeouts, failures, retries, and handling requests that are still in progress. That will naturally lead us to the next step in the project: <strong>Kafka</strong> and <strong>event-driven workflows</strong>.</p>
<p>See you in the next one...</p>
]]></content:encoded></item><item><title><![CDATA[A Practical Journey from Application to Distributed Systems - Part 2]]></title><description><![CDATA[In Part 1, I explained what QuickBite is and what we are trying to learn. In this part, we build the first real backend service: Orders.
This post follows the same structure as the final project we wi]]></description><link>https://dhruvnakum.xyz/a-practical-journey-from-application-to-distributed-systems-part-2</link><guid isPermaLink="true">https://dhruvnakum.xyz/a-practical-journey-from-application-to-distributed-systems-part-2</guid><category><![CDATA[Microservices]]></category><category><![CDATA[PostgreSQL]]></category><category><![CDATA[migration]]></category><category><![CDATA[Docker]]></category><category><![CDATA[Cloud Computing]]></category><category><![CDATA[Beginner Developers]]></category><category><![CDATA[Developer]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Go Language]]></category><dc:creator><![CDATA[Dhruv Nakum]]></dc:creator><pubDate>Fri, 01 May 2026 17:44:24 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/f7b13fd9-f285-4a73-9c9c-9fbabd47ce8d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In Part 1, I explained what QuickBite is and what we are trying to learn. In this part, we build the first real backend service: <strong>Orders</strong>.</p>
<p>This post follows the same structure as the final project we will build. That means we do it in a clean way from the beginning: proper folders, Postgres migrations, and Docker Compose.</p>
<p>Even if you are new to backend systems, this part will make sense because we will move slowly and explain why each file exists.</p>
<blockquote>
<p>WARNING: This is going to be a LONG read so make sure you have enough time to read.</p>
</blockquote>
<p><a class="embed-card" href="https://media.giphy.com/media/v1.Y2lkPWVjZjA1ZTQ3YjM5OWE5NmpwanpiYWNocnB5aDNsZzFlcTBlNTJwYTI3czQybWNxNSZlcD12MV9naWZzX3NlYXJjaCZjdD1n/XCfEnqL6CSekcYJY4m/giphy.gif">https://media.giphy.com/media/v1.Y2lkPWVjZjA1ZTQ3YjM5OWE5NmpwanpiYWNocnB5aDNsZzFlcTBlNTJwYTI3czQybWNxNSZlcD12MV9naWZzX3NlYXJjaCZjdD1n/XCfEnqL6CSekcYJY4m/giphy.gif</a></p>

<hr />
<h2>What we are building in this part</h2>
<p>We will build the <strong>Orders</strong> service with these endpoints:</p>
<ul>
<li><p><code>GET /healthz</code> -&gt; basic health check</p>
</li>
<li><p><code>POST /v1/orders</code> -&gt; create a new order</p>
</li>
<li><p><code>GET /v1/orders/{id}</code> -&gt; fetch an order</p>
</li>
</ul>
<p>We will store orders in a <strong>Postgres</strong> database, and we will run everything locally using <strong>Docker Compose</strong>.</p>
<hr />
<h2>Project structure</h2>
<p>Inside the repo, this is the structure we use:</p>
<pre><code class="language-plaintext">quickbite/ 
  services/ 
    orders/ 
      cmd/ 
        orders/ 
          main.go 
      internal/ 
        db/ 
          db.go 
        http/ 
          http.go 
        store/ 
          orders.go 
      migrations/ 
        000001_create_orders_table.up.sql                 
        000001_create_orders_table.down.sql 
     Dockerfile 
     go.mod 
  docker-compose.yml
</code></pre>
<h3>Why this structure matters</h3>
<p>This layout is common in real Go services:</p>
<ul>
<li><p><code>cmd/orders/main.go</code> is the entry point. It wires things together and starts the server.</p>
</li>
<li><p><code>internal/</code> contains code that should be used only by this service.</p>
</li>
<li><p><code>internal/http</code> contains routing + request/response handling.</p>
</li>
<li><p><code>internal/store</code> contains database queries.</p>
</li>
<li><p><code>internal/db</code> contains DB connection logic.</p>
</li>
<li><p><code>migrations/</code> contains versioned SQL changes (your DB schema history).</p>
</li>
</ul>
<p>This separation keeps the code easy to grow. When the project becomes bigger (Kafka, outbox, DLQ), you won’t end up with one giant <code>main.go</code>.</p>
<h2>Step 1: Create the Orders module</h2>
<p>From repo root:</p>
<pre><code class="language-plaintext">mkdir -p services/orders
cd services/orders
go mod init quickbite/orders
</code></pre>
<p>This creates <code>go.mod</code>. It tells Go the module name and tracks dependencies.</p>
<blockquote>
<p>Make sure you have Go installed on your system.</p>
</blockquote>
<h2>Step 2: Add database migrations</h2>
<blockquote>
<p>For someone who doesn't know what Migration is:</p>
<ul>
<li><p>A migration is a saved database update.</p>
</li>
<li><p>Instead of putting database setup code directly in your app, you create small files that describe each change, like creating a table or updating data.</p>
</li>
<li><p>The changes run one by one in the correct order, and the system keeps track of which ones have already been applied.</p>
</li>
</ul>
</blockquote>
<p>Create the migrations folder:</p>
<pre><code class="language-plaintext">mkdir -p migrations
</code></pre>
<p>Now create:</p>
<p><code>services/orders/migrations/000001_create_orders_table.up.sql</code></p>
<pre><code class="language-sql">CREATE TABLE IF NOT EXISTS orders ( 
    id BIGSERIAL PRIMARY KEY, -- gives each order an automatic unique number.
    user_id TEXT NOT NULL, 
    note TEXT NOT NULL DEFAULT '', 
    status TEXT NOT NULL DEFAULT 'PENDING', -- new orders start as "PENDING" unless changed.
    sku TEXT NOT NULL DEFAULT '', 
    quantity INT NOT NULL DEFAULT 1, 
    created_at TIMESTAMPTZ NOT NULL DEFAULT now() 
);

CREATE INDEX IF NOT EXISTS idx_orders_user_id ON orders(user_id); 
CREATE INDEX IF NOT EXISTS idx_orders_status ON orders(status); 
CREATE INDEX IF NOT EXISTS idx_orders_sku ON orders(sku);
</code></pre>
<p>This SQL creates an <code>orders</code> table to store order records, but only if it does not already exist.</p>
<p>The three <code>CREATE INDEX</code> lines make searches faster when looking up orders by:</p>
<ul>
<li><code>user_id, status, sku</code></li>
</ul>
<p>Second, create <code>down.sql</code> file:</p>
<p><code>services/orders/migrations/000001_create_orders_table.down.sql</code></p>
<pre><code class="language-sql">DROP TABLE IF EXISTS orders;
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/db707f54-8dea-4c12-89cc-00599d7d8230.png" alt="" style="display:block;margin:0 auto" />

<h3>Why are migrations better than “create table" in code?</h3>
<p>So, when you deploy software, code changes, and database changes must move together safely, right? And migrations make the database changes visible, repeatable, and versioned in Git. And so, if you change your schema later, you add a new migration file instead of quietly changing code.</p>
<h2>Step 3: Write the DB connection helper</h2>
<p>Now that we have our migration files in place, let's connect our application to the PostgreSQL database. And in order to do that, we need to create a helper function called <code>MustConnectWithRetry</code>. Which will try to connect it with proper error handling.</p>
<p>Let's create: <code>services/orders/internal/db/db.go</code></p>
<pre><code class="language-go">package db

import (
	"context"
	"log"
	"time"

	"github.com/jackc/pgx/v5/pgxpool"
)

// Keeps trying to connect to Postgres until it works, or until it gives up and crashes.
func MustConnectWithRetry(dsn string) *pgxpool.Pool {
	var lastErr error

	// it will try upto 30 times with the sleep of 0.5 second.
	for i := 0; i &lt; 30; i++ {
		ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
		db, err := pgxpool.New(ctx, dsn)
		cancel()

		if err == nil {
			ctx2, cancel2 := context.WithTimeout(context.Background(), 2*time.Second)
			defer cancel2()

			// if ping succeeds return the pool and you are connected.
			pingErr := db.Ping(ctx2)
			if pingErr == nil {
				return db
			}
			lastErr = pingErr
			db.Close()
		} else {
			lastErr = err
		}
		time.Sleep(500 * time.Millisecond)
	}
	log.Fatalf("db connect failed after retries: %v", lastErr)
	return nil
}
</code></pre>
<p><strong>You might ask, why are we writing the logic of retrying?</strong></p>
<p>When the service starts, PostgreSQL may not be ready yet, especially when running with Docker Compose. Instead of failing immediately, this helper function retries the connection several times before giving up.</p>
<p>It creates a Postgres connection pool, checks that the database is reachable with a ping, and returns the pool only when the connection is confirmed to be working. Each attempt is wrapped in a timeout so the application does not hang forever, and a short delay between retries gives the database time to finish starting.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/ec1d358f-289c-42c3-9993-7bf304f3a37b.png" alt="" style="display:block;margin:0 auto" />

<h2>Step 4: Write the database <code>store</code></h2>
<p>Now that the application can connect to PostgreSQL, the next step is to define how orders are stored and retrieved. This file is responsible for the data-access layer for orders. In other words, it contains the code that talks directly to the database.</p>
<p>Create: <code>services/orders/internal/store/orders.go</code></p>
<pre><code class="language-go">package store

import (
	"context"
	"errors"
	"time"

	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgxpool"
)

// These constants are for the status of the order
const (
	StatusPending   = "PENDING"
	StatusConfirmed = "CONFIRMED"
	StatusCancelled = "CANCELLED"
)

// This is how our Order model gonna look like
type Order struct {
	ID        int64
	UserID    string
	Note      string
	Status    string
	Sku       string
	Quantity  int
	CreatedAt time.Time
}

type OrdersStore struct {
	db *pgxpool.Pool
}

func NewOrdersStore(db *pgxpool.Pool) *OrdersStore {
	return &amp;OrdersStore{db: db}
}

// This method inserts a new row into the orders table and returns the generated order ID.
func (s *OrdersStore) Create(ctx context.Context, userID, note, sku string, quantity int) (int64, error) {
	var id int64
	err := s.db.QueryRow(ctx,
		`INSERT INTO orders (user_id, note, status, sku, quantity)
		 VALUES (\(1, \)2, \(3, \)4, $5)
		 RETURNING id`,
		userID, note, StatusPending, sku, quantity,
	).Scan(&amp;id)
	return id, err
}

// Getting the order with ID from DB when user request it.
func (s *OrdersStore) GetByID(ctx context.Context, id int64) (Order, error) {
	var o Order
	err := s.db.QueryRow(ctx,
		`SELECT id, user_id, note, status, sku, quantity, created_at
		 FROM orders WHERE id = $1`,
		id,
	).Scan(&amp;o.ID, &amp;o.UserID, &amp;o.Note, &amp;o.Status, &amp;o.Sku, &amp;o.Quantity, &amp;o.CreatedAt)

	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return Order{}, pgx.ErrNoRows
		}
		return Order{}, err
	}
	return o, nil
}
</code></pre>
<p>We start by defining an <code>Order</code> struct, which represents how an order looks in the application, along with constants for the possible order statuses. Using constants keeps the status values consistent across the codebase and avoids hardcoded strings scattered in multiple places.</p>
<p>The <code>OrdersStore</code> type wraps the Postgres connection pool and exposes methods for creating and retrieving orders. The <code>Create</code> method inserts a new row into the <code>orders</code> table and returns the generated ID, while <code>GetByID</code> fetches an order and maps the selected columns into an <code>Order</code> struct.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/e121c228-6cf7-4289-9e0b-8b0bcb68a762.png" alt="" style="display:block;margin:0 auto" />

<h2>Step 5: Write the HTTP layer <code>routes</code> &amp; <code>validation</code></h2>
<p>Now that the database layer is ready, the next step is to expose that functionality through HTTP endpoints. This server layer connects incoming API requests to the order store.</p>
<p>In simple terms, this is the part of the application that receives requests like “create an order” or “fetch order <code>1</code>” and turns them into database operations.</p>
<p>Create: <code>services/orders/internal/http/http.go</code></p>
<pre><code class="language-go">package http

import (
	"context"
	"encoding/json"
	"errors"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/go-chi/chi/v5"
	"github.com/jackc/pgx/v5"

	"quitebite/orders/internal/store"
)

// We need to create a server and this server has the Orderstore. This will give HTTP handlers an access to the store to call its methods 
type Server struct {
	store *store.OrdersStore
}

func NewServer(store *store.OrdersStore) *Server {
	return &amp;Server{store: store}
}

// Lets create Routes now and returns the HTTP router for this service.
func (s *Server) Routes() http.Handler {
	r := chi.NewRouter()

	// Health check endpoint.This used to verify the service is running.
	r.Get("/healthz", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte("ok"))
	})

	// Create a new order.
	r.Post("/v1/orders", s.handleCreateOrder)

	// Get an existing order by id.
	r.Get("/v1/orders/{id}", s.handleGetOrder)

	return r
}

// Here we are creating a `createOrderRequest`This tells the client what data it needs when sending a create order request
type createOrderRequest struct { 
    UserID string `json:"userId"`
    Sku string `json:"sku"`
    Quantity int `json:"quantity"`
    Note string `json:"note"`
}

// Similarly `createOrderResponse`. This tells client what response to expect in return
type createOrderResponse struct {
	ID int64 `json:"id"`
}

// handleCreateOrder handles POST /v1/orders
func (s *Server) handleCreateOrder(w http.ResponseWriter, r *http.Request) {
	var req createOrderRequest

	// Decode request JSON into req.
	if err := json.NewDecoder(r.Body).Decode(&amp;req); err != nil {
		http.Error(w, "invalid json", http.StatusBadRequest)
		return
	}

	// Remove leading/trailing spaces from important string fields.
	req.UserID = strings.TrimSpace(req.UserID)
	req.Sku = strings.TrimSpace(req.Sku)

	// Validate required fields.
	if req.UserID == "" {
		http.Error(w, "userId is required", http.StatusBadRequest)
		return
	}
	if req.Sku == "" {
		http.Error(w, "sku is required", http.StatusBadRequest)
		return
	}
	if req.Quantity &lt;= 0 {
		http.Error(w, "quantity must be &gt; 0", http.StatusBadRequest)
		return
	}

	// Create a request-scoped context with timeout
	// so DB work does not hang forever.
	ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second)
	defer cancel()

	// Insert a new pending order into the database.
	id, err := s.store.CreatePending(ctx, req.UserID, req.Note, req.Sku, req.Quantity)
	if err != nil {
		http.Error(w, "db error", http.StatusInternalServerError)
		return
	}

	// Return the created order id.
	writeJSON(w, http.StatusAccepted, createOrderResponse{ID: id})
}

// getOrderResponse is the JSON shape returned to the client 
// when fetching an order. 
type getOrderResponse struct { 
    ID int64 `json:"id"`
    UserID string `json:"userId"` 
    Note string `json:"note"` 
    Status string `json:"status"` 
    Sku string `json:"sku"` 
    Quantity int `json:"quantity"`
    CreatedAt string `json:"createdAt"`
}

// handleGetOrder handles GET /v1/orders/{id}
func (s *Server) handleGetOrder(w http.ResponseWriter, r *http.Request) {
	// Read the "id" path parameter from the URL.
	idStr := chi.URLParam(r, "id")

	// Convert id from string to int64.
	id, err := strconv.ParseInt(idStr, 10, 64)
	if err != nil || id &lt;= 0 {
		http.Error(w, "invalid id", http.StatusBadRequest)
		return
	}

	// Create a timeout for the DB lookup.
	ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second)
	defer cancel()

	// Fetch the order from the database.
	o, err := s.store.GetByID(ctx, id)
	if err != nil {
		// If no row exists, return 404.
		if errors.Is(err, pgx.ErrNoRows) {
			http.Error(w, "not found", http.StatusNotFound)
			return
		}

		// Any other DB issue becomes 500.
		http.Error(w, "db error", http.StatusInternalServerError)
		return
	}

	// Convert the store model into the API response shape.
	resp := getOrderResponse{
		ID:        o.ID,
		UserID:    o.UserID,
		Note:      o.Note,
		Status:    o.Status,
		Sku:       o.Sku,
		Quantity:  o.Quantity,
		CreatedAt: o.CreatedAt.UTC().Format(time.RFC3339), // standard JSON-friendly timestamp
	}

	// Return the order as JSON.
	writeJSON(w, http.StatusOK, resp)
}

// writeJSON is a helper function to send JSON responses.
func writeJSON(w http.ResponseWriter, status int, v any) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)

	// Encode the value as JSON and write it to the response body.
	_ = json.NewEncoder(w).Encode(v)
}
</code></pre>
<p>The <code>Routes</code> method creates a <strong>Chi</strong> router and registers three endpoints: a <strong>health check</strong>, a route for <strong>creating orders</strong>, and a route for <strong>fetching an order</strong> by ID. This is the entry point that turns the application into a web service.</p>
<p>For the create-order endpoint, we define a request struct that matches the JSON sent by the client. The handler decodes the JSON body, trims unnecessary whitespace, validates the required fields, and then uses a request-scoped timeout before calling the store layer. If the insert succeeds, it returns the new order ID as JSON.</p>
<p>For the fetch-order endpoint, the handler reads the <code>id</code> from the URL path, validates it, queries the store, and maps the database model into a response struct. If the order does not exist, it returns <code>404 Not Found</code>; otherwise, it returns the order as JSON with a properly formatted timestamp.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/c5374cee-e0c5-4802-b9b6-d6ef5a01961b.png" alt="" style="display:block;margin:0 auto" />

<h2>Step 6: Write the entrypoint</h2>
<p>At this point, we have all the building blocks: <strong>database connection logic,</strong> the <strong>order store</strong>, and the <strong>HTTP server</strong>. The <code>main</code> function is where everything is assembled and the application actually starts.</p>
<p>In simple terms, this is the entry point of the service. Let's create: <code>services/orders/cmd/orders/main.go</code></p>
<pre><code class="language-go">package main

import (
	"log"
	"net/http"
	"os"
	"strings"

	"quitebite/orders/internal/db"

	httpapi "quitebite/orders/internal/http"
	"quitebite/orders/internal/store"
)

func main() {
    // getting the port and database url from the `env`
	port := getenv("PORT", "8080")
	dsn := getenv("DATABASE_URL", "postgres://postgres:postgres@localhost:5432/orders?sslmode=disable")
    
    // checking the database connection
	pool := db.MustConnectWithRetry(dsn)
	defer pool.Close()

    // creating an order store
	orderStore := store.NewOrdersStore(pool)
    // creating http server
	srv := httpapi.NewServer(orderStore)

	addr := ":" + port
	log.Printf("orders service listening on %s", addr)
    // starting the server and listening to the port
	log.Fatal(http.ListenAndServe(addr, srv.Routes()))
}

// helper function to get the env values
func getenv(key, def string) string {
	v := strings.TrimSpace(os.Getenv(key))
	if v == "" {
		return def
	}
	return v
}
</code></pre>
<p>It begins by reading configuration from environment variables. The service uses <code>PORT</code> to decide which port to listen on and <code>DATABASE_URL</code> to connect to PostgreSQL. Default values are provided so the application can run locally without additional setup.</p>
<p>Next, the code connects to the database using <code>MustConnectWithRetry</code>, which keeps startup reliable in environments where Postgres may take a moment to become available. Once the connection pool is ready, it is passed into <code>NewOrdersStore</code>, which creates the store layer responsible for database operations.</p>
<p>The store is then injected into <code>NewServer</code>, which creates the HTTP API server. Finally, the application logs the address it is listening on and calls <code>http.ListenAndServe</code> to begin accepting requests.</p>
<p><strong>This is how everything works together:</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/09c7730e-226c-44b4-ba2c-56b08cb6fb76.png" alt="" style="display:block;margin:0 auto" />

<h3>Step 7: Add dependencies and build locally</h3>
<p>Now that we have everything in place. Let's add all the required dependencies.</p>
<p>First, we will add <code>chi</code> router package and <code>pgxpool</code> which is the <strong>connection pool package</strong> from PostgreSQL. So instead of opening one fresh DB connection for every query, your app asks the pool for a connection, uses it, and returns it for reuse.</p>
<p>From <code>services/orders</code>: run</p>
<pre><code class="language-go">go get github.com/go-chi/chi/v5 
go get github.com/jackc/pgx/v5/pgxpool 
go mod tidy
</code></pre>
<p>This will install the dependencies</p>
<h3>Step 8: Dockerfile</h3>
<p>Now that the application is working locally, the next step is to package it into a container image. This Dockerfile uses a <strong>multi-stage build</strong>, which means we build the Go binary in one stage and run it in a much smaller image in the final stage.</p>
<p>This approach keeps the final image lightweight, secure, and production-friendly.</p>
<pre><code class="language-python"># Build stage: compiles the Go library.
# Start from the image that has the Go compiler. Name this stage as "builder".
FROM golang:1.23 AS builder 
# Set the working directory in the container to /app.
WORKDIR /app 
# Copy go.mod and go.sum in that dir
COPY go.mod go.sum ./ 
# Downloads the dependencies.
RUN go mod download 
# Copy the remaining files
COPY . . 
# Compile your go app into a single executable file.
RUN CGO_ENABLED=0 GOOS=linux go build -o orders ./cmd/orders

# Run stage: runs image with the compiled binary.
# gcr.io/distroless/static:nonroot -&gt; minimal Linux image without any additional packages.
FROM gcr.io/distroless/static:nonroot 
# Set the working directory in the container to /.
WORKDIR / 
# Copt the built file from stage 1 (builder) and copy it into this final image. So now the final image contains basically /orders (your executable)
COPY --from=builder /app/orders /orders 
# run the program as non-root user and not as admin/root for better security. If someone gains access to the container, they won't have root access.
USER nonroot:nonroot 
# And the a label saying 8080, this container will listen on port 8080. You still need -p 8080:8080 to map the container port to the host port.
EXPOSE 8080 
# When the container starts, runs "/orders"
ENTRYPOINT ["/orders"]
</code></pre>
<p>You might ask why we are using multi-stage build. The reason behind it is that it separates <strong>building</strong> from <strong>running</strong>. If you used only a single image, your final container would also contain: Go toolchain, module cache, source code, etc. That would make the image larger and increase the attack surface.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/e5f27167-2529-4b06-9c3c-779bc02956b4.png" alt="" style="display:block;margin:0 auto" />

<p>With a multi-stage build, the final image contains only what is needed to run the service.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/f66002b1-8b21-416b-9156-a80edfd0fdb9.png" alt="" style="display:block;margin:0 auto" />

<h2>Step 9: Docker Compose</h2>
<p>At this point, we have a database container, a migration step, and the Go API service. Docker Compose lets us define all three in one file and run them together as a small local system.</p>
<p>This is useful because the application does not depend on just one container. It depends on Postgres being available, the schema being created, and only then the API starting up.</p>
<pre><code class="language-dockerfile">services:
    # service name: db
    db:
      image: postgres:16
      # these env variables are read by the postgres image on first startup to initialize the database.
      environment:
        POSTGRES_USER: postgres
        POSTGRES_PASSWORD: postgres
        POSTGRES_DB: orders
      ports: 
        - "5432:5432" # left side is your machine's port, right side is container port.
      # this is where postgres will store its data.
      volumes:
        - dbdata:/var/lib/postgresql/data
      # healthcheck is Compose asking: Are you ready?
      healthcheck:
        # pg_isready checks if the DB is ready to accept connections.
        test: ["CMD-SHELL", "pg_isready -U postgres -d orders"]
        interval: 2s # check every 2 seconds.
        timeout: 3s # each check can take up to 3 seconds.
        retries: 30 # try 30 times before giving up.
    
    # this is a one time job container that runs the migrations.
    migrate:
      image: migrate/migrate:v4.17.1 # popular migration CLI
      # mounts your local migrations folder to the container.
      volumes:
        - ./services/orders/migrations:/migrations
      command: ["-path", "/migrations", "-database", "postgres://postgres:postgres@db:5432/orders?sslmode=disable", "up"]
      depends_on:
        db:
          condition: service_healthy # this makes sure the db is ready before running the migrations.
  
    orders:
      # compose will run docker build using Dockerfile in ./services/orders directory. It creates an image for your Go service.
      build:
        context: ./services/orders
      environment:
        PORT: "8080"
        DATABASE_URL: "postgres://postgres:postgres@db:5432/orders?sslmode=disable"
      ports:
        - "8080:8080"
      #  compose will start db container before orders container starts.
      depends_on:
        migrate:
          # this makes sure the migrations are completed successfully before starting the orders container.
          condition: service_completed_successfully

# define a volume called dbdata. without this, data would be lost when the container is stopped.
volumes: 
  dbdata:
</code></pre>
<p><strong>3 Services here:</strong></p>
<p><strong>1)</strong> The <code>db</code> service</p>
<ul>
<li><p>Starts a <strong>PostgreSQL</strong> database for the orders system. It also sets the default database name, username, and password, and exposes port <code>5432</code> so the database can be reached from outside the container if needed.</p>
</li>
<li><p>A volume named <code>dbdata</code> is attached so the data is saved even if the container is removed and started again. The health check is especially important here because it keeps checking whether PostgreSQL is actually ready to accept connections, rather than just assuming the container is usable as soon as it starts.</p>
</li>
</ul>
<p><strong>2)</strong> The <code>migrate</code> service</p>
<ul>
<li><p>It is responsible for applying the database migrations. In other words, it prepares the database schema before the application begins using it.</p>
</li>
<li><p>It mounts the local migrations folder into the container and runs the migration tool with the database connection string. This means the database tables and structure are created from the migration files automatically, instead of relying on manual setup. It also depends on the database being healthy first, which prevents the migration tool from running too early.</p>
</li>
</ul>
<p><strong>3)</strong> The <code>orders</code> service</p>
<ul>
<li>It is the actual Go application. It is built from the code inside <code>./services/orders</code>, gets its configuration through environment variables, and exposes port <code>8080</code> so the API can be reached from the browser, Postman, or another service. It depends on the <code>migrate</code> service completing successfully, which ensures the app only starts after the database is fully prepared.</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/0900cfac-0f76-48be-a2d3-c8b9277c9923.png" alt="" style="display:block;margin:0 auto" />

<h3>Step 10: Run and test</h3>
<p>Now it's time to put everything into test and check to make sure it works as expected. After setting up the database, migrations, and API service, the next job is to start the system and verify that each part is responding correctly.</p>
<p>From the root of your app run below commands:</p>
<pre><code class="language-shell">docker compose down -v 
docker compose up --build
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/a45118d9-d6f2-42bf-9b48-f4303a812124.png" alt="" style="display:block;margin:0 auto" />

<p>The first command stops any existing containers and removes their volumes. This helps you start from a clean state, which is useful during testing because it avoids leftover data from earlier runs.</p>
<p>The second command rebuilds the images and starts the services defined in <code>docker-compose.yml</code>. Rebuilding ensures your latest code and configuration changes are included.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/dbedb205-2899-446a-ba90-a8cf28dffb9d.png" alt="" style="display:block;margin:0 auto" />

<p>Once the containers are running, test the health endpoint:</p>
<pre><code class="language-shell">curl -s localhost:8080/healthz
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/6539128c-7534-407b-adbc-8a6d03d26e2b.png" alt="" style="display:block;margin:0 auto" />

<p>This sends a request to the service’s health check endpoint. This endpoint is a quick way to verify that the application is up and responding before you test any business functionality. If this request fails, there is no point in testing the rest of the API yet.</p>
<p>Next, let's create an order and check that endpoint:</p>
<pre><code class="language-shell">curl -s -X POST localhost:8080/v1/orders -H "Content-Type: application/json" -d '{"userId":"u1","sku":"burger","quantity":2,"note":"part 2 test"}'
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/a80c7ec7-cc4d-43cb-a8ef-3f26eb350722.png" alt="" style="display:block;margin:0 auto" />

<p>This makes a <code>POST</code> request to the <code>/v1/orders</code> endpoint with a JSON payload containing:</p>
<ul>
<li><p><code>userId</code>: the user placing the order</p>
</li>
<li><p><code>sku</code>: the item being ordered</p>
</li>
<li><p><code>quantity</code>: how many units to create</p>
</li>
<li><p><code>note</code>: an optional note attached to the order</p>
</li>
</ul>
<p>If the create request returns something like <code>{"id":1}</code>, You can fetch that order with:</p>
<pre><code class="language-plaintext">curl -s localhost:8080/v1/orders/1
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/6f1cef73-6b1e-4b64-b977-34adfeb70e3f.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3>What we learned</h3>
<p>In this part, we built the <strong>Orders service</strong>. We did not keep everything in one file, and we did not rely on a magic setup. Instead, we created a clean structure (<code>cmd/</code> and <code>internal/</code>), wrote the database queries in one place, and kept the HTTP logic separate from the database logic. This makes the code easier to read today and easier to grow later.</p>
<p>We also connected the service to <strong>PostgreSQL</strong> and introduced <strong>migrations</strong>, which is a big milestone. Migrations solve a common real-world problem: your database schema changes over time, and you need a safe and repeatable way to apply those changes across different machines and environments. By running migrations automatically with <strong>Docker Compose</strong>, we made our local setup much closer to what a production pipeline looks like.</p>
<p>At this point, we have a working base: we can create orders, store them in Postgres, and fetch them using an API. This is the foundation we will build on in the next parts. In the next post, we’ll introduce the next service (<strong>Inventory</strong>) and start connecting services together, which is where the system begins to feel like a real distributed application.</p>
<p>See you in the next one...</p>
]]></content:encoded></item><item><title><![CDATA[A Practical Journey from Application to Distributed Systems - Part 1]]></title><description><![CDATA[When I started this project, my goal was simple. I wanted to learn modern backend and distributed system tools in a way that feels real. I didn’t want to just watch tutorials or memorize commands. I w]]></description><link>https://dhruvnakum.xyz/a-practical-journey-from-application-to-distributed-systems-part-1</link><guid isPermaLink="true">https://dhruvnakum.xyz/a-practical-journey-from-application-to-distributed-systems-part-1</guid><category><![CDATA[distributed system]]></category><category><![CDATA[kafka]]></category><category><![CDATA[Docker]]></category><category><![CDATA[Go Language]]></category><category><![CDATA[gRPC]]></category><category><![CDATA[PostgreSQL]]></category><category><![CDATA[Kubernetes]]></category><category><![CDATA[Developer]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Beginner Developers]]></category><dc:creator><![CDATA[Dhruv Nakum]]></dc:creator><pubDate>Tue, 28 Apr 2026 18:01:47 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/53193540-4634-47b1-8160-93e8439748a5.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When I started this project, my goal was simple. I wanted to learn modern backend and distributed system tools in a way that feels real. I didn’t want to just watch tutorials or memorize commands. I wanted to build something and understand why each tool exists and how it works.</p>
<p>So I built this app, a food ordering system. And don't think it's JUST a food ordering system. It has a lot of concepts, tools, etc., working internally, which we will learn soon. There is gonna be a mobile app, and the backend is in <strong>Go</strong>. Over time, I added tools like <strong>Docker</strong>, <strong>PostgreSQL</strong>, <strong>gRPC,</strong> <strong>Kafka (Redpanda)</strong>, and <strong>Kubernetes (kind)</strong>. I also added important reliability patterns like <strong>Outbox</strong>, <strong>Idempotency</strong>, and <strong>Dead Letter Queues (DLQ)</strong>. These are the same ideas that show up in real production systems.</p>
<p>In this series, I’ll share what I built and what I learned, step by step, in the same order I learned it. Each post will explain the “why” first, then the “how”.</p>
<p><a class="embed-card" href="https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExdHlqNjV2Ym50ZnUzb3NhcW1rdHlydHRiMXV4bnN2cnJzZTBmemVvbyZlcD12MV9naWZzX3NlYXJjaCZjdD1n/5zf2M4HgjjWszLd4a5/giphy.gif">https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExdHlqNjV2Ym50ZnUzb3NhcW1rdHlydHRiMXV4bnN2cnJzZTBmemVvbyZlcD12MV9naWZzX3NlYXJjaCZjdD1n/5zf2M4HgjjWszLd4a5/giphy.gif</a></p>

<hr />
<h2>What we are building</h2>
<p>QuickBite is a food ordering app with a very simple flow:</p>
<ol>
<li><p>A user places an order from the app.</p>
</li>
<li><p>The backend creates the order and marks it as <strong>PENDING</strong>.</p>
</li>
<li><p>Inventory checks if the item is available.</p>
</li>
<li><p>The backend updates the order to <strong>CONFIRMED</strong> or <strong>CANCELLED</strong>.</p>
</li>
</ol>
<p>This looks simple, doesn't it? But it’s a great project because it naturally leads to real backend topics.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/282b6771-4028-4f09-994b-06726c0ca92d.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h2>The final shape of the system</h2>
<p>By the end of this project, QuickBite will have these parts:</p>
<ul>
<li><p><strong>Mobile app</strong>: creates orders and shows the order status.</p>
</li>
<li><p><strong>Orders service (Go)</strong>: exposes a REST API for the mobile app. It stores orders in PostgreSQL.</p>
</li>
<li><p><strong>Inventory service (Go)</strong>: checks and reserves stock. It stores stock and reservations in PostgreSQL.</p>
</li>
<li><p><strong>Kafka</strong>: used for events so services can work asynchronously.</p>
</li>
<li><p><strong>Kubernetes</strong>: used to run everything in a local cluster, like a small production environment.</p>
</li>
</ul>
<p>Even though I built it locally, it follows the same direction a real company system follows. Later, I can move it to AWS, but the learning already happens with local tools.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/9ff3725f-04ef-471d-908a-86a57a4bcae9.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h2>Why do we need services and events?</h2>
<p>At the beginning, it’s tempting to build everything inside one backend. That is fine for learning basics. But once you introduce “inventory”, things get more interesting.</p>
<p>Inventory is not just a function call. Inventory is a part of the business. It has its own rules, its own data, and its own failures. For example, even if the Orders service is working, Inventory might be down. Or Kafka might be down. Or the database might be slow. These failures are normal in real systems, and your code must handle them.</p>
<p>That is why I separated the backend into services and used events:</p>
<ul>
<li><p>Orders create an order and publish <strong>OrderCreated</strong>.</p>
</li>
<li><p>Inventory reads <strong>OrderCreated</strong>, reserves stock, and publishes <strong>InventoryReserved</strong> or <strong>InventoryRejected</strong>.</p>
</li>
<li><p>Orders read the inventory result and update the order status.</p>
</li>
</ul>
<p>Don't worry, we will learn in detail what Services and Events are. But this design makes the system more realistic. It also teaches the most important idea in distributed systems: <strong>your system must work even when parts of it fail</strong>.</p>
<hr />
<h2>The main workflow (with an example)</h2>
<p>Let’s say a user orders <code>burger</code> with quantity <code>2</code>.</p>
<ol>
<li><p>The mobile app sends a request:</p>
<ul>
<li><p><code>POST /v1/orders</code></p>
</li>
<li><p>payload: <code>{ "userId": "u1", "sku": "burger", "quantity": 2 }</code></p>
</li>
</ul>
</li>
<li><p>Orders service writes a row in Postgres:</p>
<ul>
<li><code>status = PENDING</code></li>
</ul>
</li>
<li><p>Orders publishes a <strong>Kafka</strong> event: (We will learn about Kafka in detail so don't stress if you don't know what it is)</p>
<ul>
<li><p>topic: <code>orders.v1</code></p>
</li>
<li><p>event type: <code>OrderCreated</code></p>
</li>
<li><p>includes: orderId, sku, quantity, eventId</p>
</li>
</ul>
</li>
<li><p>Inventory consumes the event and checks stock in its own database:</p>
<ul>
<li>If enough stock exists, it reserves it and stores a reservation record.</li>
</ul>
</li>
<li><p>Inventory publishes back a result event:</p>
<ul>
<li><p>topic: <code>inventory.v1</code></p>
</li>
<li><p><code>InventoryReserved</code> or <code>InventoryRejected</code></p>
</li>
</ul>
</li>
<li><p>Orders consume that result and update the order row:</p>
<ul>
<li><p><code>CONFIRMED</code> if reserved</p>
</li>
<li><p><code>CANCELLED</code> if rejected</p>
</li>
</ul>
</li>
</ol>
<p>This whole flow is async. That means the user might see <code>PENDING</code> for a short time. This is normal and expected. In the mobile app, I used polling to show the status updates. Later, we can improve this with streaming, but polling is perfect for learning.</p>
<hr />
<h2>What makes this project 'production-style'?</h2>
<p>A lot of demos stop at “it works once”. But real systems need more than that. In this app, I also implemented patterns that solve real production problems.</p>
<h3>Outbox pattern (so events are not lost)</h3>
<p>A common failure is that the service writes to the database successfully, but fails to publish the event. If that happens, the system becomes inconsistent. The order exists in the DB, but Inventory never hears about it.</p>
<p>To fix this, I used the <strong>Outbox pattern</strong>:</p>
<ul>
<li><p>Save the event into an <code>outbox_events</code> table in the same database transaction as the order write.</p>
</li>
<li><p>A background worker publishes those outbox events to Kafka.</p>
</li>
<li><p>If Kafka is down, the outbox keeps retrying later.</p>
</li>
</ul>
<p>This makes the system safer.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/515d8d75-7eab-4fd9-ab68-f651a37bc2a8.png" alt="" style="display:block;margin:0 auto" />

<h3>Idempotency (so duplicates don’t break things)</h3>
<p>Kafka is typically “at least once”. That means a consumer can sometimes receive the same message more than once. This is not a bug. It’s part of the design.</p>
<p>So both services must handle duplicates safely:</p>
<ul>
<li><p>Orders uses a <code>processed_events</code> table to ignore duplicate inventory events.</p>
</li>
<li><p>Inventory uses a <code>reservations</code> table keyed by <code>order_id</code> so it won’t reserve twice for the same order.</p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/104ea988-108f-478f-a8db-28a802e3b247.png" alt="" style="display:block;margin:0 auto" />

<h3>DLQ (dead letter queue) for poison messages</h3>
<p>Sometimes you get a message that can never be processed. For example, a message that is not valid JSON. If your consumer keeps retrying forever, it blocks progress.</p>
<p>So I added DLQ topics:</p>
<ul>
<li><p><code>inventory.dlq.v1</code></p>
</li>
<li><p><code>orders.dlq.v1</code></p>
</li>
</ul>
<p>Poison messages are sent to DLQ and committed, so the system keeps moving.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/f0bf1303-1869-46cc-be61-89c5d26ec77f.png" alt="" style="display:block;margin:0 auto" />

<blockquote>
<p>Again, You don't need to understand all these in detail. I am just giving you the overview of the whole project. So just read it out and keep it in your mind that we are gonna be implementing these concepts.</p>
</blockquote>
<hr />
<h2>What was the real learning part for me?</h2>
<p><a class="embed-card" href="https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExbTNsYXNhenZlaXQ4cDMxMWZkbTJlaXdiNTFsMWsxa25xY3k1OGsxdyZlcD12MV9naWZzX3NlYXJjaCZjdD1n/xUXCTpkqwLeh2SrZ4T/giphy.gif">https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExbTNsYXNhenZlaXQ4cDMxMWZkbTJlaXdiNTFsMWsxa25xY3k1OGsxdyZlcD12MV9naWZzX3NlYXJjaCZjdD1n/xUXCTpkqwLeh2SrZ4T/giphy.gif</a></p>

<p>To make sure the system is not fragile, I tested failures on purpose. This is where I learned the most.</p>
<p>Here are examples of the failure tests I ran:</p>
<ul>
<li><p>I stopped Kafka and created an order. The API still accepted the request because of the Outbox. When Kafka came back, events were published, and the order was completed.</p>
</li>
<li><p>I restarted Inventory and confirmed that stock and reservations were still correct because they were stored in Postgres.</p>
</li>
<li><p>I produced invalid JSON messages to Kafka and confirmed they went to the DLQ instead of breaking consumers.</p>
</li>
</ul>
<p>These tests are not extra work. They are part of understanding how distributed systems behave.</p>
<hr />
<h2>What this series will cover next</h2>
<p>In the next post, I’ll start with the very first step: building the Orders service in Go and connecting it to Postgres. I’ll explain:</p>
<ul>
<li><p>project structure (<code>cmd</code>, <code>internal</code>)</p>
</li>
<li><p>basic REST endpoints</p>
</li>
<li><p>Dockerfile basics</p>
</li>
<li><p>Compose basics</p>
</li>
<li><p>the first DB schema and migrations</p>
</li>
</ul>
<p>The goal is that you can follow the series and not only rebuild and copy-paste, but also understand why each piece exists.</p>
<hr />
<p>One thing I learned while building this is tools like Kafka and Kubernetes are not hard because of syntax. They feel hard because they introduce new ways of thinking. The best way to learn them is to build a project where failures happen naturally, and you fix them step by step.</p>
<p>That’s exactly what we’re doing here.</p>
<p>See you in the next blog...</p>
<p><a class="embed-card" href="https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExNWk1YW9yajVrbDluaDJ3ODVubGhrNGQ2amFiZjRrbXU5OTBjb3d0diZlcD12MV9naWZzX3NlYXJjaCZjdD1n/Py1LkJpCEtNiFo0Chr/giphy.gif">https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExNWk1YW9yajVrbDluaDJ3ODVubGhrNGQ2amFiZjRrbXU5OTBjb3d0diZlcD12MV9naWZzX3NlYXJjaCZjdD1n/Py1LkJpCEtNiFo0Chr/giphy.gif</a></p>
]]></content:encoded></item><item><title><![CDATA[Diving into Docker (Part 7): Docker Swarm]]></title><description><![CDATA[If you are still following along, I must say that a few people have come this far. And let me congratulate you because it's been a long journey, and I am sure you have learned many new things so far. ]]></description><link>https://dhruvnakum.xyz/diving-into-docker-part-7-docker-swarm</link><guid isPermaLink="true">https://dhruvnakum.xyz/diving-into-docker-part-7-docker-swarm</guid><category><![CDATA[Docker]]></category><category><![CDATA[docker swarm]]></category><category><![CDATA[Multi-Service Business Routers (MSBRs) Market Growth]]></category><category><![CDATA[containers]]></category><category><![CDATA[Kubernetes]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Beginner Developers]]></category><category><![CDATA[Developer]]></category><dc:creator><![CDATA[Dhruv Nakum]]></dc:creator><pubDate>Mon, 16 Mar 2026 22:31:07 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/073cd526-0c72-4550-97ad-02a9add22ab2.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you are still following along, I must say that a few people have come this far. And let me congratulate you because it's been a long journey, and I am sure you have learned many new things so far. We went from knowing the difference between VMs and Containers to running them and deploying to the Docker Hub. We learned about docker-compose too, which is used in production to deploy multi-service applications.</p>
<p>But if you see, till now, we were dealing with a single host and a few containers. But in real life, that is not the case. In companies, they have so many products and services, and those services do not run on a single host. They are scattered around the world or in other places. Then you might ask, who manages those services? Is it like someone sitting in front of the display and checking all the time which services are working correctly or not?</p>
<p>If you think that is how they manage, you are mistaken, because managing so many services manually is not possible today. So what's the solution? Who manages it?</p>
<p>To answer that, you have to read this blog. But if you don't want to and just want to know, then here is the short answer:</p>
<p><strong>Docker Swarm / Kubernetes</strong></p>
<p>I bet you want more details than just these two words, right? And to get that, you'll have to read the blog, so, sorry, 😅 I can't give you the short answer. Because, like life, there are no shortcuts to achieving goals. Okay, enough of motivation, let's get to the real thing.</p>
<hr />
<h2>Docker Swarm</h2>
<p>We now know how to install Docker, pull images, and work with containers. But as we discussed above, to work with that big scale, we need something that can manage these containers for us. And that's where <strong>Docker Swarm</strong> comes into the picture.</p>
<h3>What is Docker Swarm?</h3>
<p>Docker Swarm is a tool that helps you manage multiple Docker machines. Instead of running containers on just one computer, you can connect multiple machines and treat them like a big system. This group of machines is called a <strong>cluster.</strong></p>
<p>In a Swarm cluster, each machine is called a <strong>node.</strong> A node can be a physical server, a virtual machine, a cloud server, or even a small device like a Raspberry Pi. The only thing required is that Docker must be installed, and the machines must be able to talk to each other over the network.</p>
<h3>Types of Node</h3>
<p>There are two types of nodes: <strong>managers</strong> and <strong>workers</strong>.</p>
<p><strong>Managers</strong> control the cluster. They decide what tasks should run and which worker should run them.</p>
<p><strong>Workers</strong> are the machines that actually run the containers and do the work. Managers tell them what to do, and workers follow those instructions.</p>
<p>Docker Swarm also keeps track of the cluster’s information, like which containers are running and which nodes are available. This information is stored on the manager nodes in a distributed database. The nice thing is that Docker sets this up automatically, so you don’t need to configure it yourself.</p>
<p>If you are concerned about Docker's security, then don't. Because security is built into Swarm. Communication between nodes is encrypted, and each node must prove its identity before joining the cluster. Docker also automatically manages and rotates security certificates, so the system stays secure without extra effort.</p>
<p>Swarm is also used to run and manage applications made of many small services called <strong>microservices</strong>. In Swarm, the main unit used to run applications is called a <strong>service</strong>. A service is similar to a container but with extra features. For example, you can easily run multiple copies of it, update it gradually without downtime, or roll back to an older version if something breaks.</p>
<blockquote>
<p>There is also one tool called <strong>Kubernetes</strong> which is a competitor of Docker Swarm. They both orchestrate containerized applications. Well, it's true that Kubernetes is more popular and have more active community and ecosystem. Docker Swarm has it's own benefits too. It's a lot easier to configure and deploy. While it may not be the best solution for bigger companies, but its an excellent technology for small to medium businesses.</p>
</blockquote>
<p>I think that's enough of the theory. Let's get to the practical part and see how we can implement this ourselves.</p>
<hr />
<h2>Creating our OWN Swarm Cluster</h2>
<p>The example is highly inspired by the book (Docker Deep Dive - Nigel Poulton). The only thing missing there was that he didn't show how to implement it with a single host machine. Because hey, for just the example, we're not gonna have 10 different machines and run and test it, right? So what I did was, I implemented the same thing with the help of <strong>AWS.</strong> Let's see how we can implement it. The only prerequisite is to have an AWS account. So if you don't have one, I would suggest to make one. I am not gonna show it here otherwise the blog will become lengthy. So make one and then start from here.</p>
<h3>Example:</h3>
<p>So, in the example, we are going to make a secure swarm cluster with <strong>one manager nodes</strong> and <strong>two worker nodes</strong>.</p>
<p>As we saw earlier, that nodes can be virtual machines, physical servers, cloud instances (in our case), or Raspberry Pi systems. The only thing required on those system is to have Docker installed.</p>
<h3>Creating 3 EC2 instances ( 1 manager, 2 worker)</h3>
<p>Go to <a href="https://signin.aws.amazon.com/signin">https://signin.aws.amazon.com/signin</a> and sign in with the <strong>Root user</strong>. Once you are in, you will see a dashboard like this.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/99bd4b99-5733-45b4-932d-432fa01c88f8.png" alt="" style="display:block;margin:0 auto" />

<p>Now we need 3 machines, right? So let's create 3 EC2 instances. Go or search for EC2 and get in.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/0d251db0-48cf-4dcb-bef6-a930143ec6c1.png" alt="" style="display:block;margin:0 auto" />

<p>Now, click on <strong>Launch instance.</strong></p>
<h4>Creating Manager Node</h4>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/6b9e4401-e63a-429e-919a-ac32ac95ce62.png" alt="" style="display:block;margin:0 auto" />

<p>Now, we need to name our machine and configure some options.</p>
<blockquote>
<p>If you don't know anything about AWS EC2, I think it's the best time to learn. Because in today's time, it good to have some knowledge about it at-least if not a hands on experience. But anyways, you will not be needing to know much about it for this example as it's gonna be really simple.</p>
</blockquote>
<blockquote>
<p>Here, we are doing nothing but creating a Virtual instances (machines) for us to be able to test Docker Swarm capabilities.</p>
</blockquote>
<p>Let's now finish the configuration part. Go ahead and name this instance <strong>"mgr1"</strong> (this will be our manager). Then select the <strong>Ubuntu</strong> image</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/01833796-943e-493a-8bfc-69c3c8138669.png" alt="" style="display:block;margin:0 auto" />

<p>After that, we need to add a key pair. This is required as we are gonna connect this instance to our laptop's terminal and use it. So go ahead and create an RSA (.pem) private key and store it somewhere safe. I named this key <strong>docker-swarm-test.</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/56fcdab7-3c64-44ee-ac53-0e2949630d0f.png" alt="" style="display:block;margin:0 auto" />

<p>Once this is done, do nothing, just click on <strong>Launch Instance</strong>. As you can see below, we now have the running manager instance.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/3d8da1be-d193-49ef-a14a-a17554ab03af.png" alt="" style="display:block;margin:0 auto" />

<p>Now, we don't need to open these instances to the whole world, right? So what we do is we open a few ports between these instances for them to talk. We can add those ports by going into the <strong>Security</strong> section. Click on the <strong>Security Group link</strong>, it will redirect you to the new page where we can add the ports</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/decb7897-d7cd-49eb-8a63-68844a7604e8.png" alt="" style="display:block;margin:0 auto" />

<p>Click the <strong>"Edit Inbound rules"</strong>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/572f1421-ed5e-4310-8a9d-765b3e80aee0.png" alt="" style="display:block;margin:0 auto" />

<p>Add all the ports defined below. I've added the description for each port, what it is used for, so you can take a look at it too. Once done, save the rules, and you are good to go.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/5a07569a-c090-470f-a373-2845a94e26ee.png" alt="" style="display:block;margin:0 auto" />

<h4>Creating Worker Nodes</h4>
<p>Now we have one Manager node. We need two worker nodes. The process gonna be the same only except the name of the Instance. Go ahead and create <strong>wrk1</strong> and <strong>wrk2</strong>. I am gonna show <strong>wrk1,</strong> and then you can create <strong>wrk2</strong> on your own. It's pretty simple.</p>
<ol>
<li>Name the instance (wrk1) and select the Ubuntu image.</li>
</ol>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/a904b839-a347-4325-b0ec-0e45f9fad67b.png" alt="" style="display:block;margin:0 auto" />

<p>Select the same key pair that we created.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/d06c0d7a-21a3-4b69-bc11-7fe2950fcd03.png" alt="" style="display:block;margin:0 auto" />

<p>Now here comes the most <strong>IMPORTANT</strong> part, so make sure you don't miss it. We need to put all these instances into the <strong>SAME VPC and Subnet</strong> in order for them to work. How will you make sure? Just open the <strong>mgr1 instance (on the right in the screenshot below), go to the Networking tag, and you will see the VPC ID and Subnet ID.</strong></p>
<p>On the worker node (on the left), select the same Subnet ID and VPC ID.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/75c62f65-e8e4-454f-80de-d19e0488bd59.png" alt="" style="display:block;margin:0 auto" />

<p>Once done, launch that instance.</p>
<blockquote>
<p><strong>IMPORTANT:</strong> Make sure you add all the ports like we did for the Manager node.</p>
</blockquote>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/2e0bbd6a-b38e-4c63-ac97-02a8f9451602.png" alt="" style="display:block;margin:0 auto" />

<p>As you can see now, I have all three instances running on my AWS.</p>
<hr />
<h2>SSH into each instance</h2>
<p>Now, let's open these instances in our machine. Open three separate terminals and run below commands</p>
<pre><code class="language-shell">ssh -i ~/Downloads/docker-swarm-test.pem ubuntu@107.21.169.169
</code></pre>
<p>Here, make sure you provide the correct path of the key you downloaded (this is the key which gets downloaded when we created the key-pair). And then specify the <strong>Public IP Address</strong>. Which you will find in the Details tab of the instance</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/daa14961-fd6b-434e-a27b-a4fabc0aa736.png" alt="" style="display:block;margin:0 auto" />

<p>Once done, your terminal will turn into that instance terminal, and you'll see something like this.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/f2b33249-ff1a-498e-8efd-d588e3bfdbe1.png" alt="" style="display:block;margin:0 auto" />

<p>Do this for all three instances, and you will have all three instances running inside your host machine in your terminal.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/79f53448-c010-4190-82a8-d0d24d39462b.png" alt="" style="display:block;margin:0 auto" />

<p>(<strong>Top</strong> - Manager, <strong>Bottom Left</strong> - Worker 1, <strong>Bottom Right</strong> - Worker 2)</p>
<h2>Installing Docker on all 3 instances</h2>
<p>In order to run Docker Swarm, we first need to install Docker inside these instances. To do that run below commands</p>
<pre><code class="language-shell">sudo apt-get update
sudo apt-get install -y docker.io
sudo systemctl enable --now docker
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/8a10021b-a465-4299-a661-ce2b389fbc30.png" alt="" style="display:block;margin:0 auto" />

<p>Run those commands in all three terminals, and you will have Docker installed.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/a7eed435-7873-4e68-8a18-ec2533dd422d.png" alt="" style="display:block;margin:0 auto" />

<h2>Initializing a new swarm</h2>
<p>Now that we have everything in place, we can go ahead and initialize our Docker swarm. One thing to note about is that, Docker nodes that are not part of swarm are said to be in single-engine mode. Once they're added to a swarm they're automatically switched to <strong>swarm mode</strong>.</p>
<p>Running <code>docker swarm init</code> on a Docker host in <em>single-engine</em> mode will switch that node into swarm mode, create a new swarm, and make the node the first manager of the swarm.</p>
<p>And all the additional nodes can then be joined to the swarm as workers and managers. Joining a Docker host to an exisiting swarm switches them into swarm mode as part of the operation.</p>
<p>Let's run the below command to our manager node to initialize a new swarm.</p>
<pre><code class="language-shell">docker swarm init --advertise-addr &lt;manager-ip-address&gt;
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/2c164748-8f50-4d3e-bda8-af28c075b76f.png" alt="" style="display:block;margin:0 auto" />

<p>Let's understand the command:</p>
<p><code>docker swarm init</code> - This tells Docker to initialize a new swarm and make this node the first manager. It also enables swarm mode on the node.</p>
<p><code>--advertise-addr</code> - As the name suggests, this is the swarm API endpoint that will be advertised to other nodes in the swarm. It will be one of the node's IP address, but can be an external load balancer address.</p>
<p><code>--listen-addr</code> - This is the IP address that the node will accept swarm traffic on. If not explicitly set, it defaults to the same value as <code>advertise-addr</code>.</p>
<p>To list the nodes in the swarm run <code>sudo docker node ls</code> command.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/b5576968-b0df-49ad-9d89-a0182d94b187.png" alt="" style="display:block;margin:0 auto" />

<p>As you can see, only the <code>mgr1</code> node is the only node present in the swarm. It's also listed as <strong>Leader</strong>. Now it's time to add the worker nodes to join this swarm too. To do that run below command in manager node</p>
<pre><code class="language-shell">sudo docker swarm join-token worker
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/f00b20ff-a7ca-481f-9304-17ed13235307.png" alt="" style="display:block;margin:0 auto" />

<blockquote>
<p>If you want another manager to join the swarm, run:</p>
<p><code>sudo docker swarm join-token manager</code></p>
</blockquote>
<p>Now go to the worker nodes (<code>wrk1 and wrk2</code>) and join the swarm using the command you got above:</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/cb93b2af-3550-4020-850f-0795261d2a2f.png" alt="" style="display:block;margin:0 auto" />

<p>That's it. We've just created 3 node swarm with 1 manager and 2 workers. And you can see that by running <code>sudo docker node ls</code> command into the manager node. Nodes with nothing in the Manager Status are <strong>workers.</strong></p>
<p>Also if you notice the asterisk (*) in front of the node ID, it says which node you are logged on to and executing commands from.</p>
<h2>Swarm manager high availability</h2>
<p>You must be asking, why we added managers and workers, and how they work together, and what's the point of all these?</p>
<p>So in real worlds, it can happen that sometimes one or more nodes can fail, but if you are using Swarm, the survivors will keep the swarm running without interrupting anything.</p>
<p>Right now, we have only one manager, but if you have more than one managers, only one of them is active at any given moment. This active manager is called the "<strong>Leader</strong>". And the leader is the only one that will ever issue live commands against the swarm.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/66433f9b-9b47-424c-acbb-0ea783641161.png" alt="" style="display:block;margin:0 auto" />

<p>(Diagram from - Docker Deep Dive book by Nigel Poulton)</p>
<h2>Swarm Services</h2>
<p>A <strong>service</strong> in Docker Swarm is a way to run and manage containers across a cluster of machines. It is a feature that only works when Docker is running in <strong>swarm mode</strong>. A service lets you define how your application should run, and the swarm makes sure that state is always maintained</p>
<p>With services, you can set many of the same options you normally use for containers. For example, you can give the service a name, choose which image to use, map ports, and connect it to networks. But services also add extra features that are useful for modern cloud applications.</p>
<p>You can create services by running <code>docker service create</code> command</p>
<pre><code class="language-shell">docker service create --name web -p 8080:8080 --replica 5 dhruvnakum/web
</code></pre>
<p>For example, a command above create a service named <strong>web</strong>, map port <strong>8080</strong>, and run <strong>5 replicas</strong> using a specific image. After the command runs, the manager node decides where those containers should run in the cluster. Each node that receives a task downloads the image and starts a container.</p>
<p>Swarm also constantly watches all services. It runs a background process that checks if the <strong>actual state</strong> matches the <strong>desired state</strong>. If everything matches, nothing changes. But if something goes wrong, the swarm fixes it automatically.</p>
<p>For example, if one of the machines running a container fails, the number of running containers might drop from <strong>5 to 4</strong>. Swarm will notice this and automatically start another container on a different node to bring the total back to <strong>5</strong>. This is called <strong>self-healing</strong>, and it helps keep applications running even when failures happen.</p>
<p>You can see all services in the swarm using the command</p>
<pre><code class="language-shell">docker service ls
</code></pre>
<p>If you want to see the containers that belong to a specific service, you can use</p>
<pre><code class="language-shell">docker service ps
</code></pre>
<p>Another useful feature is <strong>scaling</strong>. If your application gets more traffic, you can increase the number of running containers. For example, if traffic doubles, you can scale the service from <strong>5 containers to 10</strong> using the command <code>docker service scale</code></p>
<p>Example:</p>
<pre><code class="language-shell">docker service scale web=10
</code></pre>
<pre><code class="language-shell">web scaled to 10
overall progress: 10 out of 10 tasks
1/10: running
2/10: running
3/10: running
4/10: running
5/10: running
6/10: running
7/10: running
8/10: running
9/10: running
10/10: running
verify: Service converged
</code></pre>
<p>When scaling happens, Docker tries to spread the containers evenly across the nodes in the cluster. This helps distribute the workload.</p>
<p>If traffic goes down again, you can scale the service back to a smaller number of containers. For example, you can reduce it from <strong>10 back to 5</strong>.</p>
<pre><code class="language-shell">docker service scale web=5
</code></pre>
<pre><code class="language-shell">web scaled to 5
overall progress: 5 out of 5 tasks
1/5: running
2/5: running
3/5: running
4/5: running
5/5: running
verify: Service converged
</code></pre>
<p>If you no longer need the service, you can remove it using the command <code>docker service rm</code>. When you do this, the service and all of its running containers will be deleted from the swarm.</p>
<pre><code class="language-shell">docker service rm web
</code></pre>
<hr />
<h2>Conclusion</h2>
<p>Phew!! That was a lot, right? But I hope the practical implementation, with screenshots and everything, helped you get the idea of how swarm works.</p>
<p>Docker Swarm is Docker's native technology for managing clusters of Docker nodes and deploying and managing cloud-native apps.</p>
<p>It's similar to Kubernetes. So if you understood this right now. Understanding Kubernetes will become easier for you.</p>
<p>That's it. I hope you learned something from this. Just want to say this, understanding this only won't make you understand everything until you try it on your own. So make sure you practise this.</p>
<p>See you in the next one, until then....peace</p>
<p><a class="embed-card" href="https://giphy.com/gifs/fallontonight-jimmy-fallon-peace-out-tonightshow-2nlbKhgnvAK3sR8ffw?utm_source=iframe&amp;utm_medium=embed&amp;utm_campaign=Embeds&amp;utm_term=https%3A%2F%2Fdhruvnakum.xyz%2F">https://giphy.com/gifs/fallontonight-jimmy-fallon-peace-out-tonightshow-2nlbKhgnvAK3sR8ffw?utm_source=iframe&amp;utm_medium=embed&amp;utm_campaign=Embeds&amp;utm_term=https%3A%2F%2Fdhruvnakum.xyz%2F</a></p>]]></content:encoded></item><item><title><![CDATA[Diving into Docker (Part 6): Docker Networking]]></title><description><![CDATA[Finally, here comes the interesting blog of the Diving into Docker series. I've learned so much from the book (Docker Deep Dive - Nigel Poulton) about networking. It's gonna be so much fun to know how]]></description><link>https://dhruvnakum.xyz/diving-into-docker-part-6-docker-networking</link><guid isPermaLink="true">https://dhruvnakum.xyz/diving-into-docker-part-6-docker-networking</guid><category><![CDATA[Docker]]></category><category><![CDATA[Cloud Computing]]></category><category><![CDATA[containers]]></category><category><![CDATA[networking]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Developer]]></category><category><![CDATA[Beginner Developers]]></category><dc:creator><![CDATA[Dhruv Nakum]]></dc:creator><pubDate>Sun, 15 Mar 2026 21:59:29 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/b7f57c83-d582-4330-9864-910ef7cec81f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Finally, here comes the interesting blog of the Diving into Docker series. I've learned so much from the book (Docker Deep Dive - Nigel Poulton) about networking. It's gonna be so much fun to know how Docker connects everything internally. So without further ado, let's get started.</p>
<hr />
<p>When people hear Networking, it often sounds complex and scary. But the core ideas are actually simple once you break them down. Let's tackle down and understand the Docker Networking. I will try my best to simplify it, so let's get started.</p>
<p>Docker networking is divided into 3 main parts</p>
<ol>
<li><p><strong>CNM (Container Network Model)</strong> – the design or the plan, you can say</p>
</li>
<li><p><strong>libnetwork</strong> – the implementation or the code that follows the plan</p>
</li>
<li><p><strong>Drivers</strong> – the actual network types (bridge, overlay, macvlan, etc.)</p>
</li>
</ol>
<p>A simple way to remember this is:</p>
<ul>
<li><p><strong>CNM</strong> is like the blueprint of a house</p>
</li>
<li><p><strong>libnetwork</strong> is like the construction team that follows the blueprint</p>
</li>
<li><p><strong>Drivers</strong> are like the different types of houses you can build (apartment, villa, etc.)</p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/70a94c05-f526-4033-8955-a1616d8cf90e.png" alt="" style="display:block;margin:0 auto" />

<p>Let’s go step by step and understand these in detail, and make it feel clear.</p>
<hr />
<h2>Part 1: CNM (Container Network Model)</h2>
<p>CNM is not software you install. It’s a design document. Docker created CNM to define how container networking should be structured.</p>
<p>CNM says Docker networking is built using 3 building blocks</p>
<ul>
<li><p>Sandbox</p>
</li>
<li><p>Endpoint</p>
</li>
<li><p>Network</p>
</li>
</ul>
<p>If you understand these three, you understand the base of Docker networking.</p>
<hr />
<h2>1) Sandbox</h2>
<p>A sandbox is an isolated networking environment for a container. That sentence sounds heavy, but here’s what it really means:</p>
<p>Every container gets its own private networking setup, including:</p>
<ul>
<li><p>its own <strong>IP address</strong></p>
</li>
<li><p>its own network interfaces like <strong>eth0</strong> inside the container</p>
</li>
<li><p>its own <strong>routing table</strong> and rules about where traffic should go</p>
</li>
<li><p>its own <strong>DNS</strong> settings</p>
</li>
<li><p>its own <strong>ports</strong></p>
</li>
</ul>
<p>So the sandbox is basically, everything networking related that belongs only to that container.</p>
<p>Think of it like this</p>
<ul>
<li><p>Each container lives in its own room</p>
</li>
<li><p>That room has its own Wi‑Fi setup, its own address, and its own rules</p>
</li>
<li><p>Even if two containers are on the same machine, their “rooms” are still separate</p>
</li>
</ul>
<p>On Linux, Docker achieves this separation using something called <strong>network namespaces</strong>. You don’t have to memorize that right now, just remember the outcome which is, containers behave like separate computers from a networking point of view.</p>
<blockquote>
<p>The important thing to remember here is, for example, even when Container A and Container B run on the same Docker host, their networking is still isolated because each has its own sandbox.</p>
</blockquote>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/3c9c07b2-e443-484e-aff7-d961d2bdf511.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h2>2) Endpoint</h2>
<p>Your laptop can connect to Wi‑Fi because it has a network card. Containers don’t have physical network cards. So Docker creates a <strong>virtual network interface</strong> for them. CNM calls this an <strong>endpoint</strong>.</p>
<p>An endpoint’s job is simple, which is to connect a container’s sandbox to a network.</p>
<p>There are two rules that make endpoints easy to understand</p>
<ol>
<li><p>One endpoint connects to exactly one network.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/6bf8d129-e091-48ee-9665-0b13dc0d6a99.png" alt="" style="display:block;margin:0 auto" />

<ol>
<li>If a container needs to join two networks, it needs two endpoints.</li>
</ol>
</li>
</ol>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/8a2a9d19-fde5-481d-9bd1-314c391830eb.png" alt="" style="display:block;margin:0 auto" />

<p>For Example, If a container must talk to both Network A and Network B, Docker gives it</p>
<ul>
<li><p>Endpoint 1 → Network A</p>
</li>
<li><p>Endpoint 2 → Network B</p>
</li>
</ul>
<p>That’s how one container can be part of multiple networks at the same time.</p>
<hr />
<h2>3) Network</h2>
<p>In Docker, a network is like a virtual switch. It groups endpoints. If two containers connect to the same Docker network, they can usually talk to each other.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/96a035c1-51ba-4eb0-861f-5fb1bc91edec.png" alt="" style="display:block;margin:0 auto" />

<p>If they are not on the same network, they can’t talk to each other by default (unless you set up routing manually).</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/edaed8b0-ddfb-46f5-83c5-72b48663cbf2.png" alt="" style="display:block;margin:0 auto" />

<p>A very simple analogy is that a Docker network is like a WhatsApp group. People inside the same group can message each other. If you’re not in the group, you can’t message the group members.</p>
<p>Same idea with containers: same network = communication is possible.</p>
<hr />
<h2>Part 2: libnetwork</h2>
<p>So far, CNM is just a design. It describes what should exist: sandboxes, endpoints, networks. But Docker needs real code that actually creates and manages these things.</p>
<p>That code is called <strong>libnetwork</strong>.</p>
<p>So, CNM says what should exist, <strong>libnetwork</strong> actually creates it and manages it.</p>
<p>Whenever you run commands like:</p>
<ul>
<li><p><code>docker network create</code></p>
</li>
<li><p><code>docker run</code></p>
</li>
<li><p><code>docker network connect</code></p>
</li>
</ul>
<p>Docker Engine uses <strong>libnetwork</strong> behind the scenes. You can think of libnetwork as Docker’s network manager.</p>
<hr />
<h2>Why Docker separated networking from the daemon?</h2>
<p>Early on, a lot of Docker networking code lived inside the Docker daemon <code>dockerd</code>. But over time networking became bigger and more complicated:</p>
<ul>
<li><p>multiple network types (bridge, overlay, macvlan…)</p>
</li>
<li><p>DNS and service discovery features</p>
</li>
<li><p>load balancing</p>
</li>
<li><p>multi-host networking</p>
</li>
</ul>
<p>The daemon started becoming too large and messy. That’s why Docker engineers moved networking into a separate library called <strong>libnetwork</strong>.</p>
<p>This was good because networking could improve without constantly modifying the main daemon, and the design became more modular (cleaner separation), as well as other projects could reuse the networking library</p>
<p>So when someone says networking was ripped out of the daemon and refactored into libnetwork, they mean it was separated into its own component.</p>
<hr />
<h2>Control plane vs Data plane</h2>
<p>These two terms show up a lot in networking. Here’s what it means.</p>
<h3>Control plane (libnetwork)</h3>
<p>The control plane is responsible for deciding how the network should behave and setting up the rules. It does not move packets itself. Instead, it programs the system so packets know where to go. In Docker networking, the control plane is mainly handled by <strong>libnetwork</strong>.</p>
<p>For Example, when you run <code>docker network create mynet</code>: The <strong>control plane performs multiple tasks</strong>.</p>
<h4>1. Create a Network Object</h4>
<p>Docker stores metadata like:</p>
<pre><code class="language-plaintext">Network Name: mynet
Driver: bridge
Subnet: 172.18.0.0/16
Gateway: 172.18.0.1
</code></pre>
<p>This is stored in Docker's internal database.</p>
<h4>2. IP Address Management (IPAM)</h4>
<p>Docker assigns a subnet to the network.</p>
<p>Example:</p>
<pre><code class="language-plaintext">Subnet: 172.18.0.0/16
Gateway: 172.18.0.1
</code></pre>
<p>Later, when containers join the network:</p>
<pre><code class="language-plaintext">Container A → 172.18.0.2
Container B → 172.18.0.3
</code></pre>
<p>IP allocation is controlled by the IPAM system in the control plane.</p>
<h4>3. Select Network Driver</h4>
<p>Docker supports different drivers:</p>
<ul>
<li><p>bridge</p>
</li>
<li><p>overlay</p>
</li>
<li><p>macvlan</p>
</li>
<li><p>host</p>
</li>
<li><p>none</p>
</li>
</ul>
<p>Example:</p>
<pre><code class="language-plaintext">Driver = bridge
</code></pre>
<p>The control plane decides which driver will implement the network.</p>
<h4>4. Maintain Network State</h4>
<p>Control plane tracks:</p>
<ul>
<li><p>which containers belong to the network</p>
</li>
<li><p>assigned IPs</p>
</li>
<li><p>driver configuration</p>
</li>
<li><p>DNS entries</p>
</li>
</ul>
<p>Example state:</p>
<pre><code class="language-plaintext">Network: mynet
Containers:
   web -&gt; 172.18.0.2
   db  -&gt; 172.18.0.3
</code></pre>
<h4>5. Configure System Networking</h4>
<p>The control plane tells Linux:</p>
<ul>
<li><p>create bridges</p>
</li>
<li><p>configure routes</p>
</li>
<li><p>configure iptables rules</p>
</li>
</ul>
<p>But it doesn't move packets itself. Instead, it programs the data plane.</p>
<h3>Data plane (driver)</h3>
<p>Data plane means where the real packets move.</p>
<p>Imagine two containers:</p>
<pre><code class="language-plaintext">Container A
172.18.0.2

Container B
172.18.0.3
</code></pre>
<p>They are connected to:</p>
<pre><code class="language-plaintext">docker0 bridge
</code></pre>
<p>now the network structure is like this</p>
<pre><code class="language-plaintext">Container A
   |
 vethA
   |
 docker0 bridge
   |
 vethB
   |
Container B
</code></pre>
<p>Now when A sends a packet to B:</p>
<pre><code class="language-plaintext">ping 172.18.0.3
</code></pre>
<p>The data plane handles the packet movement.</p>
<hr />
<h2>Implementation</h2>
<p>Let’s walk through a simple example:</p>
<h3>Step 1: Create a bridge network</h3>
<p>You run:</p>
<p><code>docker network create -d bridge mynet</code></p>
<p>Docker Engine receives the command. It asks <code>libnetwork</code> to create a new network object. libnetwork stores, name=mynet, driver=bridge, settings, IP ranges, etc. And then it calls the bridge driver and says build the real network for this.</p>
<p>The bridge driver then sets up Linux-level things like:</p>
<ul>
<li><p>a Linux bridge device</p>
</li>
<li><p>NAT / iptables rules</p>
</li>
<li><p>isolation rules</p>
</li>
</ul>
<h3>Step 2: Run a container on that network</h3>
<p>You run:</p>
<p><code>docker run --network mynet nginx</code></p>
<p>In this case, Docker asks libnetwork to attach this container to “<strong>mynet</strong>”. So what <code>libnetwork</code> will do is create, a sandbox which is a container’s private network environment and an endpoint.</p>
<p>Then libnetwork connects the endpoint to the network and assigns IP + DNS settings. And finally asks the bridge driver that “connect this endpoint to the bridge”</p>
<p>So a very clean way to remember:</p>
<ul>
<li><p>libnetwork organizes and manages</p>
</li>
<li><p>the driver does the real network wiring</p>
</li>
</ul>
<hr />
<h2>Single-host Bridge Networks</h2>
<p>The simplest Docker network type is the single-host bridge network. The name itself tells you the meaning of it. <strong>Single-host</strong> works only on one Docker machine. <strong>Bridge</strong> behaves like a Layer 2 switch.</p>
<p>Every Docker host gets a default bridge network. On Linux it’s called <code>bridge.</code> On Windows it’s called <code>nat.</code></p>
<blockquote>
<p>If you don’t specify a network, containers join this default one</p>
</blockquote>
<p>On Linux, this default <code>bridge</code> network is backed by a real Linux bridge called <code>docker0</code>.</p>
<h3>Creating your own bridge network</h3>
<p>Run the command below to create your own bridge network.</p>
<pre><code class="language-plaintext">docker network create -d bridge localnet
</code></pre>
<p>Then run a container attached to it. For example we have two container <code>c1</code> and <code>c2</code>.</p>
<ul>
<li>run <code>c1</code> on <code>localnet</code></li>
</ul>
<pre><code class="language-plaintext">docker container run -d --name c1 \
--network localnet \
alpine sleep 1d
</code></pre>
<ul>
<li>run <code>c2</code> on <code>localnet</code></li>
</ul>
<pre><code class="language-plaintext">docker container run -d --name c2 \
--network localnet \
ubuntu:latest
</code></pre>
<p>Now <code>c2</code> can reach <code>c1</code>, and very importantly, it can resolve it by name, not just IP.</p>
<p>That leads to an important concept: Service discovery.</p>
<hr />
<h2>Service discovery</h2>
<p>Container IP addresses are often not stable. For example, containers restart and may get a new IP, or scaling up/down creates new containers with new IPs.</p>
<p>So you don’t want your app to hardcode IP addresses.</p>
<p>Instead, you want to say: “talk to db”, “talk to api”, “talk to redis”</p>
<p><strong>That is service discovery, automatic mapping from name → IP.</strong></p>
<p>Docker supports this on the same network using an internal DNS system:</p>
<p>Docker runs an internal DNS server that knows container names and IPs, and containers have a local DNS resolver that forwards requests to Docker’s DNS</p>
<p>So if you run <code>ping c1</code> from inside <code>c2</code>, Docker DNS helps <code>c2</code> translate “c1” into the correct IP.</p>
<blockquote>
<p>This name-based discovery works only within the same Docker network. If two containers aren’t on the same network, Docker DNS won’t resolve them by name for each other.</p>
</blockquote>
<hr />
<h2>Port mapping</h2>
<p>Containers on a bridge network are mainly meant to talk to each other inside the same host/network. But what if you want a browser, something outside Docker, to reach a container web server? That’s where <strong>port mapping</strong> comes in.</p>
<p>For example, you have seen this <code>--publish 5000:80</code> In a command, we write when we run the container. What does that mean?</p>
<p>It simply means that container listens on port 80 and Docker host opens port 5000</p>
<blockquote>
<p>your machine : docker container (5000:80)</p>
</blockquote>
<p>So the outside world talks to the Docker host, and Docker forwards the traffic to the container. This is extremely common for local development.</p>
<hr />
<h2>Multi-host networking</h2>
<p>Bridge networks are great when everything is on one machine. But real systems often run across multiple machines. For example, Host 1 runs some containers. Host 2 runs other containers, and you want containers across hosts to talk like they are on one shared network</p>
<p>By default, containers on different hosts are like they’re in different houses, they’re not on the same local network.</p>
<p>An <strong>overlay network</strong> solves this by creating a virtual network that spans multiple Docker hosts.</p>
<h3>Overlay network</h3>
<p>An overlay network is a virtual network built on top of the real network.</p>
<p>For example, you can consider like this:</p>
<ul>
<li><p>real network = normal roads</p>
</li>
<li><p>overlay network = secret tunnels built on top of those roads</p>
</li>
</ul>
<p>Containers use the tunnels so that, even across different machines, they behave like they’re on the same LAN.</p>
<h3>How overlay “tunneling” works</h3>
<p>When Container A on Host 1 sends data to Container B on Host 2:</p>
<ul>
<li><p><strong>A</strong> sends a normal packet, it thinks <strong>B</strong> is nearby.</p>
</li>
<li><p>Docker on Host 1 wraps that packet inside another packet like putting a letter in an envelope</p>
</li>
<li><p>the outer packet travels over the real network to Host 2</p>
</li>
<li><p>Docker on Host 2 unwraps it and delivers the original packet to B</p>
</li>
</ul>
<p>Docker has a built-in overlay driver, so you can create an overlay network like:</p>
<pre><code class="language-plaintext">docker network create -d overlay mynet
</code></pre>
<p>Usually this is used with Swarm or other orchestrators</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/38d9ebf5-2cfe-489a-aa41-99b0ba95b8ca.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h2>MACVLAN: making containers look like real devices on your LAN</h2>
<p>Overlay is about connecting containers across hosts inside a Docker-managed virtual network. Macvlan is a different goal.</p>
<p>Macvlan is used when you want containers to join the real physical network directly.</p>
<p>In other words, instead of hiding containers behind the host like bridge / NAT often does, <strong>macvlan</strong> makes each container appear like a real machine on your network.</p>
<p>With macvlan, each container gets its own MAC address, its own IP address from your real LAN subnet</p>
<p>So your switch/router sees the container as it sees: your laptop, a server, a VM, and now… the container</p>
<p>macvlan often requires the host network card to be in promiscuous mode. Promiscuous mode is sometimes blocked in corporate networks or cloud providers</p>
<p>So, macvlan is commonly a good fit in controlled environments like data centers.</p>
<hr />
<h2>Overlay vs Macvlan</h2>
<p>Overlay:</p>
<ul>
<li><p>Best for container-to-container communication across multiple Docker hosts.</p>
</li>
<li><p>Containers feel like they are on one shared Docker network.</p>
</li>
<li><p>Common in clusters, Swarm or microservices</p>
</li>
</ul>
<p>Macvlan:</p>
<ul>
<li><p>Best when containers must be first-class citizens on the physical network</p>
</li>
<li><p>Containers get real LAN IPs and MACs</p>
</li>
<li><p>Common when containers must talk to physical servers directly and be reachable from outside without port mapping</p>
</li>
</ul>
<hr />
<h2>Swarm publishing: Ingress mode vs Host mode</h2>
<p>In Swarm, you usually deploy services and not individual containers. A service can have replicas running on different nodes. And to allow outside users to access a service, you publish a port.</p>
<p>For example,</p>
<ul>
<li><p>published port (outside) = 5000</p>
</li>
<li><p>target port (inside container) = 80</p>
</li>
</ul>
<p>Swarm supports two publishing modes:</p>
<h3>1) Ingress mode (default)</h3>
<ul>
<li><p>Any node can accept traffic, even if it’s not running the container. And the cluster routes it internally to the right node.</p>
</li>
<li><p>Ingress mode is the default. This means any time you publish a service with -p or --publish it will default to ingress mode.</p>
</li>
<li><p>To publish a service in host mode you need to use the long format of the <code>--publish</code> flag and add <code>mode=host</code>. Let’s see an example using host mode.</p>
</li>
</ul>
<pre><code class="language-plaintext">docker service create -d --name c1 \
--publish published=5000,target=80,mode=host \
nginx
</code></pre>
<h3>2) Host mode</h3>
<ul>
<li>In this case, only nodes that are actually running the service accept traffic on that port.</li>
</ul>
<p>In practice, people often talk about routing mesh and load balancing here. If multiple replicas exist, Swarm can spread incoming requests across them.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/a734834b-8972-4f61-ad21-a8ef81599b3d.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h2>Conclusion</h2>
<ul>
<li><p>So in summary, Docker networking boils down to three things.</p>
<ul>
<li><p><strong>CNM</strong> defines the model - sandbox, endpoint, network.</p>
</li>
<li><p><strong>libnetwork</strong> is the <strong>manager</strong> that creates and connects those pieces, and drivers - bridge/overlay/macvlan, do the real packet-moving work.</p>
</li>
<li><p><strong>Bridge</strong> is for containers on one host, overlay connects containers across hosts like one network, and macvlan puts containers directly onto the physical LAN with their own MAC/IP.</p>
</li>
</ul>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Diving into Docker (Part 5): Deploying apps with `docker-compose` ]]></title><description><![CDATA[In the previous blog...
We saw how we can containerize our application and push it to the Docker Hub registry for anyone to use.
In real world, applications are not that simple, right? There are loads]]></description><link>https://dhruvnakum.xyz/diving-into-docker-part-5-deploying-apps-with-docker-compose</link><guid isPermaLink="true">https://dhruvnakum.xyz/diving-into-docker-part-5-deploying-apps-with-docker-compose</guid><dc:creator><![CDATA[Dhruv Nakum]]></dc:creator><pubDate>Sat, 14 Mar 2026 18:27:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/289c0df5-d354-42dc-acef-630d60c8794a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>In the previous blog...</h2>
<p>We saw how we can containerize our application and push it to the Docker Hub registry for anyone to use.</p>
<p>In real world, applications are not that simple, right? There are loads of things like, web server, database, cache, some background worker, etc. And things get complicated quickly. If we try to manage this using <code>docker run</code> command and custom scripts then things become messy and hard to maintain.</p>
<p>This is exactly where <strong>Docker Compose</strong> helps. Docker Compose lets you describe your entire multi-container application in one simple configuration file, and then deploy everything with a single command. Instead of remembering a dozen commands, you maintain one declarative file. Once the app is running, Compose also gives you a small set of commands to manage the whole app’s lifecycle, like start, stop, restart, view status, and tear it down cleanly. Because the configuration is just a text file, you can also store it in Git and treat it like normal code.</p>
<p>Docker Compose works across platforms, and if you use Docker Desktop on Mac, Compose is typically installed automatically. You can quickly verify it by checking the version with <code>docker-compose --version</code>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/8ac6bdef-6e34-44c0-8435-be2663d4719f.png" alt="" style="display:block;margin:0 auto" />

<h2>Understanding Compose Files (YAML)</h2>
<p>Docker Compose uses a YAML file to define a multi-service application. YAML is popular because it’s human-readable and clean for configuration. Technically, YAML is a superset-like format that can represent the same structures as JSON, and Compose also supports JSON, but YAML is the standard and most common format you’ll see in real projects.</p>
<p>By default, Compose looks for a file named <code>docker-compose.yml</code>. If you want to use a different name, for example, a production-specific file, you can pass the filename using the <code>-f</code> flag when you run Compose.</p>
<p>To make this more understandable, imagine a small app built with three services:</p>
<ul>
<li><p>backend</p>
</li>
<li><p>frontend</p>
</li>
<li><p>db</p>
</li>
</ul>
<p>Compose lets you define all the services together so they can be started and managed as one unit.</p>
<h2>What’s Inside a <code>docker-compose.yml</code></h2>
<p>Just look at the below docker-compose file.</p>
<pre><code class="language-shell">version: "3.8"
services:
  web-fe:
   ...
  redis:
   ...
networks:
  counter-net:

volumes:
  counter-volume:
</code></pre>
<p>A Compose file is structured into top-level sections (keys). The file includes mostly these four important top-level keys:</p>
<ol>
<li><p><code>version</code></p>
</li>
<li><p><code>services</code></p>
</li>
<li><p><code>networks</code></p>
</li>
<li><p><code>volumes</code></p>
</li>
</ol>
<p>Other top-level keys also exist (like <code>secrets</code> and <code>configs</code>), but these four are the core ones you’ll see often.</p>
<h3>1) <code>version</code>: Compose file format version</h3>
<p>The <code>version</code> key is required in this style of Compose file and appears at the top. It defines the version of the Compose file format, you can think of it like the schema/API version for the YAML format. A common mistake is assuming this refers to your Docker Engine version or the Docker Compose binary version it does not. It only describes the format rules for the YAML file itself.</p>
<p>In your example, the Compose file uses version <code>3.8</code>, which is a widely used format.</p>
<h3>2) <code>services</code>: The containers that make up your app</h3>
<p>The <code>services</code> section is the heart of Compose. This is where you define each microservice that will run as a container. In the example, there are two services: <code>web-fe</code> and <code>redis</code>.</p>
<p>A really important detail is that Compose uses these service names when it generates container names. So if you define services named <code>web-fe</code> and <code>redis</code>, the containers created will include those names (often combined with the project name, like <code>counter-app_web-fe_1</code>). This naming becomes useful when you’re troubleshooting, checking logs, or inspecting containers later.</p>
<h3>3) <code>networks</code>: How containers talk to each other</h3>
<p>The <code>networks</code> section tells Docker what networks to create. By default, Compose typically creates <strong>bridge networks</strong>, which are <strong>single-host networks</strong>. That means the network connects containers running on the same Docker host. If you need more advanced networking, Compose can reference other drivers, but the default bridge model is perfect for local development and many single-host deployments.</p>
<blockquote>
<p>We have the whole dedicated Docker Networking blog upcoming, so stay tuned</p>
</blockquote>
<p>In the example, a network named <code>counter-net</code> is defined. Both the service attach to this network, which means they can talk to each other using service names as DNS names.</p>
<h3>4) <code>volumes</code>: Persistent data storage</h3>
<p>The <code>volumes</code> section defines Docker volumes that should exist for the application. Volumes are used when you want data to survive container restarts and container deletion. This is especially important for databases, but it’s also helpful for development workflows where you want to persist files, cached dependencies, or app state.</p>
<p>In the example, a volume called <code>counter-vol</code> is defined and mounted into the <code>web-fe</code> container. This means some directory inside the container maps to a real persistent storage location managed by Docker.</p>
<h2>Breaking Down the Example Services</h2>
<pre><code class="language-shell">version: "3.8"
services:
  web-fe:
    build: .
    command: python app.py
    ports:
      - target: 5000
        published: 5000
    networks:
      - counter-net
    volumes:
      - type: volume
        source: counter-vol
        target: /code
  redis:
    image: "redis:alpine"
    networks:
      counter-net:

networks:
  counter-net:

volumes:
  counter-vol:
</code></pre>
<h3>The <code>web-fe</code> service</h3>
<p>The <code>web-fe</code> service is a custom application container. Instead of pulling a prebuilt image from Docker Hub, it uses <code>build: .</code>. That tells Docker: Build an image from the Dockerfile in the current directory. This is a very common development pattern because you keep the application code and Dockerfile together in one repo.</p>
<p>Next, the service defines a command like <code>python app.py</code>. This tells the container what process to run when it starts. In this case, the main process is a Python program called <code>app.py</code> your Flask web app.</p>
<p>It also defines <code>ports</code>, mapping port 5000 inside the container to port 5000 on the host machine. This is what makes the app accessible from your browser. Without a port mapping, the service might run fine but would be isolated inside Docker networking and not reachable directly from your host.</p>
<p>The service attaches to the <code>counter-net</code> network. This is critical because it allows <code>web-fe</code> to communicate with <code>redis</code> over an isolated internal network and it also keeps the service topology tidy instead of throwing everything onto the default network.</p>
<p>Finally, the service mounts a volume. The Compose file mounts <code>counter-vol</code> into <code>/code</code> inside the container. This means files written into <code>/code</code> in the container are actually stored in a Docker managed volume, so they persist beyond the lifecycle of that one container instance.</p>
<p>Putting it all together, Compose deploys one container for <code>web-fe</code>, built from your local Dockerfile, running <code>app.py</code>, exposed on port 5000, connected to the app network, and using persistent storage for the mounted path.</p>
<h3>The <code>redis</code> service</h3>
<p>The <code>redis</code> service is simpler because it uses an existing image: <code>redis:alpine</code>. This tells Docker to pull the Redis image from Docker Hub if it’s not already present locally.</p>
<p>It also attaches to the same <code>counter-net</code> network, so the web service can talk to it. There’s no need to publish Redis ports to the host unless you specifically want to connect to Redis from outside Docker. Many apps keep Redis internal, accessible only to other containers.</p>
<h2>Deploying a Multi-Container App with One Command</h2>
<p>Let's clone an example app repo and start it using Compose.</p>
<ul>
<li><p><code>git clone &lt;</code><a href="https://github.com/red-star25/docker_tutorial_compose_example">https://github.com/red-star25/docker_tutorial_compose_example</a>&gt;</p>
</li>
<li><p><code>docker-compose up &amp;</code></p>
</li>
</ul>
<p>The <code>docker-compose up</code> command is the standard way to bring up an app. When you run it, Compose does several things for you automatically:</p>
<ol>
<li><p>Builds images that require building (like the <code>web-fe</code> service)</p>
</li>
<li><p>Pulls images that need downloading (like <code>redis:alpine</code>)</p>
</li>
<li><p>Creates required networks <code>counter-net</code>)</p>
</li>
<li><p>Creates required volumes ( <code>counter-volume</code>)</p>
</li>
<li><p>Starts containers in the correct configuration</p>
</li>
</ol>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/bb36f02e-2cc5-4615-b0d6-7f343293f739.png" alt="" style="display:block;margin:0 auto" />

<p>By default, <code>docker-compose up</code> expects the Compose file to be named <code>docker-compose.yml</code>. If your file is named something else, you must pass it with <code>-f</code>. For example, if your file is <code>something-else.yml</code>, you would run <code>docker-compose -f something-else.yml up</code>.</p>
<p>Many people also start apps in “detached mode” using <code>-d</code>, which runs containers in the background and returns your terminal immediately. So instead of using <code>&amp;</code>, you can run <code>docker-compose up -d</code>. Both approaches give you your terminal back, but <code>-d</code> is the standard Compose way.</p>
<p>After the app starts, you can confirm what happened by listing images, containers, and networks. You’ll notice a new image for the web app (created from your Dockerfile) and a container for each service.</p>
<p><code>docker container ls</code></p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/3d095bac-16d5-455b-92d7-e8ac6ceb5365.png" alt="" style="display:block;margin:0 auto" />

<p><code>docker image ls</code></p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/a97f2b15-7324-4ba2-af8a-a36a4562cbd1.png" alt="" style="display:block;margin:0 auto" />

<p>At this point, you’ve successfully deployed a multi-container app using Compose.</p>
<h2>Managing the App Lifecycle with Compose</h2>
<p>Once the app is running, Compose becomes your control panel.</p>
<h3>Stopping and removing: <code>docker-compose down</code></h3>
<p>If you want to stop the app and remove the containers and network it created, you use <code>docker-compose down</code>. This is like the tear down the environment command. It shuts down the services and removes the containers (and usually the default network created for the app).</p>
<p><code>docker-compose down</code></p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/b321daee-45fd-4ee6-99d9-7eb013288645.png" alt="" style="display:block;margin:0 auto" />

<p>A key detail from here is that volumes are not deleted by default when you run <code>down</code>. This is intentional. Volumes are meant to store persistent data, so Docker treats them as long-lived resources. That means if your app wrote data to a volume, the data will still be there the next time you bring the app up.</p>
<p>Also, images you built or pulled remain on the system. This is another reason redeployments become faster because Docker doesn’t need to download or rebuild everything again unless something changed.</p>
<h3>Bringing it back quickly: <code>docker-compose up -d</code></h3>
<p>If you run <code>docker-compose up -d</code> again after bringing it down, you’ll often see it start faster. The images are already present and the volume still exists, so Compose only needs to recreate containers and the network, then start everything.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/3a2e3d98-d6e4-412c-9918-ee0c377e61c9.png" alt="" style="display:block;margin:0 auto" />

<h3>Checking status: <code>docker-compose ps</code></h3>
<p>To see what containers belong to the Compose app and their status (running/stopped), you use <code>docker-compose ps</code>. This is more convenient than <code>docker container ls</code> because it focuses on the services in the Compose project.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/e2c8e84c-7919-4952-ba56-d83e84c1831c.png" alt="" style="display:block;margin:0 auto" />

<h3>Seeing processes inside containers: <code>docker-compose top</code></h3>
<p>If you want to see which processes are running inside each service container, you can use <code>docker-compose top</code>. This is useful when debugging “Is my app actually running?” situations.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/a86ca499-ec59-49ec-8c09-c451057c4592.png" alt="" style="display:block;margin:0 auto" />

<h3>Stop without deleting resources: <code>docker-compose stop</code></h3>
<p>Sometimes you just want to pause the app without removing containers, networks, and configuration. <code>docker-compose stop</code>stops all containers in the Compose app but keeps them around. You can then confirm their status using <code>docker-compose ps</code>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/153e816f-e3d9-429f-9ea4-ae12d89d866a.png" alt="" style="display:block;margin:0 auto" />

<h3>Restarting: <code>docker-compose restart</code></h3>
<p>If you’ve stopped the app (or if something is acting strange), <code>docker-compose restart</code> restarts the services. It’s a convenient way to bounce the entire application without destroying and recreating everything.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/87da64f6-8905-490f-8445-2438686f88f1.png" alt="" style="display:block;margin:0 auto" />

<h2>Key Docker Compose Commands</h2>
<p>To wrap everything up, here’s what the common commands mean in simple terms:</p>
<p><code>docker-compose up</code> starts the whole app (building/pulling images, creating networks/volumes, starting containers).</p>
<p><code>docker-compose up -d</code> does the same but runs in the background.</p>
<p><code>docker-compose ps</code> shows the status of the app’s containers.</p>
<p><code>docker-compose top</code> shows the processes running inside each container.</p>
<p><code>docker-compose stop</code> stops the containers but keeps them and their resources.</p>
<p><code>docker-compose restart</code> restarts containers that belong to the app.</p>
<p><code>docker-compose down</code> stops and removes the app’s containers</p>
<p><code>docker-compose rm</code> removes stopped containers</p>
<h2>Final Thoughts</h2>
<p>Docker Compose is powerful because it turns a messy set of manual Docker commands into one clean, version-controlled application definition. It makes it easy to bring up complex multi-service applications on your machine or on a server, and it gives you a consistent way to manage the application over time.</p>
<p>Once you get comfortable reading and writing Compose files especially understanding <code>services</code>, <code>networks</code>, and <code>volumes</code> you’ll find deploying and managing multi-container apps becomes much simpler and far less error-prone.</p>
<p>See you in the next one, until then...</p>
<p><a class="embed-card" href="https://giphy.com/gifs/fallontonight-jimmy-fallon-peace-out-tonightshow-2nlbKhgnvAK3sR8ffw?utm_source=iframe&amp;utm_medium=embed&amp;utm_campaign=Embeds&amp;utm_term=https%3A%2F%2Fdhruvnakum.xyz%2F">https://giphy.com/gifs/fallontonight-jimmy-fallon-peace-out-tonightshow-2nlbKhgnvAK3sR8ffw?utm_source=iframe&amp;utm_medium=embed&amp;utm_campaign=Embeds&amp;utm_term=https%3A%2F%2Fdhruvnakum.xyz%2F</a></p>]]></content:encoded></item><item><title><![CDATA[Diving into Docker (Part 4): Containerizing an app]]></title><description><![CDATA[Docker is all about taking an application and running it inside a container. As we saw in the previous blog, a container is a lightweight package that has your app plus everything it needs to run. Thi]]></description><link>https://dhruvnakum.xyz/diving-into-docker-part-4-containerizing-an-app</link><guid isPermaLink="true">https://dhruvnakum.xyz/diving-into-docker-part-4-containerizing-an-app</guid><category><![CDATA[Docker]]></category><category><![CDATA[containerization]]></category><category><![CDATA[Devops]]></category><category><![CDATA[Cloud Computing]]></category><category><![CDATA[Microservices]]></category><category><![CDATA[ci-cd]]></category><category><![CDATA[software development]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Beginner Developers]]></category><dc:creator><![CDATA[Dhruv Nakum]]></dc:creator><pubDate>Fri, 13 Mar 2026 22:17:56 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/7102f925-0c6f-436a-96a0-e3fd60cb44fd.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Docker is all about taking an application and running it inside a container. As we saw in the previous blog, a container is a lightweight package that has your app plus everything it needs to run. This makes it much easier to build, ship, and run the same app on different computers without the it works on my machine problem. That's the whole point, right?</p>
<p>When you containerize an app, you are basically preparing it so Docker can turn it into an image, and then run that image as a container.</p>
<h3>What does “containerizing” actually mean?</h3>
<p>Think of containerizing like packing your app into a travel bag:</p>
<ul>
<li><p>Your app code is inside the bag.</p>
</li>
<li><p>Your app dependencies, like Node.js packages, etc, are inside the bag.</p>
</li>
<li><p>The instructions on how to start your app are also inside the bag.</p>
</li>
</ul>
<p>Once the bag is packed, anyone can run it the same way, as long as they have Docker.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/59be4c47-52f6-409d-b26a-3e211e76da4c.png" alt="" style="display:block;margin:0 auto" />

<h3>The usual flow of containerizing an app</h3>
<p>Most Docker container workflows follow the same steps:</p>
<p>First, you start with your <strong>application code</strong> and <strong>whatever it depends on (libraries, packages, config files)</strong>. Next, you write a <strong>Dockerfile</strong>. This Dockerfile is a set of instructions that tells Docker how to build your app into an image.</p>
<p>After that, you run a Docker build command. Docker reads your Dockerfile line by line and creates an image. Once you have an image, you can store it in an image registry (optional, but very common). Finally, you run a container using that image.</p>
<p>So the flow is:</p>
<p>You have code → you create a Dockerfile → you build an image → you push it to a registry (this is optional) → you run a container.</p>
<h3>Example: containerizing a simple single-container app</h3>
<p>Let’s say you want to containerize a small Node.js web app.</p>
<p>You copy the code from GitHub and go into the project folder:</p>
<ul>
<li><a href="https://github.com/red-star25/docker_tutorial_sample_nodejs_app.git">https://github.com/red-star25/docker_tutorial_sample_nodejs_app.git</a> clone this repository</li>
</ul>
<p>Inside that folder, you’ll find the Dockerfile.</p>
<h3>What is a Dockerfile?</h3>
<p>A Dockerfile is the starting point for creating a container image. It describes:</p>
<ul>
<li><p>What base system should your app start from</p>
</li>
<li><p>What software needs to be installed</p>
</li>
<li><p>What files to copy into the image</p>
</li>
<li><p>Which command should run when the container starts</p>
</li>
</ul>
<p>One important detail: the folder that contains your app code and files you want to copy into the image is called the <strong>build context</strong>. Most people keep the Dockerfile at the root of this folder, so it is easy to build.</p>
<p>Also, the name matters: it must be exactly <code>Dockerfile</code> with a capital <strong>D</strong>, and it must be one word.</p>
<h3>Walking through the example Dockerfile</h3>
<p>Here is the Dockerfile from the page, and what each line is doing:</p>
<p>The first line is:</p>
<ul>
<li><code>FROM alpine</code></li>
</ul>
<p>This means: start with Alpine Linux as the base image. Alpine is a very small Linux image, so it’s popular for small containers. Every Dockerfile must start with a <code>FROM</code> instruction because it sets the base layer.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/2ffdd6d0-ef01-489a-a352-d635f5133b30.png" alt="" style="display:block;margin:0 auto" />

<p>Then we have:</p>
<ul>
<li><code>LABEL maintainer="dhruv.nakum25@gmail.com"</code></li>
</ul>
<p>A label is metadata. It doesn’t install anything or change files in a big way. It’s just extra information added to the image. Adding a maintainer label is a nice practice because people know who to contact about the image.</p>
<p>Next is:</p>
<ul>
<li><code>RUN apk add --update nodejs nodejs-npm</code></li>
</ul>
<p>This installs Node.js and npm in the image using Alpine’s package manager <code>apk</code>. This step creates a new image layer because it adds software to the image.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/bf94b974-b9f9-4b2e-8dea-54f1a8ded9de.png" alt="" style="display:block;margin:0 auto" />

<p>Then:</p>
<ul>
<li><code>COPY . /src</code></li>
</ul>
<p>This copies everything in your build context (your current folder) into the image at <code>/src</code>. This also creates a new layer, because you are adding files to the image.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/2d523308-6758-433c-a18a-bc3cd9974120.png" alt="" style="display:block;margin:0 auto" />

<p>Next:</p>
<ul>
<li><code>WORKDIR /src</code></li>
</ul>
<p>This sets the working directory inside the image. From this point onward, commands run as if <code>/src</code> is the current folder. This is usually metadata, not a layer that adds content.</p>
<p>After that:</p>
<ul>
<li><code>RUN npm install</code></li>
</ul>
<p>This runs npm install inside <code>/src</code> (because of the WORKDIR). It installs dependencies listed in <code>package.json</code>. This creates another new layer because it adds installed packages into the image.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/cd7a8e3e-0243-4806-a09d-1bdb0662517d.png" alt="" style="display:block;margin:0 auto" />

<p>Then:</p>
<ul>
<li><code>EXPOSE 8080</code></li>
</ul>
<p>This tells that the app listens on port 8080 inside the container. This is important for humans and tools, but it’s metadata, not a layer.</p>
<p>Finally:</p>
<ul>
<li><code>ENTRYPOINT ["node", "./app.js"]</code></li>
</ul>
<p>This tells Docker what command should run when the container starts. In this case, it runs the Node app. This is also metadata.</p>
<h3>Building the Docker image</h3>
<p>Once you have the Dockerfile, you build the image using the build command. In this example, the image is called <code>web:latest</code>.</p>
<p>The command is:</p>
<ul>
<li><code>docker image build -t web:latest .</code></li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/705b82d2-a803-453e-ac4f-ab7b995ed9b3.png" alt="" style="display:block;margin:0 auto" />

<p>The dot (<code>.</code>) at the end is very important. It tells Docker use my current folder as the build context.</p>
<p>After the build finishes, you can list images to confirm it exists:</p>
<ul>
<li><code>docker image ls</code></li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/9a06846f-e8de-46a9-8db2-b3d840ed3b36.png" alt="" style="display:block;margin:0 auto" />

<p>You can also inspect the image to see what settings Docker stored from your Dockerfile:</p>
<ul>
<li><code>docker image inspect web:latest</code></li>
</ul>
<p>This is a good way to confirm things like the entry point and the image layers.</p>
<h3>Pushing images to a registry (Docker Hub)</h3>
<p>Pushing is optional, but it’s very useful. If your image is only on your laptop, no one else can pull it easily. If you push it to a registry, you can download and run it from anywhere.</p>
<p>Docker Hub is the most common public registry and is the default place Docker pushes to.</p>
<p>First you login:</p>
<ul>
<li><code>docker login</code></li>
</ul>
<p>Before pushing, you need to tag the image properly. Docker needs three parts:</p>
<ul>
<li><p>Registry (example: <a href="http://docker.io/">docker.io</a> for Docker Hub, even if you don’t write it)</p>
</li>
<li><p>Repository (often includes your username)</p>
</li>
<li><p>Tag (like latest, v1, etc.)</p>
</li>
</ul>
<p>If you try to push <code>web:latest</code> directly, Docker will attempt to push it to a repository named <code>web</code>, which you probably don’t own.</p>
<p>So instead, you tag it with your Docker Hub username (Docker ID). Example from the page:</p>
<ul>
<li><code>docker image tag web:latest dhruvnakum/web:latest</code></li>
</ul>
<p>This does not remove the old tag. It just adds another tag pointing to the same image.</p>
<p>Now you can push:</p>
<ul>
<li><code>docker image push dhruvnakum/web:latest</code></li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/4db401e1-3e91-47fb-9a2c-89bf427bd448.png" alt="" style="display:block;margin:0 auto" />

<p>Once it is pushed, you can pull it later from anywhere.</p>
<h3>Running the container</h3>
<p>After the image is built, you run it as a container.</p>
<p>Example command:</p>
<ul>
<li><code>docker container run -d --name mycontainer -p 80:8080 web:latest</code></li>
</ul>
<p>Let’s break that down simply:</p>
<ul>
<li><p><code>-d</code> means run in the background (detached mode)</p>
</li>
<li><p><code>--name c1</code> gives the container a friendly name</p>
</li>
<li><p><code>-p 80:8080</code> maps port 80 on your computer to port 8080 inside the container</p>
</li>
<li><p><code>web:latest</code> is the image you want to run</p>
</li>
</ul>
<p>So if your app listens on 8080 inside the container, you can visit <code>http://localhost</code> in your browser because port 80 on your machine is connected to port 8080 in the container.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/2e870ca3-2346-4c5d-9c8b-7f8cf213ab58.png" alt="" style="display:block;margin:0 auto" />

<h3>A closer look: layers vs metadata</h3>
<p>Docker reads the Dockerfile from top to bottom, one line at a time. Some lines create real filesystem layers, and some lines only create metadata.</p>
<p>In general:</p>
<ul>
<li><p>Instructions like <code>FROM</code>, <code>RUN</code>, and <code>COPY</code> create layers because they add or change files.</p>
</li>
<li><p>Instructions like <code>WORKDIR</code>, <code>EXPOSE</code>, <code>ENV</code>, and <code>ENTRYPOINT</code> usually add metadata instead of creating layers.</p>
</li>
</ul>
<p>If you want to see the image build steps and layers, you can use:</p>
<ul>
<li><code>docker image history web:latest</code></li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/89d7117f-408c-42d0-a033-1cdf8792c6c7.png" alt="" style="display:block;margin:0 auto" />

<p>And to verify the final result including entrypoint and layers, use:</p>
<ul>
<li><code>docker image inspect web:latest</code></li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/bf903fb9-5f50-4b96-9ab8-6af8a7d8d535.png" alt="" style="display:block;margin:0 auto" />

<h3>Why image size matters</h3>
<p>When it comes to Docker images, big is bad.</p>
<p>Smaller images are usually:</p>
<ul>
<li><p>faster to build</p>
</li>
<li><p>faster to push and pull</p>
</li>
<li><p>easier to store</p>
</li>
<li><p>less risky</p>
</li>
</ul>
<p>A common problem is that we install build tools and temporary dependencies, but then we forget to remove them. That makes production images bigger than they need to be.</p>
<p>Multi-stage builds solve this in a clean way. A multi-stage Dockerfile has multiple <code>FROM</code> lines. Each <code>FROM</code> starts a new stage. You can build in one stage, and then copy only the final needed output into the last stage (which stays small and clean).</p>
<h3>Build cache best practices</h3>
<p>Docker tries to speed up builds using cache.</p>
<p>For each Dockerfile instruction, Docker checks if it already has a cached layer for that exact step. If yes, it reuses it, and the build is faster. If no, Docker builds a new layer.</p>
<p>But here’s the key: once the cache is broken at some step, Docker usually has to rebuild everything after that step too.</p>
<p>That’s why ordering your Dockerfile matters a lot.</p>
<p>Also, Docker checks files you copy. Even if the Dockerfile line didn’t change, if the content of files in the folder changed, the checksum will change and Docker will rebuild that layer.</p>
<p>If you ever want to ignore cache completely, you can build with:</p>
<ul>
<li><code>docker image build --no-cache=true -t web:latest .</code></li>
</ul>
<h3>Squashing images</h3>
<p>Sometimes images end up with many layers. Squashing can combine layers into fewer layers.</p>
<p>This can be helpful when you want fewer layers, but it has a downside: squashed images don’t share layers as efficiently. That can increase storage usage and make pushes/pulls bigger in some cases.</p>
<p>If you want to squash during build, you can add <code>--squash</code> to the build command.</p>
<h2>Conclusion</h2>
<p>So we learned that containerizing an app is mainly about writing a good Dockerfile and using it to build a clean image. Once you have an image, you can run it the same way on any Docker host, and you can share it by pushing to a registry. That's it.</p>
<p>Next up, we have <strong>docker-compose.</strong> It's the most fundamental topic for multi-stage builds. And we use it in production. So it's gonna be really fun learning that. But for now, create a simple app, containerize it using Dockerfile, and try it on your own and see if you understood the concept till now. Until then...</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/02925105-deb0-44f1-8495-a0972248855e.png" alt="" style="display:block;margin:0 auto" />]]></content:encoded></item><item><title><![CDATA[Diving into Docker (Part 3) : Container]]></title><description><![CDATA[In the previous blog...
We learned about Images. What image is, and how it works internally. In this blog, we are gonna dive deep into Containers and learn what they are, how they differ from VMs, the]]></description><link>https://dhruvnakum.xyz/diving-into-docker-part-3-container</link><guid isPermaLink="true">https://dhruvnakum.xyz/diving-into-docker-part-3-container</guid><category><![CDATA[Docker]]></category><category><![CDATA[containers]]></category><category><![CDATA[Cloud Computing]]></category><category><![CDATA[Microservices]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Developer]]></category><dc:creator><![CDATA[Dhruv Nakum]]></dc:creator><pubDate>Thu, 12 Mar 2026 22:01:54 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/336026a6-002f-4d3a-9b4e-880fb4157755.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>In the previous blog...</h2>
<p>We learned about Images. What image is, and how it works internally. In this blog, we are gonna dive deep into Containers and learn what they are, how they differ from VMs, their use cases, their lifecycle, and much more. So let's get right into it.</p>
<hr />
<h3>What is a container?</h3>
<p>Containers are one of the main ideas behind Docker. If you understand what a container is and how to run one, most Docker commands will start to make sense.</p>
<p>A <strong>container</strong> is a running copy of an <strong>image</strong>. What does that mean!!?</p>
<p>In the previous blog, we learned about images and saw that an <strong>image</strong> is like a blueprint/class that has everything needed to run an app. And to make use of the class what do we do in programming!!?? Yes, we make objects out of it.</p>
<p>That's what a container is. A <strong>container</strong> is what you get when you start that image. And you can start <strong>many containers from the same image, as you would with a class and an</strong> <strong>object</strong>.</p>
<p>Let's now see the difference between Containers and VMs, because hey, we had VMs earlier, which solved our problems, right? Then why container?</p>
<h3>Containers vs Virtual Machines (VMs)</h3>
<p>Containers and VMs both run on a host machine (your laptop, a server, or a cloud instance). But they work differently.</p>
<h4>How VMs work</h4>
<p>VMs use a <strong>hypervisor</strong>. You might or might not know about a hypervisor, so let me give you the basic idea about it.</p>
<blockquote>
<h4>Hypervisor</h4>
<p>A hypervisor is software that lets you run multiple virtual machines (VMs) on one physical computer. It creates virtual machines, Gives each VM its own CPU, memory and storage. It keeps VMs isolated from each other and shares the physical hardware safely.</p>
<p>So instead of one computer running one OS, you can run: Window, Linux, Another Linux, etc all on the same machine</p>
<p>So each VM has, its own OS and kernel. This is why we say "Hypervisors virtualize hardware."</p>
</blockquote>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/064e040d-a1af-407b-aaff-1f158975eb3b.png" alt="" style="display:block;margin:0 auto" />

<h4>How do containers work?</h4>
<p>Unlike VMs, containers do not create an OS for every app. It shares the host operating system and also shares its kernel.</p>
<p>What it does is split OS resources into isolated containers, meaning each container has its own filesystem, processes, and network space.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/556498d8-3de0-429b-9836-362495f2e279.png" alt="" style="display:block;margin:0 auto" />

<p>This is why we say, <strong>“Containers virtualize the operating system.”</strong></p>
<p>One downside of containers is that they are less secure by default than VMs, because isolation is not as strong as that of VMs. There are ways to improve security, but they can add complexity.</p>
<hr />
<h2>Running your first container</h2>
<p>Let's now create our first container, and the easiest way to start a container is:</p>
<pre><code class="language-shell">docker container run &lt;image&gt; &lt;app&gt;
</code></pre>
<p>Example:</p>
<pre><code class="language-bash">docker container run -it ubuntu /bin/bash
</code></pre>
<p>What this does:</p>
<ul>
<li><p><code>docker container run</code> creates and starts a new container</p>
</li>
<li><p><code>-it</code> connects your terminal to the container (interactive mode)</p>
</li>
<li><p><code>ubuntu</code> Is the image</p>
</li>
<li><p><code>/bin/bash</code> is the app (a shell) that runs inside the container</p>
</li>
</ul>
<p>Your terminal prompt will change because you are now inside the container.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/d31a7e2b-de25-4884-80f2-18152529fb9b.png" alt="" style="display:block;margin:0 auto" />

<h3>What's happening behind the scenes?</h3>
<p>So when you hit enter after running the above command, the Docker client will send this command to the API server running on the Docker Daemon. Docker Daemon will search for the Docker host's local image repository to see if it already has a copy of the requested image. If it finds the image, it will pull it locally, otherwise, it will go to the Docker hub (image repository) and see if it can find it there.</p>
<p>Once the image is pulled, Docker Daemon tells <code>containerd</code> and <code>runc</code> to create and start the container (remember the Docker architecture we discussed in Part -1). Once its done you will be able to get inside the container.</p>
<p>A container runs <strong>as long as the main app is running</strong>. If the app exists, the container stops. If you stop the container, it stops. If you remove the container, it is gone.</p>
<hr />
<h2>Stopping, starting, and deleting containers</h2>
<p>Common lifecycle commands:</p>
<ul>
<li>Stop a running container:</li>
</ul>
<pre><code class="language-shell">docker container stop &lt;container&gt;
</code></pre>
<ul>
<li>Start a stopped container:</li>
</ul>
<pre><code class="language-bash">docker container start &lt;container&gt;
</code></pre>
<ul>
<li>Remove a container forever:</li>
</ul>
<pre><code class="language-bash">docker container rm &lt;container&gt;
</code></pre>
<hr />
<h2>Working with container processes</h2>
<p>If you are inside a container running <code>/bin/bash</code>, and you kill that main bash process, the container stops.</p>
<p>To exit the container but keep it running:</p>
<ul>
<li>Press: <code>Ctrl-PQ</code></li>
</ul>
<p>Now you are back on your host machine, but the container is still running in the background.</p>
<p>Check running containers:</p>
<pre><code class="language-bash">docker container ls
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/50b699a1-17ed-40d9-bd90-d08997fca346.png" alt="" style="display:block;margin:0 auto" />

<h3>Re-attach using exec</h3>
<p>You can open a new shell inside the running container:</p>
<pre><code class="language-bash">docker container exec -it &lt;container_id&gt; bash
</code></pre>
<p>This creates a <strong>new process</strong> inside the container.</p>
<p>So if you type <code>exit</code> here, the container can still stay alive because the original main process is still running.</p>
<hr />
<h2>Container lifecycle</h2>
<p>You can give a custom name to your container like this</p>
<pre><code class="language-bash">docker container run --name boo -it ubuntu:latest /bin/bash
</code></pre>
<p>After running it, we will be inside the container. Let's create a file here</p>
<pre><code class="language-bash">cd tmp
echo "Hello World" &gt; newfile
ls -l
cat newfile
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/c4876450-d67b-4d02-824e-1437a05f24ed.png" alt="" style="display:block;margin:0 auto" />

<p>Detach without stopping it:</p>
<ul>
<li><code>Ctrl-PQ</code></li>
</ul>
<p>Stop it:</p>
<pre><code class="language-bash">docker container stop boo
</code></pre>
<p>Start it again:</p>
<pre><code class="language-bash">docker container start boo
</code></pre>
<p>Go back inside:</p>
<pre><code class="language-bash">docker container exec -it boo bash
</code></pre>
<p>Check the file:</p>
<pre><code class="language-bash">cd tmp
ls -l
cat newfile
</code></pre>
<p>You will see the file is still there.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/c5a3bad8-3294-4928-a94c-3149c0d42eb3.png" alt="" style="display:block;margin:0 auto" />

<p>So what did we learn from this?</p>
<h3>Two important notes</h3>
<ol>
<li><p>That the data lives on the Docker host. If the host fails, the data is lost.</p>
</li>
<li><p>Containers are meant to be <strong>immutable</strong>, so writing data inside them is not a good habit.</p>
</li>
</ol>
<p>That is why Docker has <strong>volumes</strong>, which store data outside the container. We will look into volumes in the upcoming blog.</p>
<hr />
<h2>Stopping containers gracefully</h2>
<p>There is a big difference between stopping and force-killing.</p>
<ul>
<li><p><code>docker container stop</code> is polite.</p>
<p>It sends a <code>SIGTERM</code> to the main process (PID 1) and gives it time (usually 10 seconds) to shut down cleanly.</p>
<p>If it does not stop in time, Docker sends <code>SIGKILL</code>.</p>
</li>
<li><p><code>docker container rm -f</code> is not polite.</p>
<p>It kills the container right away (no clean shutdown).</p>
</li>
</ul>
<hr />
<h2>Restart policies</h2>
<p>It’s often a good idea to run containers with a restart policy. Why do we care? Because in the real world, we might have some container that crashes or stops suddenly, and we want them to restart automatically. This is where Restart policies come into play.</p>
<p>Restart policies enable Docker to automatically restart them after certain events or failures have occurred.</p>
<p>Common policies are:</p>
<ul>
<li><p><code>always</code> - If we attach this policy to the container, Docker will always restart the stopped container if it's closed by Docker or fails.</p>
</li>
<li><p><code>unless-stopped</code></p>
</li>
<li><p><code>on-failed</code> (often written as <code>on-failure</code>)</p>
</li>
</ul>
<p>Example: Let's create a container with the always policy attached.</p>
<pre><code class="language-bash">docker container run --name zoo -it --restart always ubuntu:lastest bash
</code></pre>
<p>Now If you type <code>exit</code>, and get out of the container, the container stops, but Docker starts it again because the policy is <code>always</code>. Try <code>docker container ls</code> and see.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/907a2692-2f68-4346-86be-eb733dbd375f.png" alt="" style="display:block;margin:0 auto" />

<p>See how the container was created 19 seconds ago, but has only been up for 11 seconds. This is because the <code>exit</code> command killed it and Docker restarted it. Be aware that Docker has restarted the same container and not created a new one.</p>
<p>Difference between <code>always</code> and <code>unless-stopped</code>:</p>
<ul>
<li><p><code>always</code>: restarts even after Docker daemon restarts</p>
</li>
<li><p><code>unless-stopped</code>: does not restart after daemon restart if it was already in a stopped state</p>
</li>
</ul>
<p>Let's see it in action: Create two container with name <code>always</code> and <code>unless-stopped</code> with respective policies attached</p>
<pre><code class="language-shell">docker container run -d --name always \                                                               
--restart always \
ubuntu:latest sleep 1d
</code></pre>
<pre><code class="language-shell">docker container run -d --name unless-stopped \                                                               
--restart always \
ubuntu:latest sleep 1d
</code></pre>
<p>Now check with <code>docker container ls</code></p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/a7b2e1f6-48f5-4988-a3d5-25acb0829563.png" alt="" style="display:block;margin:0 auto" />

<p>Now lets run, and restart the Docker</p>
<pre><code class="language-shell">docker container stop always unless-stopped
</code></pre>
<p>Now run</p>
<pre><code class="language-shell">docker container ls -a
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/ef2ad472-e9da-4598-8dc1-34144678303a.png" alt="" style="display:block;margin:0 auto" />

<p>Notice that the “always” container has been restarted but the "unless-stopped" container has not.</p>
<p>The <code>on-failure</code> policy will restart a container if it exits with a non-zero exit code.</p>
<hr />
<p>Let's clean up everything and remove all the container now.</p>
<pre><code class="language-bash">docker container rm $(docker container ls -aq) -f
</code></pre>
<p>Be careful with this command as this command will remove every container running forcefully.</p>
<hr />
<h2>Quick list of useful container commands</h2>
<ul>
<li><p><code>docker container run</code> — start a new container</p>
</li>
<li><p><code>docker container ls</code> — list running containers</p>
</li>
<li><p><code>docker container ls -a</code> — list all containers (including stopped)</p>
</li>
<li><p><code>docker container exec</code> — run a command inside a running container</p>
</li>
<li><p><code>docker container stop</code> — stop a container</p>
</li>
<li><p><code>docker container start</code> — start a stopped container</p>
</li>
<li><p><code>docker container rm</code> — delete a container</p>
</li>
<li><p><code>docker container inspect</code> — detailed container info</p>
</li>
<li><p><code>Ctrl-PQ</code> — detach without stopping the container</p>
</li>
</ul>
<hr />
<h2>Conclusion</h2>
<p>We saw how container is beneficial and works with all the important commands to start, stop, and remove. Also we learned how restart policies plays an important role in Docker ecosystem.</p>
<p>In the next blog, we will implement everything we learned so far by containerizing the application. Untill then...</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/e80376a7-df56-4571-b2fc-361d914185e9.png" alt="" style="display:block;margin:0 auto" />]]></content:encoded></item><item><title><![CDATA[Diving into Docker (Part 2): Images]]></title><description><![CDATA[In the previous blog...
We learned about some fundamental concepts of Docker and its architecture. We saw why we need Docker in the first place and how it is solving the problems and making things eas]]></description><link>https://dhruvnakum.xyz/diving-into-docker-part-2-images</link><guid isPermaLink="true">https://dhruvnakum.xyz/diving-into-docker-part-2-images</guid><category><![CDATA[Cloud Computing]]></category><category><![CDATA[Docker]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[containers]]></category><category><![CDATA[distributed system]]></category><category><![CDATA[development]]></category><dc:creator><![CDATA[Dhruv Nakum]]></dc:creator><pubDate>Wed, 11 Mar 2026 22:51:45 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/d11bbd88-3e1d-4c32-9fb4-1735528624fd.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>In the previous blog...</h2>
<p>We learned about some fundamental concepts of Docker and its architecture. We saw why we need Docker in the first place and how it is solving the problems and making things easier than the good old VMs.</p>
<p>In this blog, we are going to understand the core part of Docker, Images. We will see the difference between Images and Containers, and also learn about the Image registries.</p>
<p>We will see the commands that help us pull, create, and run the Docker image and much more. So let's get started</p>
<hr />
<h3>What is a Docker image?</h3>
<p>A <strong>Docker image</strong> is a ready-to-use package that has everything an app needs to run, such as:</p>
<ul>
<li><p>The application code</p>
</li>
<li><p>The app’s required libraries</p>
</li>
<li><p>Basic operating system files</p>
</li>
</ul>
<p>If you are a developer, you can think of an image like a <strong>class blueprint</strong>. You use it to create real running things.</p>
<p>Docker images are mainly used at <strong>build time</strong>, while <strong>containers</strong> are what you use at <strong>run time</strong> when the app is actually running.</p>
<hr />
<h3>Where do Docker images come from?</h3>
<p>Most of the time, you do not create images from scratch. You <strong>pull</strong> them from a place called an <strong>image registry,</strong> and the most common registry is <strong>Docker Hub.</strong></p>
<p>So when we pull an image from the Docker Hub, the Docker daemon downloads it to your computer (your Docker host). After that, Docker can use it to start one or more containers</p>
<hr />
<h3>Images are made of layers</h3>
<p>A Docker image is not one single file. It is built from many <strong>read-only layers</strong> stacked on top of each other.</p>
<p>Inside these layers, we have a small cut-down operating system and all the required files and dependencies for the app. Which means you don't get the full system with all the things.</p>
<p>Docker stacks the layers and shows them as one unified image.</p>
<p>When you pull an image like this:</p>
<pre><code class="language-bash">docker image pull ubuntu:latest
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/ae6db394-8079-484e-91dc-a798c7375048.png" alt="" style="display:block;margin:0 auto" />

<p>Each line that ends with <strong>“Pull complete”</strong> is basically one layer being downloaded.</p>
<p>You can also inspect layers using:</p>
<pre><code class="language-bash">docker image inspect ubuntu:latest
</code></pre>
<p>The idea here is, every image starts with a <strong>base layer,</strong> and when we add things (like installing Python or adding your app code), Docker adds new layers on top. For example, we have this Ubuntu image. And then we add Python to it, and then we add source code.</p>
<p>Now, the final image becomes a stack of layers in that order.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/97f2d1e4-85a2-4b94-a9d0-e5c81f01710d.png" alt="Image from Docker Deep Dive Zero to Docker in a single book - Nigel Poulton" style="display:block;margin:0 auto" />

<p>(Source: Docker Deep Dive - Nigel Poulton)</p>
<p>Docker also has a system called a storage driver that handles stacking these layers. There are many drivers, but the experience feels the same to you.</p>
<hr />
<h3>Images and containers: how they connect</h3>
<p>We will learn about containers in the next blog, but for now, just understand that you can start containers from an image using commands like, <code>docker container run</code> and <code>docker service create.</code> Once a container is created from an image, the image and container are linked, and we <strong>cannot delete the image</strong> until all containers using it are stopped and deleted. If you try, Docker will give an error.</p>
<p>Also, the purpose of a container is usually to run <strong>one app or one service</strong>, so the image should only include what that app needs.</p>
<p>For example:</p>
<ul>
<li><p>If the app does not need a shell, the image should not include many shells</p>
</li>
<li><p>Docker images also do not include a kernel. All containers on a machine share the <strong>host machine’s kernel</strong></p>
</li>
</ul>
<hr />
<h3>Image registries and repositories</h3>
<ul>
<li><p>An <strong>image registry</strong> is a central place to store images (for example, Docker Hub).</p>
</li>
<li><p>Image registries contain one or more image repositories. In turn, image repositories contain one or more images.</p>
</li>
</ul>
<p>Docker Hub has two types of repositories:</p>
<ul>
<li><p><strong>Official repositories</strong></p>
<ul>
<li>Checked and curated by Docker</li>
</ul>
</li>
<li><p><strong>Unofficial repositories</strong></p>
<ul>
<li><p>Can be risky</p>
</li>
<li><p>Do not assume they are safe, well-documented, or built correctly</p>
</li>
</ul>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/2faceb45-e17b-4626-8ecd-6383fcd240d1.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3>Image names and tags (how you pull the right one)</h3>
<p>For official images, pulling is usually:</p>
<pre><code class="language-bash">docker image pull &lt;repository&gt;:&lt;tag&gt;
</code></pre>
<p>Examples:</p>
<pre><code class="language-bash">docker image pull alpine:latest
docker image pull redis:latest
docker image pull mongo:4.2.6
docker image pull busybox:latest
</code></pre>
<p>If you run:</p>
<pre><code class="language-bash">docker image pull alpine
</code></pre>
<p>Docker assumes you mean:</p>
<ul>
<li><code>alpine:latest</code></li>
</ul>
<h4>Two important notes about <code>latest</code></h4>
<p>If you do not specify a tag, Docker assumes <code>latest</code>. And If the repo does not have a <code>latest</code> tag, the pull will fail.</p>
<p><code>latest</code> does not mean “newest”. It is just a label. For example, in Alpine, the newest is often tagged <code>edge</code>. So be careful when using <code>latest</code>.</p>
<hr />
<h3>Pulling images from unofficial repos</h3>
<p>Same idea, but you include the username or org name:</p>
<pre><code class="language-bash">docker image pull someusername/imagename:version
</code></pre>
<hr />
<h3>Pulling from other registries (not Docker Hub)</h3>
<p>If the image is in another registry, like there are google registries too, so to get that we include the registry’s DNS name:</p>
<pre><code class="language-bash">docker image pull gcr.io/google-containers/git-sync:v3.1.5
</code></pre>
<hr />
<h3>Searching Docker Hub from the command line</h3>
<p>You can search Docker Hub using:</p>
<pre><code class="language-bash">docker search alpine
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/2a7660c8-c658-47d7-ac7f-b7759bd77982.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><p>It searches for repos that match a string in the <strong>NAME</strong> field</p>
</li>
<li><p><strong>NAME</strong> is the repository name</p>
</li>
</ul>
<p>To show only official repos:</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/547878ea-422e-43d2-9bc8-e250949e56fe.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-bash">docker search alpine --filter "is-official=true"
</code></pre>
<hr />
<h3>Pulling images by digest</h3>
<p>Pulling by tag is common, but there is a problem: <strong>Tags can change.</strong> Someone can push a new version using the same tag. Then you might not know which exact version your running systems are using</p>
<p>For example, you have <code>exampleimage:1.5</code> with a known bug. You fix it and push it again as <code>exampleimage:1.5</code>Now two different images have used the same tag name over time. It becomes hard to know what is running where</p>
<p>Docker uses a content-based ID called a <strong>digest,</strong> which is a cryptographic hash.</p>
<p>The idea here is that if the image content changes, the digest changes. So digests are <strong>unchangeable</strong>, and they uniquely identify the exact image</p>
<p>When you pull an image, Docker often shows the digest:</p>
<pre><code class="language-bash">docker image pull alpine
</code></pre>
<p>You can list digests locally:</p>
<pre><code class="language-bash">docker image ls --digests ubuntu
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/c9c094f9-7b03-4b52-8c67-0711faabe3fd.png" alt="" style="display:block;margin:0 auto" />

<p>And you can pull the exact same image again using the digest:</p>
<pre><code class="language-bash">docker image pull ubuntu@sha256:d1e2e92...7e495eff4f9
</code></pre>
<p>This makes sure you get <strong>exactly</strong> the image you expect.</p>
<hr />
<h3>Multi-architecture images</h3>
<p>Different machines can have different Operating systems (Linux, Windows), different CPU types (x64/amd64, ARM, etc.).</p>
<p>For example, your laptop might be <strong>Linux on x64,</strong> A Raspberry Pi is <strong>Linux on ARM,</strong> A Windows server might be <strong>Windows on x64</strong></p>
<p>Before multi-architecture support, you had to <strong>manually pick the right image</strong>, which was confusing.</p>
<p>So, when you run</p>
<pre><code class="language-bash">docker pull golang:latest
</code></pre>
<p><strong>Docker automatically detects your OS and CPU</strong>, pulls the correct version for your system. You don’t need to specify anything.</p>
<h4>How does Docker do this?</h4>
<p>Docker uses two important things in the registry:</p>
<ol>
<li><p><strong>Manifest List:</strong> This is nothing but a list of supported platforms for that image tag</p>
<ul>
<li>Example: Linux/amd64, Linux/arm, Windows/amd64</li>
</ul>
</li>
<li><p><strong>Manifests:</strong> Each platform has its own Manifest. And that Manifest lists the layers and configuration for that platform</p>
</li>
</ol>
<p>So the Flow is:</p>
<pre><code class="language-bash">docker pull golang:latest
        |
Docker checks manifest list
        |
Finds match for your OS + CPU
        |
Downloads correct layers
</code></pre>
<p>Docker also lets you build images for other CPU types using <code>buildx</code>.</p>
<p>Example:</p>
<pre><code class="language-bash">docker buildx build --platform linux/arm/v7 -t myimage:arm-v7 .
</code></pre>
<p>You can even build ARM images while working on an x64 machine. How cool is that!!</p>
<hr />
<h3>Deleting Docker images</h3>
<p>To delete an image:</p>
<pre><code class="language-bash">docker image rm 02674b9cb179
</code></pre>
<p>Delete multiple images:</p>
<pre><code class="language-bash">docker image rm f70734b6a266 a4d3716dbb72
</code></pre>
<p><strong>Important rules:</strong></p>
<ul>
<li><p>Deleting an image removes the image and its layers.</p>
</li>
<li><p>But if a layer is shared by multiple images, it will not be removed until all those images are deleted.</p>
</li>
<li><p>If an image is used by a running container, Docker will not let you delete it</p>
<ul>
<li>Stop and delete the container first</li>
</ul>
</li>
</ul>
<p>Shortcut to delete all images (force):</p>
<pre><code class="language-bash">docker image rm $(docker image ls -q) -f

# Here $(docker image ls -q) will list all the images. Its a shortcut.
</code></pre>
<hr />
<h2>Conclusion</h2>
<p>This is more than enough for you to understand everything about Docker images right now. In the next blog, we will dive into Containers and see what a container is and how it actually works under the hood. Until then...</p>
<p><a class="embed-card" href="https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExbjFtZG8yY3V6azR5ejRoY2p4eXltcWdzcDVrMXVhcjlzdnVrcG5zayZlcD12MV9naWZzX3NlYXJjaCZjdD1n/2nlbKhgnvAK3sR8ffw/giphy.gif">https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExbjFtZG8yY3V6azR5ejRoY2p4eXltcWdzcDVrMXVhcjlzdnVrcG5zayZlcD12MV9naWZzX3NlYXJjaCZjdD1n/2nlbKhgnvAK3sR8ffw/giphy.gif</a></p>]]></content:encoded></item><item><title><![CDATA[Diving Into Docker (Part 1): The Big Picture]]></title><description><![CDATA[Introduction
So, I've been reading this book called Docker Deep Dive Zero to Docker by Nigel Poulton. Why? Because I've been working on mobile, web, and cloud applications for quite a long time now. A]]></description><link>https://dhruvnakum.xyz/diving-into-docker-part-1</link><guid isPermaLink="true">https://dhruvnakum.xyz/diving-into-docker-part-1</guid><category><![CDATA[Docker]]></category><category><![CDATA[containers]]></category><category><![CDATA[virtual machine]]></category><category><![CDATA[Cloud Computing]]></category><category><![CDATA[AWS]]></category><category><![CDATA[Orchestration]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Developer]]></category><dc:creator><![CDATA[Dhruv Nakum]]></dc:creator><pubDate>Wed, 11 Mar 2026 02:08:59 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/162e365e-d62f-4154-9d59-9e38afab46f3.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Introduction</h2>
<p>So, I've been reading this book called <strong>Docker Deep Dive Zero to Docker</strong> by <strong>Nigel Poulton</strong>. Why? Because I've been working on mobile, web, and cloud applications for quite a long time now. And I really wanted to know how Docker works internally and how it does the magic.</p>
<p>And then I came across this book and thought to give it a try because I love reading, and to be honest, I hardly read technical books to learn about something personally (well, except college and study books, of course lol). So while reading it, I was also making notes for myself.</p>
<p>I will be sharing everything I learned from this book. I will be using the book to explain and rewrite things, making things simpler to understand, because some of the things later on in this book took a while for me to understand. I will also be using Nano Banana to create visuals to help you better understand because I don't want to waste time on graphics too much. And obiously AI does better job than me now in designing these things ;)</p>
<p>So, let's get started without further ado.</p>
<hr />
<h2>The Big Picture</h2>
<p>A few years ago, running apps looked very different. Most companies ran <strong>one application per server</strong>. If the business needed a new app, the IT team often had to buy a new server. This cost a lot of money and wasted resources, because many servers had unused CPU and RAM.</p>
<p>Then <strong>virtual machines (VMs)</strong> became popular.</p>
<h3>VMs Solved One Problem, But Created Another</h3>
<p>VMs made it possible to run <strong>multiple applications on one server</strong>. That was a big improvement. But there was a downside too, which were</p>
<ul>
<li><p>Each VM needs its <strong>own full operating system.</strong></p>
</li>
<li><p>That OS uses <strong>CPU and RAM</strong> even when the app is small.</p>
</li>
<li><p>Sometimes each OS also <strong>needs its own license.</strong></p>
</li>
<li><p>VMs can be <strong>slow to boot.</strong></p>
</li>
<li><p><strong>Moving VMs across machines</strong> is not always smooth.</p>
</li>
</ul>
<p>So even though VMs helped, they still had a lot of overhead.</p>
<h3>Containers: A Lighter Way To Run Apps</h3>
<p>To solve the above problem, <strong>containers</strong> come into the picture. What is a container? For now, just think that a container is similar to a VM in one way. It runs an application in an isolated environment.</p>
<p>But the key difference is that <strong>Containers share the host machine’s kernel.</strong> They <strong>do not need a full OS per container</strong></p>
<p>And because of this, containers are Faster to start, more lightweight, and Easier to move around, which was the problem in VMs, remember?</p>
<p>Google has used container tech for a long time. But for many companies, containers were still too complex to use directly. To address this complexity and make things easier for everyone, Docker Inc. began developing Docker.</p>
<hr />
<h2>What Does “Docker” Mean?</h2>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/5707cdaa-ddaa-429a-829b-3b19a82abdc0.png" alt="" style="display:block;margin:0 auto" />

<p>When people say “Docker”, they might mean two things:</p>
<ol>
<li><p><strong>Docker, Inc.</strong> - the company</p>
</li>
<li><p><strong>Docker, the technology</strong> - the tool that creates and runs containers</p>
</li>
</ol>
<p><strong>Docker</strong>, the technology runs on Linux and Windows, and helps you build, run, and manage containers.</p>
<hr />
<h2>Docker Architecture</h2>
<p>There are three things that we need to be aware of when we talk about Docker.</p>
<ol>
<li><p><strong>Docker Runtime</strong></p>
</li>
<li><p><strong>Docker Daemon</strong></p>
</li>
<li><p><strong>Docker Orchestrator</strong></p>
</li>
</ol>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/80fe5be2-c993-4296-a44e-cab1d3e308b6.png" alt="" style="display:block;margin:0 auto" />

<p>Let's see each one of them in detail now.</p>
<h3>Docker Runtime</h3>
<p>Docker uses a “tiered runtime” setup. <strong>Low-level</strong> and <strong>High-level runtime.</strong></p>
<blockquote>
<p>When I say <strong>Docker uses a tiered runtime architecture</strong>, I mean that Docker splits responsibilities between different components instead of having one big program do everything.</p>
</blockquote>
<h4>Low-level runtime: <code>runc</code></h4>
<ul>
<li>This talks to the operating system. Starts and stops containers. Each container typically has a <code>runc</code> instance managing it.</li>
</ul>
<h4>High-level runtime: <code>containerd</code></h4>
<ul>
<li>This manages the whole container lifecycle. It pulls images. Sets up networking and calls <code>runc</code> when needed</li>
</ul>
<p>You do not need to memorize this on day one, but it helps to know Docker has layers.</p>
<h3>Docker Daemon</h3>
<p>Docker Daemon sits above <code>containerd</code> and performs higher-level tasks, such as exposing the Docker remote API, managing images, volumes, and networks, and more.</p>
<p>The Docker daemon’s main job is to provide an easy-to-use standard interface that abstracts the lower levels.</p>
<h3>Docker Orchestrator</h3>
<p>Before we understand this layer, we need to understand what orchestration means.</p>
<p>You see, running one container is easy. But in real apps, you often need many containers, like web apps, databases, caches, and background workers.</p>
<p><strong>Orchestration</strong> means managing all of those automatically. If you have seen any performance in an orchestra where the orchestrator manages all the musicians, the same concept applies to the Docker too.</p>
<ul>
<li>The orchestrator, Start the containers, restart if they crash, scale up when traffic increases, and connect them properly</li>
</ul>
<p>Docker has its own orchestrator called <strong>Docker Swarm</strong>, but today most teams use <strong>Kubernetes</strong>.</p>
<h4>Kubernetes in one line (because we are not going into the details right now)</h4>
<p>Kubernetes is the most popular platform for deploying and managing containerized apps.</p>
<hr />
<h2>OCI: Why Standards Matter</h2>
<p>There is also something called the <strong>Open Container Initiative (OCI)</strong>. In simple terms, it's nothing but a government council that sets standards for the image and runtime formats.</p>
<p>An analogy often used to describe these two standards is <strong>rail trails.</strong></p>
<p>This is useful, and it’s fair to say that the two <strong>OCI</strong> specifications have had a major impact on the architecture and design of the core Docker product.</p>
<hr />
<h2>Installing Docker</h2>
<p>Docker is available everywhere to download. There are Windows, Mac, and Linux applications. You can install it in the cloud, on premises, and on your laptop. And there are manual, scripted, and wizard-based installs, too. There literally are loads of ways and places to install Docker.</p>
<h2>Docker Desktop</h2>
<p>This is the main application we use for everything. Go to <a href="https://www.docker.com/products/docker-desktop/">Docker Desktop</a> website and download it on your respective operating system. Once installed, you will see the interface something like this:</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/8ce1e7c8-52b8-407e-bfa7-793e135fc82b.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h2>Docker’s Main Parts</h2>
<p>So when you install Docker, you mainly work with two pieces: <strong>Docker Client</strong> and <strong>Docker Daemon</strong></p>
<h3>1) Docker Client</h3>
<p>This is what you interact with, usually from the terminal.</p>
<p>Open your terminal and run the command below. You will see the current Docker version installed in your system.</p>
<p>Example:</p>
<pre><code class="language-plaintext">docker version
</code></pre>
<h3>2) Docker Daemon</h3>
<p>This is the background service that does the real work:</p>
<ul>
<li><p>pulls images</p>
</li>
<li><p>runs containers</p>
</li>
<li><p>manages networks and volumes</p>
</li>
<li><p>exposes the Docker API</p>
</li>
</ul>
<p>The <strong>client talks to the daemon</strong>. This is the high-level explanation. Now, let's get into the details</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/6c78378b-0ea8-42c9-8eed-0bb83f2c8fbe.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h2>The Two Core Docker Objects: Images and Containers</h2>
<h3>Docker Image</h3>
<p>A Docker image is like a package that contains:</p>
<ul>
<li>a small filesystem like Linux files, or it can be your app, or the dependencies needed to run the app</li>
</ul>
<p>The important thing here is that it's an image, not a running instance. It is more like a <strong>blueprint.</strong> You can think of a Docker Image as a <strong>Class</strong> in the world of programming. And when you create an Object of that Class, it becomes a real thing and gets memory allocated in the system. In this case, that object is Container.</p>
<p>Getting an image onto your Docker host is called <strong>pulling.</strong> So whenever you want to get the image, you run the command below,</p>
<pre><code class="language-plaintext">docker image pull ubuntu:latest
</code></pre>
<p>And from where this image will come from, you might ask? We will talk about it in the upcoming article, don't worry. For now, just know that it comes somewhere from the internet. And so, after pulling the image, you will see something like this.</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/320f94eb-7dec-4050-974b-962f29254743.png" alt="" style="display:block;margin:0 auto" />

<p>To check whether you successfully installed that image, there are two ways: First, to run the command below in your terminal</p>
<pre><code class="language-plaintext">docker image ls
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/3b729d3c-0d4c-4933-bf91-6cf80cf0e352.png" alt="" style="display:block;margin:0 auto" />

<p>Each image gets its own unique ID. When referencing images, you can refer to them using either IDs or names</p>
<p>And the second way is to check the Docker Desktop</p>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/62e2ee1f-8f86-48c2-8c10-96582d46033b.png" alt="" style="display:block;margin:0 auto" />

<h3>Docker Container</h3>
<p>A container is a <strong>running instance</strong> created from an image.</p>
<p>So:</p>
<ul>
<li><p><strong>Image = blueprint</strong></p>
</li>
<li><p><strong>Container = running app</strong></p>
</li>
</ul>
<p>Now that we have an image pulled locally, we can use the Docker container run command to launch a container from it.</p>
<pre><code class="language-plaintext">docker container run -it ubuntu:latest /bin/bash
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/828ca207-3728-4004-a853-fabe2db9cc46.png" alt="" style="display:block;margin:0 auto" />

<blockquote>
<p>Let's understand the command here first:</p>
<p><code>docker container run</code> tells the Docker daemon to start a new container.</p>
<p><code>-it</code> flag tells Docker to make the container interactive and to attach the current shell to the container’s terminal.</p>
<p><code>ubuntu:latest</code> command tells Docker that we want the container to be based on the ubuntu:latest image.</p>
<p><code>/bin/bash</code> tell Docker which process we want to run inside of the container. For linux we are running a Bash shell.</p>
</blockquote>
<p>As you can see, after running the command, we got inside the Ubuntu container. And it's a real Ubuntu image. So you can run the command you run on Ubuntu. But not all of them right now. Why? We will come to it later, don't worry.</p>
<blockquote>
<p>You can exit the container without terminating it by Ctr - PQ command</p>
</blockquote>
<p>If you want to see all the containers, you can run</p>
<pre><code class="language-plaintext">docker container ls
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/67e33eb7-4769-44b5-8428-f57659ef8534.png" alt="" style="display:block;margin:0 auto" />

<p>Now, if you have exited the container, you can again get back into it using the <code>exec</code> command</p>
<pre><code class="language-plaintext">docker container exec -it 0acc2d5f2fef bash
</code></pre>
<p>We used the <code>-it</code> options to attach our shell to the container’s shell</p>
<p>We can stop and kill the container using the commands below</p>
<pre><code class="language-plaintext">docker container stop &lt;name/id&gt;
</code></pre>
<pre><code class="language-plaintext">docker container rm &lt;name/id&gt;
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/60d634b1de92ef110afdcce9/6b227cad-22cc-4fad-af07-b1e0f72bea7f.png" alt="" style="display:block;margin:0 auto" />

<p><strong><mark class="bg-yellow-200 dark:bg-yellow-500/30">IMPORTANT NOTE:</mark></strong></p>
<ul>
<li>If you are like this right now...</li>
</ul>
<p><a class="embed-card" href="https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExcjE1MmVtZ3o4bXF1NzJ2aXMzZ3prYmd1dWU3NnY3ZmZ5ZDcwZ3pjbiZlcD12MV9naWZzX3NlYXJjaCZjdD1n/GPg6PuL5RkeyVCD5wk/giphy.gif">https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExcjE1MmVtZ3o4bXF1NzJ2aXMzZ3prYmd1dWU3NnY3ZmZ5ZDcwZ3pjbiZlcD12MV9naWZzX3NlYXJjaCZjdD1n/GPg6PuL5RkeyVCD5wk/giphy.gif</a></p>
*   Then I would like to say that we just went through the fundamental pieces of Docker in this article. If you didn't understand anything yet, then don't worry. I totally get you. It's hard to grasp all of these at first. But it was necessary for me to give you the big picture about it. So that, when we discuss it in detail, you will know something about it and not feel scared.
    

<hr />
<h2>Conclusion</h2>
<p>We learned about VMs and saw why it's not efficient to use them anymore. We saw how containers can be so much lighter and faster than VMs.</p>
<p>We also saw what Docker is and how it works using its architecture. We learned about OCI and how it helps maintain the standard practice of images and containers.</p>
<p>We also went through some basic commands of Docker, like:</p>
<table>
<thead>
<tr>
<th><code>docker image pull &lt;image-name&gt;</code></th>
<th>to pull an image</th>
</tr>
</thead>
<tbody><tr>
<td><code>docker image ls</code></td>
<td>to list all the installed images</td>
</tr>
<tr>
<td><code>docker container run -it &lt;image-name&gt; &lt;app-name&gt;</code></td>
<td>To start and run the container</td>
</tr>
<tr>
<td><code>docker container ls</code></td>
<td>To list all the installed containers</td>
</tr>
<tr>
<td><code>docker container exec -it &lt;container_name&gt; bash</code></td>
<td>To attach the terminal to the running container's terminal</td>
</tr>
<tr>
<td><code>docker container stop &lt;nameid&gt;</code></td>
<td>To stop the running container</td>
</tr>
<tr>
<td><code>docker container rm &lt;nameid&gt;</code></td>
<td>To remove/kill the container</td>
</tr>
</tbody></table>
<p>This big picture view should help you with the upcoming article, where we will dig deeper into images and containers.</p>
<p>See you in the next article, until then....</p>
<p><a class="embed-card" href="https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExbjFtZG8yY3V6azR5ejRoY2p4eXltcWdzcDVrMXVhcjlzdnVrcG5zayZlcD12MV9naWZzX3NlYXJjaCZjdD1n/2nlbKhgnvAK3sR8ffw/giphy.gif">https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExbjFtZG8yY3V6azR5ejRoY2p4eXltcWdzcDVrMXVhcjlzdnVrcG5zayZlcD12MV9naWZzX3NlYXJjaCZjdD1n/2nlbKhgnvAK3sR8ffw/giphy.gif</a></p>]]></content:encoded></item><item><title><![CDATA[Ship It Like a Pro: Node.js on EC2 with Caddy & systemd]]></title><description><![CDATA[Introduction

I've been learning a lot about AWS lately, and to be honest, there's a lot to understand and learn. I decided to take a little break and try to implement what I've learned so far. At the same time, I'm working on a project that's still ...]]></description><link>https://dhruvnakum.xyz/ship-it-like-a-pro-nodejs-on-ec2-with-caddy-and-systemd</link><guid isPermaLink="true">https://dhruvnakum.xyz/ship-it-like-a-pro-nodejs-on-ec2-with-caddy-and-systemd</guid><category><![CDATA[AWS]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[deployment]]></category><category><![CDATA[Cloud Computing]]></category><category><![CDATA[ec2]]></category><category><![CDATA[Amazon Web Services]]></category><category><![CDATA[Ubuntu 22.04]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Developer]]></category><dc:creator><![CDATA[Dhruv Nakum]]></dc:creator><pubDate>Mon, 10 Nov 2025 04:03:43 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1762747315634/05e6012f-75a4-4cb8-859b-02276fbd9a0c.gif" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-introduction">Introduction</h1>
<ul>
<li><p>I've been learning a lot about AWS lately, and to be honest, there's a lot to understand and learn. I decided to take a little break and try to implement what I've learned so far. At the same time, I'm working on a project that's still in progress, and I'll be announcing it soon. I have a backend app in Node.js for this project and wanted to deploy it. So, I thought, why not deploy it on AWS to test my knowledge and see if I've learned anything?</p>
</li>
<li><p>A little bit about my Node.js app: I'm using JavaScript, and I have MongoDB running. It's on Atlas in the cloud. I think that's all you need to know. If you have a Node.js application running, you can follow similar steps to make your backend accessible to your frontend application or any third-party consumer.</p>
</li>
<li><p>So, without further ado, let's get started.</p>
</li>
</ul>
<hr />
<h1 id="heading-set-up-an-aws-account">Set up an AWS Account</h1>
<ul>
<li><p>First, we need an AWS account, so I'm assuming you already have one. If not, it's really easy to create one. Go to <a target="_blank" href="https://aws.amazon.com">https://aws.amazon.com</a> and create an account.</p>
</li>
<li><p>It might ask for your credit card information, but don't worry, they won't charge you until you exceed the free-tier limit. In this guide, we won't exceed the free tier, so there's no need to worry. Just add your details, and you're all set.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762472741823/3c3cfdd2-3058-49c7-833c-9de1e99fd12b.png" alt class="image--center mx-auto" /></p>
<hr />
<h1 id="heading-aws-ec2-instance">AWS EC2 Instance</h1>
<ul>
<li><p>So, we are going to use the AWS EC2 service to deploy our application to AWS. If you're not familiar with EC2, think of it as renting a virtual machine, similar to your laptop or PC. There are many benefits, such as storing data, choosing your operating system, deciding how much computing power you need, selecting the number of CPU cores, RAM, and the type of network you want to use, and more.</p>
</li>
<li><p>You can customize your virtual machine as you like and rent it from AWS, which is the power of the cloud.</p>
</li>
<li><p>What we'll do is copy our local app, with all its files and folders, directly to this virtual machine and then run the app there. The advantage is that anyone with the link can access it, not just us on our local machine.</p>
</li>
</ul>
<hr />
<h1 id="heading-creating-ec2-instance">Creating EC2 Instance</h1>
<ul>
<li>Simply search for EC2 in the search bar and click on it to open the service page.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762538757575/d7139c10-6121-476e-8c2e-2b815254a19f.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>Once it's open, you will see a page like this.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762538803979/d9858efc-b7ed-4b14-9b26-11b259f42605.png" alt class="image--center mx-auto" /></p>
<ul>
<li>Simply click on a Launch Instance on Dashboard and give it a name.</li>
</ul>
</li>
</ul>
<p>    <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762538890516/d043f510-ab55-4054-96e9-180bf2771294.png" alt class="image--center mx-auto" /></p>
<ul>
<li>It will also ask you to select the operating system image. Choose Ubuntu and scroll down.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762538972299/36e35659-2e56-4fad-ab3a-c8bed8154c77.png" alt class="image--center mx-auto" /></p>
<ul>
<li>We will use the <code>t2.micro</code> instance because it is part of the free tier. If you need a higher configuration CPU, you can check other instances and select the one that meets your needs.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762539062261/6906a284-e72e-4ef7-b92a-a85da7e982e2.png" alt class="image--center mx-auto" /></p>
<ul>
<li>To connect to the instance from our local machine or anywhere else, we need access. To do this, we generate a key pair, which we can use to connect to our AWS instance and launch it in our local environment.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762539188902/2daf408e-9be9-4ded-a7a1-3f6733c94449.png" alt class="image--center mx-auto" /></p>
<ul>
<li>Click on "Generate Key Pair," give it a relevant name, and make sure to select the RSA key pair type. Finally, create the key pair, and it will download to your local machine.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762539363601/a6b2c185-9b9d-4fe5-b726-466e2e3a1ac0.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>In the network settings, select:</p>
<ul>
<li><p>Allow SSH traffic from - <strong>My IP -</strong> Because we only want to access the instance from our local machine.</p>
</li>
<li><p>Allow HTTPS traffic from the internet (so our application can access our Node.js server using an endpoint or URL with HTTPS)</p>
</li>
<li><p>Allow HTTP traffic from the internet (so our application can access our Node.js server using an endpoint or URL with HTTP)</p>
</li>
</ul>
</li>
<li><p>That's it. Now, review the summary and launch the instance.</p>
</li>
</ul>
<hr />
<h1 id="heading-connecting-via-ssh">Connecting via SSH</h1>
<ul>
<li>Once it’s created, click on <code>Connect to your instance</code></li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762539556290/34f5e2ae-0e62-4512-994b-0f5b21d3845f.png" alt class="image--center mx-auto" /></p>
<ul>
<li>This will redirect you to the interface shown below. Here, you can run the instance within AWS Cloud itself and access it, or you can use the SSH Client to access this instance from your local machine, which is what we want.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762539589729/85b67bc9-fed8-45df-9603-5ac2026b1283.png" alt class="image--center mx-auto" /></p>
<ul>
<li>Before setting up SSH, remember that we downloaded the <code>Key-Pair</code>. Let's first place it in the correct location.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762539669936/624e096c-440f-4ddd-8507-ddc669744599.png" alt class="image--center mx-auto" /></p>
<ul>
<li>I am using a Mac, and I usually store my SSH keys inside the <code>.ssh</code> folder located in the home directory. Let's first navigate to the home directory.</li>
</ul>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> ~/
</code></pre>
<ul>
<li>Now, move the key pair from the download folder to the .ssh folder using the <code>mv</code> command below:</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762539917008/9ca8ba85-d506-4bf8-8203-e4a364988814.png" alt class="image--center mx-auto" /></p>
<ul>
<li>Once it’s done, we are good to go. So let’s follow these instructions.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762539953498/725997fd-ae22-49a8-bc4d-2a31daad1ee4.png" alt class="image--center mx-auto" /></p>
<ul>
<li>Run <code>chmod</code> A command to change the permission to ensure your key is not publicly viewable.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762540006041/71c8f652-afd3-43cd-8b38-7a02ef3672d1.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>Now run</p>
</li>
<li><pre><code class="lang-bash">  ssh -i <span class="hljs-string">"&lt;your-key-name&gt;.pem"</span> ubuntu@&lt;your-ip&gt;
</code></pre>
<p>  to connect to the instance we created.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762540063323/36c2dc8b-32e7-46f7-a3cc-f3f7a3cabf33.png" alt class="image--center mx-auto" /></p>
<ul>
<li>Type <code>yes</code> and press Enter</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762540144370/f34169c9-4779-4188-af03-3e090fc2da09.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>As you can see, we are now inside the instance we created. Isn't it cool that we can access it from our local machine?</p>
</li>
<li><p>Now you can perform any tasks that you would normally do on your local machine.</p>
</li>
</ul>
<hr />
<h1 id="heading-installingupdating-packages">Installing/Updating Packages</h1>
<ul>
<li>Now that we have our instance connected, before starting anything, we need to ensure we are using the latest packages in Ubuntu and install any available updates. To do this, we run two commands:</li>
</ul>
<pre><code class="lang-bash">sudo apt update
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762569629578/dc523adf-19a2-483e-bf1f-907d6175a2b0.png" alt class="image--center mx-auto" /></p>
<ul>
<li>Which refreshes package lists (checks for updates) and then</li>
</ul>
<pre><code class="lang-bash">sudo apt upgrade
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762569742182/8fb1e954-8458-4f56-a118-dc81c726a5fe.png" alt class="image--center mx-auto" /></p>
<ul>
<li>Which Installs available updates for installed packages</li>
</ul>
<hr />
<h1 id="heading-installing-nodejs-in-an-ec2-instance">Installing Node.js in an EC2 instance</h1>
<ul>
<li><p>Now that we have all the latest packages, we need to install Node.js (or the backend framework/library you need).</p>
</li>
<li><p>To do this, we need to download and run the NodeSource setup script, which configures the system to install Node.js version 20 from their official repository.</p>
</li>
<li><p>Use the command below to do that:</p>
</li>
</ul>
<pre><code class="lang-bash">curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762569958288/abfe6fae-3158-4145-94fc-1e36dc2897c6.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>Once the NodeSource repository is added, we need to install <strong>Node.js</strong> and <strong>npm</strong> (Node.js’s package manager) on our system to use Node.js.</p>
</li>
<li><p>To do this, run the command below</p>
</li>
</ul>
<pre><code class="lang-bash">sudo apt-get install -y nodejs
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762570037013/107a72fd-b2ee-43f0-abd2-9efd75fc4918.png" alt class="image--center mx-auto" /></p>
<hr />
<h1 id="heading-pushing-local-app-to-ec2">Pushing Local App to EC2</h1>
<ul>
<li>Now that we have the Node environment set up, we can upload our local Node.js app to this instance. We can do this using the SSH connection we already set up.</li>
</ul>
<pre><code class="lang-bash">rsync -avz --exclude <span class="hljs-string">'node_modules'</span> --exclude <span class="hljs-string">'.git'</span> --exclude <span class="hljs-string">'.env'</span> \
  -e <span class="hljs-string">"ssh -i ~/.ssh/your-key.pem"</span> \
  . ubuntu@ip-address:~/app
</code></pre>
<ul>
<li>In simple terms, this command copies the current folder (<code>.</code>) to the remote server’s <code>~/app</code> directory using SSH. It skips unnecessary files like <code>node_modules</code>, <code>.git</code>, and <code>.env</code> because we don't need those.</li>
</ul>
<blockquote>
<p>NOTE: Be sure to replace ‘your-key’ with your SSH key name in the command above, and replace ‘ip-address’ with your actual IP address.</p>
</blockquote>
<ul>
<li>To execute this command, go to your code directory and run the command above.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762570682270/4981a055-758d-4652-b6ca-e7bbe66ee73a.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>If you now run the <code>ls</code> command in your instance's home directory, you will see the <code>app</code> folder has been created.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762570770769/d4ca1d29-2c81-4c8d-a98b-37ee11e59e7e.png" alt class="image--center mx-auto" /></p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762570840301/a9b579be-75a0-41eb-b976-2a61a548d372.png" alt class="image--center mx-auto" /></p>
<p>  And if you use <code>cd</code> to enter it, you will see all your source code.</p>
</li>
</ul>
<hr />
<h1 id="heading-setting-up-environment-env-file">Setting up Environment (<code>.env</code>) File.</h1>
<ul>
<li><p>If you have a database running locally, like PostgreSQL or MySQL, you can install those on this instance just like we did for Node.js.</p>
</li>
<li><p>I have MongoDB running on MongoDB Atlas. So, to allow this instance to access the database, we need to add the instance's IP to the whitelist. Simply copy the instance's IP address and paste it into the IP Access List entry.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762571242267/e0e541c6-c091-40e3-a94e-459a4334adc9.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>Now that everything is set up, we need one more file: the <code>.env</code> file. Remember, we excluded it when we transferred our files to the instance.</p>
</li>
<li><p>So let’s create the <code>.env</code>file</p>
</li>
</ul>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> app/
sudo vim .env <span class="hljs-comment"># This will create and open .env file</span>
</code></pre>
<pre><code class="lang-bash"><span class="hljs-comment"># .env</span>
<span class="hljs-comment"># paste your environment variables</span>
</code></pre>
<ul>
<li>You can exit the Vim editor by pressing <code>esc</code>, then <code>:</code> (colon), typing <code>wq</code>, and pressing enter.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762624002875/5bb70b47-77b9-4978-9ba5-1d88b454ef93.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>As you can see, we now have the <code>.env</code> file in place.</p>
</li>
<li><p>Before we run our app, let’s make sure we have downloaded all the required packages by running</p>
</li>
</ul>
<pre><code class="lang-bash">npm i
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762624116285/70e3e5ff-cac7-41db-8837-75bd1fde59f3.png" alt class="image--center mx-auto" /></p>
<ul>
<li>Now, if we run the app, it should run without any errors.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762624131648/d43df60e-3c0a-40d0-9887-929ae89f9e7c.png" alt class="image--center mx-auto" /></p>
<hr />
<h1 id="heading-configuring-security-rules">Configuring Security Rules</h1>
<ul>
<li>To check if it is running, go to the instance dashboard, select your instance, copy the Public IP address, and paste it into your browser.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762624225373/a7974e5f-3fde-40a3-b4e2-5567b391460e.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762624308356/6a25bfde-598a-4a4b-b5fa-1976d5e5c599.png" alt class="image--center mx-auto" /></p>
<ul>
<li>We can't see anything, and it didn't work. Why? Do you remember the security group we set up earlier?</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762624373812/a9d5be7e-a9dd-4d84-8647-ecbe891a9121.png" alt class="image--center mx-auto" /></p>
<ul>
<li>We haven't specified our IP address and because we are accessing it from our machine we must add our IP address with port numner. So, let's add our IP address and the port number to the inbound rules and see if it works now.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762630234179/0d0385a9-a5c1-489f-a153-c878f8e1f6d2.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>Click on Add Rules and add Custom TCP and specify your IP by selecting My IP.</p>
</li>
<li><p>Now, if you run it, it should work fine. I am hitting <code>/test</code> the endpoint, which shows Hello World text.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762630310591/96b2183e-56d3-4bbf-aa2f-7a4c83195f5c.png" alt class="image--center mx-auto" /></p>
<hr />
<h1 id="heading-running-app-in-background-systemd">Running App in Background (<code>systemd</code>)</h1>
<ul>
<li><p>But here we have two major problems, and I hope you already know what they are.</p>
</li>
<li><p>One big issue is that it's running on PORT <code>7575</code> and doesn't have a domain name. The second issue is that it's running on our local terminal, so if we close this terminal, we won't be able to access our server using that public IP address.</p>
</li>
<li><p>First, let's address the second issue. To fix that, we need to find a way to run the application in the background so that no matter what we do in the terminal, the server will stay up the whole time.</p>
</li>
<li><p>If you want to run your Node app as a background service, we use the <code>systemd</code> command.</p>
</li>
<li><p>We need to create a service file. This file tells <strong>systemd</strong> (Linux’s init system) how to start, monitor, and restart your Node.js app as a background service, similar to how SSH or MongoDB are managed.</p>
</li>
<li><p>Run the command below to create a new service file.</p>
</li>
</ul>
<pre><code class="lang-bash">sudo vim /etc/systemd/system/myapp.service
</code></pre>
<ul>
<li>And paste lines below in it once Vim is opened</li>
</ul>
<pre><code class="lang-bash">[Unit]
Description=Node.js App 
After=network.target multi-user.target

[Service]
User=ubuntu
WorkingDirectory=/home/ubuntu/app
ExecStart=/usr/bin/npm start
Restart=always
Environment=NODE_ENV=production
EnvironmentFile=/home/ubuntu/app/.env
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=myapp

[Install]
WantedBy=multi-user.target
</code></pre>
<ul>
<li><p>In the [Unit] section, we include our app description and ensure our app starts <em>after networking</em> is ready (so Node can bind to a port).</p>
</li>
<li><p>In the [Service] section, we specify how the app will run by including the working directory, environment, and other settings.</p>
</li>
</ul>
<p>Now run</p>
<pre><code class="lang-bash">sudo systemctl daemon-reload
</code></pre>
<ul>
<li>Which Reloads systemd so it notices the new service file.</li>
</ul>
<pre><code class="lang-bash">sudo systemctl <span class="hljs-built_in">enable</span> myapp.service
</code></pre>
<ul>
<li>Enables the service to auto-start on boot.</li>
</ul>
<pre><code class="lang-bash">sudo systemctl start myapp.service
</code></pre>
<ul>
<li>Start it right now (without reboot).</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762631451599/094fcd1c-e82a-4831-ba3b-54c0b20efd6b.png" alt class="image--center mx-auto" /></p>
<pre><code class="lang-bash">sudo systemctl status myapp.service
</code></pre>
<ul>
<li>Shows current status, logs, and whether it’s running or failed.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762631568531/b15e0f4d-71d1-4fde-b10b-3da884193aa2.png" alt class="image--center mx-auto" /></p>
<ul>
<li>And now, if you kill your terminal/app and still hit the URL, it should work.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762632046949/e294a052-e688-4423-8e1b-ceb4836c0dc0.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762632115976/938cbc5e-eb78-493d-aa15-33ed98f00fc9.png" alt class="image--center mx-auto" /></p>
<hr />
<h1 id="heading-setting-up-reverse-proxy-using-caddy">Setting up Reverse Proxy (using Caddy)</h1>
<ul>
<li><p>The next step is to allow HTTP access directly, without using PORT 7575. Then, we will add a domain name and an SSL certificate.</p>
</li>
<li><p>To keep your actual web server, <code>localhost:7575</code>, hidden from the world and exposing only the HTTP and HTTPS ports, which are 80 and 443, we use reverse proxies like Nginx or Caddy.</p>
</li>
<li><p>A <strong>reverse proxy</strong> is a server that sits <strong>in front of your application servers</strong> and manages all incoming client requests before sending them to your backend app, such as your Node.js service.</p>
</li>
<li><p>We use this for security purposes. It allows us to add a firewall before requests reach the actual server. It also enables load balancing, as the reverse proxy can distribute requests to multiple servers.</p>
</li>
</ul>
<pre><code class="lang-bash">http://54.86.58.33:7575/
</code></pre>
<p>We do</p>
<pre><code class="lang-bash">https://api.mydomain.com
</code></pre>
<ul>
<li><p>To implement this, there are many services like Nginx and Caddy. For this example, we'll use Caddy because it's easy to set up.</p>
</li>
<li><p>Caddy is a <strong>modern, lightweight reverse proxy and web server</strong>, similar to Nginx but with a major advantage:</p>
</li>
<li><p>Visit this <a target="_blank" href="https://caddyserver.com/docs/install#debian-ubuntu-raspbian">URL</a> and run all the commands.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762632733100/756fe7e1-ae28-4565-8e35-e5c4ed799d46.png" alt class="image--center mx-auto" /></p>
<pre><code class="lang-bash">sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf <span class="hljs-string">'https://dl.cloudsmith.io/public/caddy/stable/gpg.key'</span> | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf <span class="hljs-string">'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt'</span> | sudo tee /etc/apt/sources.list.d/caddy-stable.list
chmod o+r /usr/share/keyrings/caddy-stable-archive-keyring.gpg
chmod o+r /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy
</code></pre>
<ul>
<li>Once it is done, let’s remove the custom TCP rule we added in the security group for our IP</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762632841866/a2ecc0bc-249a-4834-96d0-f3c9edf75d34.png" alt class="image--center mx-auto" /></p>
<ul>
<li>And now, if we run</li>
</ul>
<pre><code class="lang-bash">sudo systemctl start caddy
</code></pre>
<ul>
<li>And then visit the IP address <code>54.86.58.33</code> Without mentioning any port, you should see a Caddy page</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762632983642/a5317fd6-d88b-4856-ab39-3ad2c5a87fe9.png" alt class="image--center mx-auto" /></p>
<ul>
<li>But what we want is to forward the traffic to our application instead of showing this page. So, we will edit the Caddy file by opening it using the command below:</li>
</ul>
<pre><code class="lang-bash">sudo vim /etc/caddy/Caddyfile
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762633152753/82190f95-4e98-4934-a962-ba5a69e806fe.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>Comment down the <code>root</code> and <code>file_server</code> line and uncomment the <code>reverse_proxy</code> line and add your port.</p>
</li>
<li><p>And now let’s restart Caddy</p>
</li>
</ul>
<pre><code class="lang-bash">sudo systemctl restart caddy
</code></pre>
<ul>
<li>If you again run this URL, you should be able to see your app.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762634080934/e7f74781-5cb3-4a71-b50b-a3d3646c4a16.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>That’s it, now you can use this URL anywhere to access the backend APIs.</p>
</li>
<li><p>You can also set up a custom domain name and attach it to this current IP address (for example, example.com) or something like this using the Route53 service.</p>
</li>
</ul>
<hr />
<h1 id="heading-conclusion">Conclusion</h1>
<ul>
<li><p>Thank you for taking the time to read my blog. I hope you enjoyed it and learned something new. I'll be sharing more about AWS soon as I continue to learn. Keep learning!!</p>
</li>
<li><p>Until then…</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711803084752/aa266313-a315-41a8-9538-8ff4370577fb.gif?auto=format,compress&amp;gif-q=60&amp;format=webm&amp;auto=format,compress&amp;gif-q=60&amp;format=webm&amp;auto=format,compress&amp;gif-q=60&amp;format=webm&amp;auto=format,compress&amp;gif-q=60&amp;format=webm&amp;auto=format,compress&amp;gif-q=60&amp;format=webm&amp;auto=format,compress&amp;gif-q=60&amp;format=webm&amp;auto=format,compress&amp;gif-q=60&amp;format=webm&amp;auto=format,compress&amp;gif-q=60&amp;format=webm" alt class="image--center mx-auto" /></p>
<ul>
<li>Connect with me on <a target="_blank" href="https://www.linkedin.com/in/dhruv-nakum-4b1054176/"><strong>LinkedIn</strong></a><strong>,</strong> <a target="_blank" href="https://github.com/red-star25"><strong>Github</strong></a><strong>, and</strong> <a target="_blank" href="https://twitter.com/dhruv_nakum"><strong>Twitter</strong></a><strong>.</strong></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Building Scalable GO Application With Docker, AWS, and GitHub Actions]]></title><description><![CDATA[Introduction

In the last part, we successfully put the Go application in a Docker container. Now, we need to deploy it so others can access it.

First, we need to upload our Docker application to Docker Hub. We'll start with that and then set up a G...]]></description><link>https://dhruvnakum.xyz/building-scalable-go-application-with-docker-aws-and-github-actions-1-1</link><guid isPermaLink="true">https://dhruvnakum.xyz/building-scalable-go-application-with-docker-aws-and-github-actions-1-1</guid><category><![CDATA[golang]]></category><category><![CDATA[backend]]></category><category><![CDATA[AWS]]></category><category><![CDATA[Cloud]]></category><category><![CDATA[GitHub]]></category><category><![CDATA[github-actions]]></category><category><![CDATA[Redis]]></category><category><![CDATA[MongoDB]]></category><category><![CDATA[Docker]]></category><dc:creator><![CDATA[Dhruv Nakum]]></dc:creator><pubDate>Sat, 15 Feb 2025 22:25:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1739579318898/a9c508b1-5b95-4dfa-bac9-d569c7cd54c1.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-introduction">Introduction</h1>
<ul>
<li><p>In the last part, we successfully put the Go application in a Docker container. Now, we need to deploy it so others can access it.</p>
</li>
<li><p>First, we need to upload our Docker application to Docker Hub. We'll start with that and then set up a GitHub workflow action to help us deploy our application to AWS EC2.</p>
</li>
<li><p>You might wonder why we use GitHub Actions. In real-world projects, things change often, and you can't manually deploy the application every time. To automate this, we use a tool like GitHub Actions.</p>
</li>
<li><p>Let's begin with Docker Hub, and then we'll create the workflow file.</p>
</li>
</ul>
<hr />
<h1 id="heading-upload-go-application-to-dockerhub">Upload Go Application to DockerHub</h1>
<ul>
<li><ul>
<li><p>DockerHub is like GitHub but for Docker images. It's an online place where developers can find and share Docker images.</p>
<p>    * You make your custom image on your computer and push it to DockerHub for others to use.</p>
<p>    * As developers, we just need to pull these images using the command <code>docker pull</code>, and we're ready to go.</p>
<p>    * Now, let's upload our Go application to DockerHub.Step 1: Login to Dockerhub</p>
</li>
</ul>
</li>
<li><p>First, we need to log into the docker hub in order to upload.</p>
</li>
<li><p>Run the below command.</p>
</li>
</ul>
<pre><code class="lang-bash">docker login
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739417596170/b7a870f2-f440-4c08-a73a-6843d6316bf1.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>This takes you to the browser where you can log in or sign up. Once you do that, you'll see a message confirming your login was successful.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739417784101/9f64319c-18fb-4a19-a2f9-81bfff37de27.png" alt class="image--center mx-auto" /></p>
<ul>
<li>Once you are logged in, go to <code>hub.docker.com</code></li>
</ul>
</li>
</ul>
<h2 id="heading-create-repository">Create Repository</h2>
<ul>
<li>Just like GitHub needs a repository to store code, Docker Hub needs a repository to store Docker images. Go to the <code>Repositories</code> tab in the Navbar.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739417959111/465bb798-4b5e-473a-aa3d-a1c789aa56bc.png" alt class="image--center mx-auto" /></p>
<ul>
<li>And create a public repository. I already have one, so I won't create it again.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739419153043/bda81966-556d-456f-b578-5d9345b65695.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-push-image">Push Image</h2>
<ul>
<li>Once you've created the repository, you can push the image we made using the <code>docker push</code> command. But first, we need to tag our local image as <code>latest</code>, as shown below.</li>
</ul>
<pre><code class="lang-bash">docker tag anonymous-go dhruvnakum/anonymous-go:latest
</code></pre>
<ul>
<li>And now let’s push it</li>
</ul>
<pre><code class="lang-bash"> docker push dhruvnakum/anonymous-go:latest
</code></pre>
<ul>
<li><p>Replace <code>dhruvnakum</code> with your username, and if you named the repository differently, use that name instead.</p>
</li>
<li><p>After you run this, the image will be successfully pushed to the repository we just created.</p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739516794707/deb48b3b-d513-454f-8c96-66a5e1e780bf.png" alt class="image--center mx-auto" /></p>
<p>  Now, let's create a workflow to automate deployment.</p>
</li>
</ul>
<hr />
<h1 id="heading-creating-the-github-actions-workflow">Creating the GitHub Actions Workflow</h1>
<ul>
<li>To automate deployment, we will set up a GitHub Actions workflow.</li>
</ul>
<h3 id="heading-create-the-workflow-file">Create the Workflow File</h3>
<ul>
<li>In the project, create a new file at <code>.github/workflows/cicd.yml</code>.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739514002340/754732d8-9a9b-4fcf-80d0-a868ec8f2b7e.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-define-the-cicd-pipeline">Define the CI/CD Pipeline</h3>
<pre><code class="lang-bash">name: Deploy Go Application

on:
  push:
    branches: 
      - ec2

<span class="hljs-built_in">jobs</span>:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Source
        uses: actions/checkout@v4
      - name: Create .env file
        run: <span class="hljs-built_in">echo</span> <span class="hljs-string">"PORT=<span class="hljs-variable">${{ secrets.PORT }</span>}"</span> &gt;&gt; .env
      - name: Login to docker hub
        run: sudo docker login -u <span class="hljs-variable">${{ secrets.DOCKER_USERNAME }</span>} -p <span class="hljs-variable">${{ secrets.DOCKER_PASSWORD }</span>}
      - name: Build docker image
        run: sudo docker build -t dhruvnakum/anonymous-go .
      - name: Push image to docker hub
        run: sudo docker push dhruvnakum/anonymous-go:latest
  deploy:
    needs: build
    runs-on: self-hosted
    steps:
      - name: Delete old anonymous-go container
        run: sudo docker rm -f anonymous-go  
      - name: Pull new anonymous-go docker image
        run: sudo docker pull dhruvnakum/anonymous-go
      - name: Delete old mongo container
        run: sudo docker rm -f mongo-demo
      - name: Pull new mongo docker image
        run: sudo docker pull mongo:8.0
      - name: Delete old network
        run: sudo docker network rm anonymous-go-nw
      - name: Create networks
        run: sudo docker network create anonymous-go-nw
      - name: Run mongo container
        run: sudo docker run --name mongo-demo --network anonymous-go-nw -d mongo:8.0
      - name: Run docker container
        run: |
          sudo docker run --name anonymous-go \
          --network anonymous-go-nw \
          -p 3000:3000 \
          -e MONGODB_URI=<span class="hljs-string">"mongodb://mongo-demo:27017"</span> \
          -e JWT_SECRET=<span class="hljs-string">"secret"</span> \
          -e REDIS_SECRET=<span class="hljs-string">"secret"</span> \
          -e REDIS_ADDR=<span class="hljs-string">"redis-11068.c261.us-east-1-4.ec2.redns.redis-cloud.com:11068"</span> \
          -e REDIS_PASSWORD=<span class="hljs-string">"DM4iBq2pTYNQkweBZmwqGKyDYrj872M8"</span> \
          -e PORT=3000 \
          -d dhruvnakum/anonymous-go:latest
</code></pre>
<p>Let me explain what's happening here:</p>
<ul>
<li><p>First, we named the workflow <code>Deploy to Go Application</code>.</p>
</li>
<li><p>To be safe, I created a new branch called <code>ec2</code> and pushed all the code to it. We want the workflow to run automatically whenever this branch is updated. Here's how we do it:</p>
</li>
</ul>
<pre><code class="lang-bash">on:
  push:
    branches: 
      - ec2
</code></pre>
<ul>
<li><p>We have two jobs: <strong>Build and Deploy</strong>.</p>
</li>
<li><p>In the <strong>Build job</strong>, we download our project's code, build a Docker image from it, and push it to Docker Hub.</p>
</li>
<li><p>In the <strong>Deploy job</strong>, we pull the Docker image of our project and the Mongo image from Docker Hub, run them inside one container as we did before, and finally, we run that container.</p>
</li>
</ul>
<hr />
<h2 id="heading-build-job">Build Job</h2>
<ul>
<li>We want this job to run on the latest Ubuntu operating system.</li>
</ul>
<pre><code class="lang-bash"><span class="hljs-built_in">jobs</span>:
  build:
    runs-on: ubuntu-latest
</code></pre>
<ul>
<li>Now we want to download our project’s code from GitHub inside this OS, so we write this:</li>
</ul>
<pre><code class="lang-bash">name: Checkout <span class="hljs-built_in">source</span> code
uses: actions/checkout@v2
</code></pre>
<ul>
<li>Since we have environment variables in our code, we need to make them available here. To do this, we must add these secrets in <code>GitHub Action Secrets</code>. Go to the project's settings, and under secrets and variables, add these secrets.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739515733028/3ca76d47-72ef-4a61-a07a-620022310337.png" alt class="image--center mx-auto" /></p>
<ul>
<li>Now, to get this from there, we create .env inside this OS and push everything:</li>
</ul>
<pre><code class="lang-bash">- name: Create .env file
  run: <span class="hljs-built_in">echo</span> <span class="hljs-string">"PORT=<span class="hljs-variable">${{ secrets.PORT }</span>}"</span> &gt;&gt; .env
</code></pre>
<ul>
<li>Now that we have all the secrets. We Login to the docker:</li>
</ul>
<pre><code class="lang-bash">- name: Login to docker hub
  run: sudo docker login -u <span class="hljs-variable">${{ secrets.DOCKER_USERNAME }</span>} -p <span class="hljs-variable">${{ secrets.DOCKER_PASSWORD }</span>}
</code></pre>
<ul>
<li>Then we build the docker image of our project:</li>
</ul>
<pre><code class="lang-bash">- name: Build docker image
  run: sudo docker build -t dhruvnakum/anonymous-go .
</code></pre>
<ul>
<li>Finally, we push the image to the docker hub:</li>
</ul>
<pre><code class="lang-bash">- name: Push image to docker hub
  run: sudo docker push dhruvnakum/anonymous-go:latest
</code></pre>
<hr />
<h2 id="heading-deploy-job">Deploy Job</h2>
<ul>
<li><p>In this job, everything we write will run on the AWS EC2 instance provided by GitHub.</p>
</li>
<li><p>We want this job to start after the build job finishes successfully.</p>
</li>
</ul>
<pre><code class="lang-bash">deploy:
    needs: build
</code></pre>
<ul>
<li>In this job, we first want to delete all the old containers and pull the latest ones. Once that is done, we run the container using <code>docker run</code> command.</li>
</ul>
<pre><code class="lang-bash">deploy:
    needs: build
    runs-on: self-hosted
    steps:
      - name: Delete old anonymous-go container
        run: sudo docker rm -f anonymous-go  
      - name: Pull new anonymous-go docker image
        run: sudo docker pull dhruvnakum/anonymous-go
      - name: Delete old mongo container
        run: sudo docker rm -f mongo-demo
      - name: Pull new mongo docker image
        run: sudo docker pull mongo:8.0
      - name: Delete old network
        run: sudo docker network rm anonymous-go-nw
      - name: Create networks
        run: sudo docker network create anonymous-go-nw
      - name: Run mongo container
        run: sudo docker run --name mongo-demo --network anonymous-go-nw -d mongo:8.0
      - name: Run docker container
        run: |
          sudo docker run --name anonymous-go \
          --network anonymous-go-nw \
          -p 3000:3000 \
          -e MONGODB_URI=<span class="hljs-string">"mongodb://mongo-demo:27017"</span> \
          -e JWT_SECRET=<span class="hljs-string">"secret"</span> \
          -e REDIS_SECRET=<span class="hljs-string">"secret"</span> \
          -e REDIS_ADDR=<span class="hljs-string">"redis-11068.c261.us-east-1-4.ec2.redns.redis-cloud.com:11068"</span> \
          -e REDIS_PASSWORD=<span class="hljs-string">"DM4iBq2pTYNQkweBZmwqGKyDYrj872M8"</span> \
          -e PORT=3000 \
          -d dhruvnakum/anonymous-go:latest
</code></pre>
<blockquote>
<p>Before we move ahead I forgot to change the URL in the main from localhost to <code>0.0.0.0</code> . So make sure you have this inside main.go at the end.</p>
<pre><code class="lang-bash">log.Fatal(r.Run(<span class="hljs-string">"0.0.0.0:"</span> + os.Getenv(<span class="hljs-string">"PORT"</span>)))
</code></pre>
</blockquote>
<hr />
<h1 id="heading-setting-up-aws-ec2-instance">Setting Up AWS EC2 Instance</h1>
<ul>
<li><p>Before deploying the project, we need to set up an EC2 instance.</p>
</li>
<li><p>To do this, log in to the AWS Management Console and go to the EC2 dashboard.</p>
</li>
<li><p>Use this link: <a target="_blank" href="https://aws.amazon.com/ec2/">https://aws.amazon.com/ec2/</a></p>
</li>
<li><p>It might ask for your card details, but don't worry; they won't charge you unless you exceed the limit, which you won't be charged for for this purpose.</p>
</li>
<li><p>Search for EC2 and click on it.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739576315008/4743aac7-5276-4ada-a4b2-839861719665.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Now click on the “Launch Instance.”</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739576363008/886edd48-54f3-46df-a3bc-77e9eb6e3708.png" alt class="image--center mx-auto" /></p>
<ul>
<li>Now, give the instance name and select the Ubuntu image</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739576476093/51e24e8e-7158-4890-a481-ef8162406067.png" alt class="image--center mx-auto" /></p>
<ul>
<li>Also, select Key pair. If you don’t have one. You can create it by clicking on the Create new key pair button.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739576528760/9a33561f-da16-4cc2-9fb9-ef9558b87978.png" alt class="image--center mx-auto" /></p>
<ul>
<li>Once it is done, click on the Launch Instance button on the right</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739576587591/5d1b7476-7395-4aa4-a996-e05561354490.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>Since our app is running on port 3000, go to the Security Group settings and allow inbound traffic on <strong>port 3000</strong> to make sure the application is accessible.</p>
</li>
<li><p>To do that, click on the Instance ID</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739576672455/daa31b8d-6768-4e61-a14b-1cd1dd729f85.png" alt class="image--center mx-auto" /></p>
<ul>
<li>Then click on the Security tab and there click on Security Group</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739576717820/dcbadb45-e7d2-4430-8284-5c4c19204800.png" alt class="image--center mx-auto" /></p>
<ul>
<li>Click on Edit inbound rules and then add custom TCP with Port range 3000 as shown below and save the rules.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739576774337/ad884b6d-df63-41f7-b114-81c37a401cd0.png" alt class="image--center mx-auto" /></p>
<ul>
<li>Now, we are ready to run the instance.</li>
</ul>
<h2 id="heading-setting-up-docker-inside-ec2-instance">Setting up Docker inside EC2 Instance,</h2>
<ul>
<li>Our instance is ready. First, we need to make sure Docker is installed. To do this, let's connect to the instance. Click the Connect button after selecting the instance.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739576897708/00324931-7359-47cf-afe2-00d882b64f6b.png" alt class="image--center mx-auto" /></p>
<ul>
<li>Once you do that you will see a window like this:</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739576982747/bcad9c57-c111-40ec-9f73-ee94ceef7df2.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>To confirm whether docker is there or not, run <code>docker</code> command, you will most likely see this:</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739577088622/017a2ea5-fbdc-4f14-9666-b58a9cca731c.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Now, to install everything, run the below commands</p>
</li>
</ul>
<pre><code class="lang-bash">sudo apt-get update &amp;&amp; sudo apt-get install docker.io -y &amp;&amp; sudo systemct| start docker &amp;&amp; sudo chmod 666 /var/run/docker.sock &amp;&amp; sudo systemcti <span class="hljs-built_in">enable</span> docker &amp;&amp; docker --version
</code></pre>
<ul>
<li>Once it is done, run <code>docker version</code> to see if it’s installed correctly.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739577318335/0482a21a-55c0-4d7d-ad5c-4adc5b558c26.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-register-a-self-hosted-github-runner">Register a Self-Hosted GitHub Runner</h2>
<ul>
<li><p>A <strong>self-hosted runner</strong> is simply a computer or server that you own (like an AWS EC2 instance) that runs GitHub Actions instead of using GitHub’s default machines.</p>
</li>
<li><p>To set up, go to the project settings, and inside Actions, you will see this runner tab.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739577522960/083333ac-9bc5-421d-81ee-16df3bada386.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>Click on `New self-hosted runner:</p>
</li>
<li><p>Select Linux</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739577617221/1caf496c-c9d8-42c4-bfc4-32efc7d84a73.png" alt class="image--center mx-auto" /></p>
<ul>
<li>And run all the commands you see there inside your EC2.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739577640356/6ca70f3e-e61a-47ab-8c98-8a301644664a.png" alt class="image--center mx-auto" /></p>
<ul>
<li>Once you run the last step <code>./run.sh</code> command, you will see a runner with the status idle :</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739577761453/8e1d7651-a258-447e-aab8-4101d725c6f9.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>But there is one problem, that runner does not run on detached mode in our instance, If you kill it by pressing CTRL+C, you wont be able to run it.</p>
</li>
<li><p>To resolve this issue, just run the below command</p>
</li>
</ul>
<pre><code class="lang-bash">sudo ./svc.sh install
</code></pre>
<pre><code class="lang-bash">sudo ./svc.sh start
</code></pre>
<ul>
<li><p>Now, your running is running in the background, and you can perform other operations inside your instance.</p>
</li>
<li><p>Finally, we are done with it. Now, we can test our application.</p>
</li>
</ul>
<h2 id="heading-testing-your-application">Testing Your Application</h2>
<ul>
<li>To test, commit to the <code>ec2</code> branch that we created. Once you do that, Go to the Actions tab, and there you will see both <code>build</code> <code>deploy</code> services are running.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739578220374/e8282b00-49f0-4118-8ad2-6bbb86a45e2b.png" alt class="image--center mx-auto" /></p>
<ul>
<li>Once it is done, you will see both marked as green ticks, which means that everything went well and the docker is successfully up inside the ec2.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739578520776/9dd5ade3-30fb-404d-b77e-5e92968dc2e0.png" alt class="image--center mx-auto" /></p>
<ul>
<li>To test, go to the instance we created and find the <code>Public IPv4 address</code>. Copy it, paste it into Postman, and run any endpoint in your application.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739578733192/af6db6bd-4ce9-49ee-8b9d-8ad0c739dd4d.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739578557983/130f27ee-5289-43a1-9a3c-3a0da8e237f3.png" alt class="image--center mx-auto" /></p>
<ul>
<li>As you can see it’s working now.</li>
</ul>
<p><img src="https://media1.tenor.com/m/sTbvauq7zVoAAAAC/lest-celebrate-happy.gif" alt="a man is sitting at a table with his arms outstretched in front of a crowd with the words let 's celebrate !!" class="image--center mx-auto" /></p>
<ul>
<li>Phew! I know that seems like a lot at first. But once you follow these steps, you'll get comfortable with it.</li>
</ul>
<hr />
<h1 id="heading-wrapping-up">Wrapping Up</h1>
<ul>
<li><p>If you've made it to the end of this guide, you truly deserve a big thank you. So, thank you for sticking with it and reading all the way through. I hope you've gained a lot of valuable insights from this single blog post, covering everything from creating a GitHub Workflow to understanding Actions, setting up a Runner, and working with AWS EC2.</p>
</li>
<li><p>Keep diving deeper into these topics and explore other related concepts, as they are essential for cloud development in today's fast-paced tech world. Mastering these skills will undoubtedly enhance your ability to build and deploy applications efficiently.</p>
</li>
<li><p>I look forward to sharing more knowledge with you in the next blog post! Until then, keep learning and experimenting with new technologies.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711803084752/aa266313-a315-41a8-9538-8ff4370577fb.gif?auto=format,compress&amp;gif-q=60&amp;format=webm&amp;auto=format,compress&amp;gif-q=60&amp;format=webm&amp;auto=format,compress&amp;gif-q=60&amp;format=webm&amp;auto=format,compress&amp;gif-q=60&amp;format=webm&amp;auto=format,compress&amp;gif-q=60&amp;format=webm&amp;auto=format,compress&amp;gif-q=60&amp;format=webm&amp;auto=format,compress&amp;gif-q=60&amp;format=webm" alt class="image--center mx-auto" /></p>
<p>Reference: <a target="_blank" href="https://www.youtube.com/watch?v=sSAWMr_-Co4&amp;t=409s">https://www.youtube.com/watch?v=sSAWMr_-Co4&amp;t=409s</a></p>
<ul>
<li>Connect with me on <a target="_blank" href="https://www.linkedin.com/in/dhruv-nakum-4b1054176/"><strong>LinkedIn</strong></a><strong>,</strong> <a target="_blank" href="https://github.com/red-star25"><strong>Github</strong></a><strong>, and</strong> <a target="_blank" href="https://twitter.com/dhruv_nakum"><strong>Twitter</strong></a><strong>.</strong></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Building Scalable GO Application With Docker, AWS, and GitHub Actions]]></title><description><![CDATA[Introduction

Hey, Gophers! I hope you're doing well. I'm back with Part 2 of the series. In the last blog, we learned about Docker and how it makes development easier with Containers.

We also created two containers: MongoDB and Redis.

Now that we ...]]></description><link>https://dhruvnakum.xyz/building-scalable-go-application-with-docker-aws-and-github-actions-1</link><guid isPermaLink="true">https://dhruvnakum.xyz/building-scalable-go-application-with-docker-aws-and-github-actions-1</guid><category><![CDATA[Go Language]]></category><category><![CDATA[Docker]]></category><category><![CDATA[backend]]></category><category><![CDATA[Cloud]]></category><category><![CDATA[Beginner Developers]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Developer]]></category><dc:creator><![CDATA[Dhruv Nakum]]></dc:creator><pubDate>Tue, 11 Feb 2025 23:19:27 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1739315332499/5184e511-e38c-474c-9686-9c87207e7201.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-introduction">Introduction</h1>
<ul>
<li><p>Hey, Gophers! I hope you're doing well. I'm back with Part 2 of the series. In the last blog, we learned about Docker and how it makes development easier with Containers.</p>
</li>
<li><p>We also created two containers: MongoDB and Redis.</p>
</li>
<li><p>Now that we have both running in Docker containers, it's time to build a custom image for a Go application. In this part, we'll learn about the Dockerfile, docker-compose file, and how to dockerize the application. By the end of this tutorial, you'll have a fully functional dockerized application that you can pull onto your machine and use without installing any dependencies.</p>
</li>
<li><p>Before that, I'll give you a quick overview of the project. I won't go into too much detail to keep the blog short. I'll just explain what the project is about, and then we'll get started with the main part. Let's do that!</p>
</li>
</ul>
<hr />
<h1 id="heading-project-overview">Project Overview</h1>
<ul>
<li><p>This is a simple project, but using all this tech together will be really fun. The project is about making a post anonymously, kind of like a tiny version of Reddit. It's just for learning, so it's okay if it's not very big. We'll only ask for a username and password, nothing else. The user signs up and logs in with these two things.</p>
</li>
<li><p>Users can create posts, and they can also like and comment on other people's posts.</p>
</li>
<li><p>Here's the mind map of what we'll use in our Go app and which APIs we'll implement.</p>
</li>
<li><p>By the way, the project's name is Anonymous-GO.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738898724300/90d28497-ede8-440c-a152-40865608c9d0.png" alt class="image--center mx-auto" /></p>
<blockquote>
<p>Note: This blog requires basic knowledge of Go and how to work with APIs. I won't explain everything from the beginning. If you want to start from the basics, I already have a series on that. You might want to check it out first:</p>
<p><a target="_blank" href="https://dhruvnakum.xyz/from-zero-to-go-hero-learn-go-programming-with-me-part-1">https://dhruvnakum.xyz/from-zero-to-go-hero-learn-go-programming-with-me-part-1</a></p>
</blockquote>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/red-star25/anonymous-go">https://github.com/red-star25/anonymous-go</a></div>
<p> </p>
<ul>
<li>Here is the final project if you want to go through.</li>
</ul>
<hr />
<h1 id="heading-setting-up-go-project">Setting Up Go Project</h1>
<h2 id="heading-project-structure">Project Structure</h2>
<ul>
<li>We will structure our project as follows:</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738900037978/7fe247f7-c226-405c-9598-149e1dbd945f.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>In <code>config/</code>, we have functions for setting up Redis.</p>
</li>
<li><p><code>controllers/</code> contains all the main operations like auth, post, like, and comment. This is where we define all the route handlers.</p>
</li>
<li><p>In <code>database/</code>, we set up MongoDB and have the User and Post collections.</p>
</li>
<li><p>In <code>middleware/</code>, we define handlers for accessing private routes.</p>
</li>
<li><p>In <code>models/</code>, we define different models like User, Post, Comment, and Like.</p>
</li>
<li><p>In <code>utils/</code>, we have helper functions for JWT authentication, and for hashing and verifying passwords.</p>
</li>
<li><p>We also have the <code>.env</code> file for storing all the project secrets.</p>
</li>
<li><p>We will discuss <code>docker-compose.yml</code>, <code>Dockerfile</code>, and <code>Dockerfile.dev</code> in the next section.</p>
</li>
</ul>
<hr />
<h1 id="heading-running-mongodb-containers">Running MongoDB Containers</h1>
<ul>
<li><p>If you read the previous blog, you might have a MongoDB container ready inside your docker. Let’s first run that.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738902307655/cd5057af-91ae-4857-8658-d593cc163da4.png" alt class="image--center mx-auto" /></p>
</li>
</ul>
<hr />
<ul>
<li><p>Before we start, we will use a MongoDB container for database storage and Redis Cloud for caching.</p>
</li>
<li><p>To run the project, we first need to start the MongoDB container to connect the database with the application.</p>
</li>
<li><p>When you run the project, you will see that both MongoDB and Redis are connected.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739135965979/a48a335f-c077-4c62-b7a0-55647a884dbf.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Let’s now dockerize this application.</p>
</li>
</ul>
<hr />
<h1 id="heading-dockerize-go-application">Dockerize GO Application</h1>
<ul>
<li>Before we start, let’s recap things we learned about Docker in our previous blog.</li>
</ul>
<h3 id="heading-what-is-docker-image"><strong>What is Docker Image?</strong></h3>
<ul>
<li><p>As we saw in our previous blog, Docker Image is a package/bundle that is used as a template to create <strong>containers.</strong></p>
</li>
<li><p>It contains everything needed to run an application. The docker image contains the application code. In our case, it is our Go code.</p>
</li>
<li><p>It also contains dependencies that we use in our application, like any libraries, frameworks, etc.</p>
</li>
<li><p>Contains all the tools and settings.</p>
</li>
<li><p>But the thing is Docker image cannot run on its own. As it’s just a template. To run the Image, we need to create a Container, just like Class and Object, Class is nothing without an Object, right!?</p>
</li>
</ul>
<h3 id="heading-what-is-docker-container"><strong>What is Docker Container?</strong></h3>
<ul>
<li><p>As Docker Image cannot run on its own, it requires Containers. Containers are basically running instances of a Docker Image.</p>
</li>
<li><p>You can think Docker Image as <code>Class</code> and Docker Containers as <code>Objects</code>. Class is nothing but a template and we can make use of class by creating it’s objects.</p>
</li>
<li><p>Every containers run in its isolated environment. And this containers runs exactly the same on every machine. Whether it’s Windows, Mac, or Linux.</p>
</li>
<li><p>As you can create multiple objects from a defined class. You can create multiple containers from the same image.</p>
</li>
</ul>
<hr />
<ul>
<li><p>Now you might be asking that, Okay, I understand Image and Containers now. But how do we create an Image of our application right?</p>
</li>
<li><p>Well that’s where Dockerfile comes into picture.</p>
</li>
</ul>
<h2 id="heading-dockerfile">Dockerfile</h2>
<ul>
<li><p>A Dockerfile is nothing but a set of instructions that tells Docker how to build a Docker Image.</p>
</li>
<li><p>Let’s see how we can write a docker file</p>
</li>
</ul>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> golang:<span class="hljs-number">1.23</span>-alpine AS builder
<span class="hljs-keyword">WORKDIR</span><span class="bash"> /app</span>
<span class="hljs-keyword">COPY</span><span class="bash"> go.mod go.sum ./</span>
<span class="hljs-keyword">RUN</span><span class="bash"> go mod download</span>
<span class="hljs-keyword">COPY</span><span class="bash"> . .</span>
<span class="hljs-keyword">RUN</span><span class="bash"> CGO_ENABLED=0 GOOS=linux go build -o main .</span>

<span class="hljs-keyword">FROM</span> alpine:latest
<span class="hljs-keyword">COPY</span><span class="bash"> --from=builder /app/main /main</span>
<span class="hljs-keyword">COPY</span><span class="bash"> .env .</span>
<span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">3000</span>
<span class="hljs-keyword">ENTRYPOINT</span><span class="bash"> [<span class="hljs-string">"/main"</span>]</span>
</code></pre>
<ul>
<li><p>Let’s understand what’s happening here</p>
</li>
<li><p>First of all, we want Docker to compile our application, right? How do we do that? Well, Docker Hub already has the docker compiler image for us. It has all the tools, like <code>go build</code> <code>go mod</code>, etc.</p>
</li>
<li><p>So, we are using this docker image as our base. You might ask why we are using this image only and not the other one. The simple answer to that is it’s lightweight.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739145821608/206eed13-67d3-45ba-9557-54174436eb34.png" alt class="image--center mx-auto" /></p>
</li>
</ul>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> golang:<span class="hljs-number">1.23</span>-alpine
</code></pre>
<ul>
<li><p>The line above does that task. We also added <code>AS builder</code> after it. This simply labels this stage as "builder," so we can refer to it later.</p>
</li>
<li><p>You might ask why we need <code>builder</code> a stage.</p>
<ul>
<li><p>Well, we don’t want the final application to have every Go tool. It’s unnecessary. So, instead of storing unnecessary files in the final image, we directly build the app in this stage and then copy only the final compiled binary file into a new lightweight image.</p>
</li>
<li><p>This keeps the docker image small, fast, and secure.</p>
</li>
</ul>
</li>
</ul>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">WORKDIR</span><span class="bash"> /app</span>
</code></pre>
<ul>
<li><p>Here, we are creating a working directory inside the container. So, when we say <code>/app</code>, this means we are creating an app folder inside the container.</p>
</li>
<li><p>You might ask why we are creating app directory, can’t we just push all the things directly. Well, you can, but it’s not recommended. Why? Because If we don’t do this, every time we need to execute <code>cd</code> command manually to go inside <code>/app</code> folder</p>
</li>
</ul>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">COPY</span><span class="bash"> go.mod go.sum ./</span>
</code></pre>
<ul>
<li><p>The <code>COPY</code> instruction is used to copy the files from your <strong>local machine</strong> to <strong>docker containers.</strong></p>
</li>
<li><p>It will copy it inside <code>/app</code> as we previously set the current working directory as <code>/app</code></p>
</li>
<li><p>You might ask why we are copying this. well, otherwise, our application won’t run. These files contain all the dependencies required by our application.</p>
</li>
</ul>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">RUN</span><span class="bash"> go mod download</span>
</code></pre>
<ul>
<li>This command is used to download and cache all dependencies specified in <code>go.mod</code></li>
</ul>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">COPY</span><span class="bash"> . .</span>
</code></pre>
<ul>
<li><p>Now that we are done with the basic setup, we need to copy all the files and folders of our application into a docker container.</p>
</li>
<li><p>The . (dot) here refers to the current directory on your local system and second . (dot) refers to the current working directory inside the container.</p>
</li>
</ul>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">RUN</span><span class="bash"> CGO_ENABLED=0 GOOS=linux go build -o main .</span>
</code></pre>
<ul>
<li><p>Now that we have copied our files, folders, and dependencies, we are ready to compile the application inside the container. This is what the above <code>RUN</code> instruction does.</p>
</li>
<li><p><code>go build -o main .</code> tells Go to <strong>compile the application</strong> and generate an <strong>executable file</strong> named <code>main</code>.</p>
</li>
<li><p>The <code>.</code> at the end means <strong>compiling all Go files in the current directory</strong> (<code>/app</code>). which is <code>main.go</code> in this case.</p>
</li>
<li><p>CGO allows Go programs to use C libraries, but it also makes the binary dependent on system libraries. And we don’t want that, so we are disabling it by assigning <code>0</code> value.</p>
</li>
<li><p>GOOS=linux is used to build the application for <strong>Linux</strong>. As we are using Alpine Linux image, we need to do that to make it lightweight for the system.</p>
</li>
</ul>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> alpine:latest
</code></pre>
<ul>
<li><p>Now that we have compiled the Go application in the previous <code>builder</code> stage. We now need to run the final binary which is created inside container.</p>
</li>
<li><p>To do that, we are using <code>alpine</code> container to create a lightweight container. This removes all the unnecessary libraries and tools.</p>
</li>
<li><p>In the previous stage (<code>golang:1.23-alpine AS builder</code>), we <strong>compiled the Go application</strong>.</p>
</li>
<li><p>Now, we only need to <strong>run the final binary</strong>, so we switch to a clean, minimal image.</p>
</li>
</ul>
<p>So what exactly happens here:</p>
<ul>
<li><p>Docker starts with a <strong>fresh Alpine Linux image</strong>.</p>
</li>
<li><p>No Go compiler or extra tools are installed, just a clean OS.</p>
</li>
<li><p>The final container only contains what's needed to run the application.</p>
</li>
</ul>
<p>Why Not Just Use <code>golang:1.23-alpine</code> for Everything?</p>
<ul>
<li>That’s a good question. It’s because The Go compiler and tools are only needed for building/compiling the app, not for running it. Keeping them in the final image wastes space and increases security risks. Using Alpine separately ensures a much smaller, optimized final container.</li>
</ul>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">COPY</span><span class="bash"> --from=builder /app/main /main</span>
</code></pre>
<ul>
<li><p>This line copies the compiled Go application from the <code>builder</code> stage into the final Alpine-based image.</p>
</li>
<li><p><code>/app/main</code> this is where the Go binary was built in the builder stage</p>
</li>
<li><p>And <code>/main</code> this is the destination inside the Alpine container</p>
</li>
</ul>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">3000</span>
</code></pre>
<ul>
<li>With this instruction, we are telling Docker that this container is expecting traffic at port 3000.</li>
</ul>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">ENTRYPOINT</span><span class="bash"> [<span class="hljs-string">"/main"</span>]</span>
</code></pre>
<ul>
<li><p><code>ENTRYPOINT ["/main"]</code> tells Docker to <strong>execute the compiled Go binary</strong> (<code>/main</code>) inside the container.</p>
</li>
<li><p>So now, when you run the container, it automatically <strong>runs the</strong> <code>/main</code> binary.</p>
</li>
</ul>
<hr />
<h3 id="heading-how-does-this-overall-process-work">How does this overall process work?</h3>
<p><strong>First Stage (</strong><code>builder</code>)</p>
<ul>
<li><p>Uses <code>golang:1.23-alpine</code></p>
</li>
<li><p>Builds the <strong>Go application (</strong><code>main</code>) inside <code>/app</code></p>
</li>
<li><p>This stage has all the necessary tools (Go compiler, dependencies)</p>
</li>
</ul>
<p><strong>Final Stage (</strong><code>alpine:latest</code>)</p>
<ul>
<li><p>Starts with a <strong>clean Alpine Linux image</strong></p>
</li>
<li><p><strong>Copies only the</strong> <code>main</code> binary from <code>/app/main</code> into <code>/main</code></p>
</li>
<li><p>The container is now <strong>ready to run the application</strong></p>
</li>
</ul>
<hr />
<h3 id="heading-does-that-mean-we-are-using-two-containers">Does that mean we are using two Containers?</h3>
<ul>
<li><p><strong>No</strong>, we are <strong>not</strong> creating two containers. Instead, we are creating <strong>two separate environments during the build process</strong></p>
</li>
<li><p>Docker <strong>processes both stages</strong> during the <strong>image build</strong> phase.</p>
</li>
<li><p>It <strong>uses the first stage</strong> (<code>builder</code>) to compile the application.</p>
</li>
<li><p>It then <strong>discards the first stage</strong> and only <strong>keeps the final image</strong> from the second stage.</p>
</li>
<li><p>The <strong>container is created from this final, lightweight image</strong>.</p>
</li>
</ul>
<hr />
<h1 id="heading-docker-compose">docker-compose</h1>
<ul>
<li>Now our <code>Dockerfile</code> is ready. But right now, our app is only containerized. Remember, we are also using a <strong>MongoDB</strong> image in our app. If you try to run the app right now, it will throw an error because the app <strong>cannot connect to MongoDB</strong>.</li>
</ul>
<h2 id="heading-why-do-we-need-docker-composeyml">Why Do We Need <code>docker-compose.yml</code>?</h2>
<ul>
<li><p>Our <strong>app and MongoDB are running in separate containers</strong>.</p>
</li>
<li><p>The app <strong>depends on MongoDB</strong> to function, but without a shared network, they cannot communicate.</p>
</li>
<li><p>Running both services manually using <code>docker run</code> commands is inefficient.</p>
</li>
<li><p>This is where <strong>Docker Compose</strong> comes into play.</p>
</li>
<li><p>Docker Compose allows us to <strong>define and run multiple containers as a single service</strong>, ensuring that all required services (our app and MongoDB) start together, communicate seamlessly, and work as a unit.</p>
</li>
<li><p>With a <code>docker-compose.yml</code> file, we can:</p>
<ul>
<li><p><strong>Define multiple services</strong> (e.g., <code>app</code> and <code>mongo</code>) in a single configuration.</p>
</li>
<li><p><strong>Ensure MongoDB starts before the app</strong> using <code>depends_on</code>.</p>
</li>
<li><p><strong>Use a shared network</strong> so the app can communicate with MongoDB by referring to it as <code>mongo</code> instead of <a target="_blank" href="http://localhost"><code>localhost</code></a>.</p>
</li>
<li><p><strong>Set up environment variables</strong> for both services.</p>
</li>
<li><p><strong>Use volumes to persist MongoDB data</strong>, preventing data loss when containers restart.</p>
</li>
</ul>
</li>
<li><p>Now let’s write docker-compose for our app:</p>
</li>
</ul>
<pre><code class="lang-dockerfile">services:
  app:
    build:
      context: .
    ports:
      - <span class="hljs-string">"3000:3000"</span>
    depends_on:
      - mongo
    environment:
      - MONGODB_URI=mongodb://admin:admin@mongo:<span class="hljs-number">27017</span>/
      - JWT_SECRET=secret
      - REDIS_SECRET=secret
      - REDIS_ADDR=redis-<span class="hljs-number">11068</span>.c261.us-east-<span class="hljs-number">1</span>-<span class="hljs-number">4</span>.ec2.redns.redis-cloud.com:<span class="hljs-number">11068</span>
      - REDIS_USERNAME=default
      - REDIS_PASSWORD=DM4iBq2pTYNQkweBZmwqGKyDYrj872M8

  mongo:
    image: mongo:<span class="hljs-number">8.0</span>
    environment:
      MONGO_INITDB_ROOT_USERNAME: admin
      MONGO_INITDB_ROOT_PASSWORD: admin
    ports:
      - <span class="hljs-string">"27017:27017"</span>
    volumes:
      - mongodata:/data/db

volumes:
  mongodata:
    driver: local
</code></pre>
<ul>
<li><p>So, as we know, we need two services: <strong>app</strong> and <strong>Mongo</strong></p>
</li>
<li><p>And that’s why we are defining those inside <code>services</code></p>
</li>
</ul>
<ol>
<li><p><code>app</code> Service:</p>
<ul>
<li><p>This service is used to build the app using <code>Docekerfile</code>.</p>
</li>
<li><p>So, we are defining its context with the current directory by specifying . (dot).</p>
</li>
<li><p>Then, we want to map the container port to the local system’s port (called port mapping). We do that inside <code>port</code> tag.</p>
</li>
<li><p>Also, we want mongo-db to run before our app starts. To achieve that, we have to use <code>depends_on</code> tag and specify <code>mongo</code> image.</p>
</li>
<li><p>Also, if you look at the project, we have specified many environmental variables. And so we want this app service to use that. So, we specify all the environment variables inside <code>ennvironment</code> tag.</p>
</li>
</ul>
</li>
<li><p><code>mongo</code> Service:</p>
<ul>
<li><p>We first need to pull up the Mongo image. We are doing it by specifying it inside the image tag.</p>
</li>
<li><p>As we did in our app, we are specifying env variables inside the environment tag.</p>
</li>
<li><p>Then, we need to expose port 27017 for database access.</p>
</li>
<li><p>Also, we want to keep the data in a persistent volume. So, we are using volumes</p>
</li>
</ul>
</li>
</ol>
<hr />
<h1 id="heading-running-docker-compose">Running docker-compose</h1>
<ul>
<li>Now that we are done with the <code>docker-compose</code> file, we can now run the container using the following command.</li>
</ul>
<pre><code class="lang-bash">docker compose up -d
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739313199863/d034dcf1-1661-4cc9-8b93-d18e44716573.png" alt class="image--center mx-auto" /></p>
<ul>
<li>It will start the container inside the docker.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739313229207/7701e00c-c1d0-4976-ab92-5a621a063237.png" alt class="image--center mx-auto" /></p>
<p><img src="https://media.tenor.com/gRAe8dfvrKMAAAAM/television-tv-shows.gif" alt="a man with a mustache in a blue suit says tiny celebration in front of a group of people" class="image--center mx-auto" /></p>
<ul>
<li><p>As you can see, our entire application is now dockerized.</p>
</li>
<li><p>Now, let’s test our API to see if it’s working or not.</p>
</li>
<li><p>Let’s create a user with a signup API</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739313584476/1bf032bb-1c0b-4d91-a781-0c81f991a2c4.png" alt class="image--center mx-auto" /></p>
<ul>
<li>Now, let’s test the login functionality with Redis caching.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739313661204/5d48a2df-ed6e-4583-86c3-f868fb450301.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739313672723/94f1b903-6474-4dda-a4ec-053c8d2b45d5.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>As you can see, Redis-session is added to cookies for accessing private routes. Let’s also test private routes to make sure they're working.</p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739313781567/d7c9a3ab-1f81-41f8-ab0d-1fe951c0f5fe.png" alt class="image--center mx-auto" /></p>
<p>  And yes, it is working. And now, if you try to access private routes after logging out. It will not work. Let’s check that.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739313819152/ea79e366-ab33-48ff-aadb-2d3af8bad306.png" alt class="image--center mx-auto" /></p>
<p>  - We logged out.</p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739313888578/a3c04d94-effb-44c5-a7d4-edd2f56bd279.png" alt class="image--center mx-auto" /></p>
<p>  As you can see, it’s throwing an error.</p>
</li>
</ul>
<hr />
<h1 id="heading-whats-next">What’s Next?</h1>
<ul>
<li><p>Currently, our APIs are working locally. But we want developers to use these APIs, right? To do that, we need to deploy this.</p>
</li>
<li><p>Now, there are many deployment options available in the market.</p>
</li>
<li><p>The most popular one is AWS.</p>
</li>
<li><p>In the next part, we are going to push our custom image to Docker Hub, set up GitHub actions, and Upload and Run this docker container inside the AWS EC2 instance.</p>
</li>
<li><p>It’s gonna be really good learning for anyone who is not familiar with AWS deployment. Because I learned a lot.</p>
</li>
<li><p>Let’s meet in the next one, until then…</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711803084752/aa266313-a315-41a8-9538-8ff4370577fb.gif?auto=format,compress&amp;gif-q=60&amp;format=webm&amp;auto=format,compress&amp;gif-q=60&amp;format=webm&amp;auto=format,compress&amp;gif-q=60&amp;format=webm&amp;auto=format,compress&amp;gif-q=60&amp;format=webm&amp;auto=format,compress&amp;gif-q=60&amp;format=webm&amp;auto=format,compress&amp;gif-q=60&amp;format=webm" alt class="image--center mx-auto" /></p>
]]></content:encoded></item><item><title><![CDATA[Building Scalable GO Application With Docker, AWS, and GitHub Actions]]></title><description><![CDATA[Introduction

Hey, Gophers! Thanks for stopping by and taking the time to read my blog. If you’ve been following my previous blogs, you might know that I recently started writing about Go. In my last article, we explored JWT authentication using the ...]]></description><link>https://dhruvnakum.xyz/building-scalable-go-application-with-docker-aws-and-github-actions</link><guid isPermaLink="true">https://dhruvnakum.xyz/building-scalable-go-application-with-docker-aws-and-github-actions</guid><category><![CDATA[Go Language]]></category><category><![CDATA[backend]]></category><category><![CDATA[Docker]]></category><category><![CDATA[Cloud]]></category><category><![CDATA[Beginner Developers]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Developer]]></category><dc:creator><![CDATA[Dhruv Nakum]]></dc:creator><pubDate>Fri, 07 Feb 2025 21:11:05 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1738807715646/e6db401e-5c94-4fa0-a4bb-dc06130106d9.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-introduction">Introduction</h1>
<ul>
<li><p>Hey, Gophers! Thanks for stopping by and taking the time to read my blog. If you’ve been following my previous blogs, you might know that I recently started writing about Go. In my last article, we explored JWT authentication using the JWT and Fiber packages in Go.</p>
</li>
<li><p>After covering the basics, I learned more advanced topics, such as working with databases, handling cookies and sessions, deploying Go applications on the cloud, using Docker, containerizing Go applications, etc.</p>
</li>
<li><p>And to put all these concepts into practice, I built a project that combined them all. Honestly, it was quite a challenge since AWS, Dockerization, Redis, and GitHub Actions were new to me. But after overcoming everything, I’m excited to share everything I’ve learned with you.</p>
</li>
<li><p>In this series, I’ll break down each concept in a simple and easy-to-understand way. If anything is unclear, feel free to ask in the comments!</p>
</li>
<li><p>By the end of this series, you’ll have a fully deployed, containerized project running in the cloud. If you're a beginner, this project will be a great addition to your resume, as we’ll be implementing many industry standard practices used in real-world applications.</p>
</li>
<li><p>We first need to learn a very important concept/tool called <strong>Docker.</strong> Because nowadays everyone is using it.</p>
</li>
<li><p>So, let’s first start with Docker and understand what Docker is. Why do we and people in the software industry use it? And What problem does it resolve?</p>
</li>
</ul>
<hr />
<h1 id="heading-why-do-we-need-docker">Why do we need Docker?</h1>
<ul>
<li><p>Before diving into what Docker is, let's first understand the issues it helps to resolve.</p>
</li>
<li><p>Imagine an organization with hundreds of developers, each working on different machines with different operating systems and configurations. Now, consider a project where a team of 50 developers is collaborating on the same GitHub repository.</p>
</li>
<li><p>As the team grows, new developers are hired and provided with new systems to work on. To get started, they need to clone the project from the repository and set up the development environment. Let's take two developers as an example:</p>
<ul>
<li><p><strong>Developer 1</strong> – A current employee who has already set up the project environment.</p>
</li>
<li><p><strong>Developer 2</strong> – A new employee who has just joined and needs to set up the project from scratch.</p>
</li>
</ul>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738801055565/de5f91c4-4db7-47fb-a97c-16f2bd089dcd.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>Developer 1</strong>, who has been working on the project from the beginning, has already set up all the necessary environments. Since this is a Go project, he runs the server locally using the following versions of key dependencies:</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738801665997/268d347b-357d-439c-ab9a-7f1048fc92ba.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Now, the real challenge begins when <strong>Developer 2</strong> (a new employee) joins the team and needs to run the project on his system.</p>
<h4 id="heading-challenges-faced-by-the-new-developer">Challenges Faced by the New Developer</h4>
<ol>
<li><p><strong>Manual Setup Hassle</strong></p>
<ul>
<li><p>The new developer first clones the project from the repository.</p>
</li>
<li><p>He then needs to install multiple dependencies manually (Go, MongoDB, Redis, etc.).</p>
</li>
<li><p>In real-world projects, there are even more dependencies to install, making the setup tedious and error-prone.</p>
</li>
</ul>
</li>
<li><p><strong>Operating System Differences</strong></p>
<ul>
<li><p>Developer 1 might be using <strong>Windows</strong>, while Developer 2 has a <strong>Mac</strong>.</p>
</li>
<li><p>Some project commands may work on Windows but not on Mac.</p>
</li>
<li><p>Certain OS-specific compatibility issues might arise during the setup.</p>
</li>
</ul>
</li>
<li><p><strong>Version Mismatches</strong></p>
<ul>
<li><p>Developer 2 might install the latest versions of dependencies, which could be <strong>incompatible</strong> with the project.</p>
</li>
<li><p>Some dependencies may have breaking changes, causing unexpected issues.</p>
</li>
</ul>
</li>
<li><p><strong>Cloud Deployment Challenges</strong></p>
<ul>
<li><p>Eventually, the project will need to be deployed on the <strong>Cloud</strong>.</p>
</li>
<li><p>The cloud server might have a different environment, requiring another round of installations and conflict resolution.</p>
</li>
<li><p>Even after setting everything up, there’s no guarantee that the project will work as expected.</p>
</li>
</ul>
</li>
<li><p><strong>Scalability Issues</strong></p>
<ul>
<li><p>Today, it’s just one new developer-facing these challenges, but as the team grows, the setup process will have to be repeated for every new team member.</p>
</li>
<li><p>Constant communication and troubleshooting with each developer become inefficient and time-consuming.</p>
</li>
</ul>
</li>
</ol>
</li>
</ul>
<p>    <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738801835525/36f07888-4daf-4230-99e4-d0732b620a10.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>This is where <strong>Docker</strong> comes into play. It provides a way to package the entire environment, ensuring that everyone (developers and cloud servers) runs the same setup without manual installations or version mismatches.</p>
</li>
<li><p>In the next section, we’ll dive into <strong>what Docker is, why it is useful, and how it solves these problems</strong>.</p>
</li>
</ul>
<hr />
<h1 id="heading-how-does-docker-resolve-this-problem">How does Docker resolve this problem?</h1>
<h2 id="heading-docker-container">Docker Container</h2>
<ul>
<li><p><strong>Docker is a platform</strong> that allows developers to create, deploy, and run applications inside <strong>containers</strong>.</p>
</li>
<li><p>What is <strong>a Container?</strong></p>
<ul>
<li>You can think of a <strong>container</strong> as a <strong>box</strong> that holds everything needed to run an application: its code, runtime, dependencies (like Go, MongoDB, Redis), and any required configurations.</li>
</ul>
</li>
<li><p>With Docker, we can create a <strong>container</strong> that includes all the necessary tools, packages, and dependencies. This container ensures that:</p>
<ul>
<li><p>Every developer gets the <strong>exact same versions</strong> of Go, MongoDB, Redis, and other dependencies.</p>
</li>
<li><p>The application runs <strong>identically on every system.</strong></p>
</li>
<li><p>The container is <strong>lightweight</strong>, meaning it doesn’t take up unnecessary resources like a full virtual machine would.</p>
</li>
</ul>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738804258230/afb417c8-8d1d-4214-b825-d3c33a1b5b04.png" alt class="image--center mx-auto" /></p>
<ul>
<li>As you can see, this resolves the very big issue of installing and setting up the project environment again and again, and we can save our developers from the manual errors that they can make, as seen above.</li>
</ul>
<blockquote>
<p>To conclude, With Docker, we don’t have to install things like MongoDB or Redis manually again and again. Instead, we can run them inside containers and start using them instantly!</p>
</blockquote>
<ul>
<li>Okay, so now that we understand containers, there is one more term that we need to understand, which is Images.</li>
</ul>
<hr />
<h1 id="heading-docker-images">Docker Images</h1>
<ul>
<li><p>A <strong>Docker image</strong> is like a <strong>recipe</strong> that contains all the necessary ingredients and instructions to prepare a dish. However, instead of food, a <strong>Docker image</strong> contains everything needed to set up and run a software application.</p>
<h2 id="heading-understanding-docker-images-with-an-example">Understanding Docker Images with an Example</h2>
<ul>
<li><p>Imagine you have a laptop. Without an <strong>operating system</strong> (Windows, Mac, or Linux), your laptop is just a piece of hardware that can’t do much. Similarly, a <strong>Docker container</strong> needs a <strong>Docker image</strong> to function.</p>
</li>
<li><p>Think of a <strong>Docker image</strong> as the <strong>"operating system"</strong> for a container. It includes:</p>
<ul>
<li><p>The <strong>application code</strong></p>
</li>
<li><p>All the <strong>dependencies</strong> (like Go, MongoDB, Redis, etc.)</p>
</li>
<li><p>The <strong>runtime environment</strong></p>
</li>
<li><p>Any necessary <strong>configurations</strong></p>
</li>
</ul>
</li>
<li><p>Once a Docker image is created, it serves as a <strong>blueprint</strong> to generate multiple <strong>containers</strong>. Each container runs independently but follows the same instructions defined in the image, just like you can cook the same dish multiple times using the same recipe.</p>
</li>
<li><p>With Docker images, developers can ensure consistency across different environments, making application deployment seamless and reliable.</p>
</li>
</ul>
</li>
</ul>
<p>    <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738807602410/ce121aa0-db24-4bcb-8aef-cf146af4bf34.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>I hope everything is clear now.</p>
</li>
<li><p>If not, then don’t worry; we are now going to do some hands-on exercises and see everything that we have learned so far in action.</p>
</li>
</ul>
<hr />
<h1 id="heading-docker-installation">Docker Installation</h1>
<h2 id="heading-docker-desktop">Docker Desktop</h2>
<ul>
<li><p>To run <strong>Docker containers</strong> on your machine, you need to install the <strong>Docker Desktop</strong> application.</p>
<p>  You can download it from the official website:</p>
</li>
<li><p><a target="_blank" href="https://www.docker.com/">Docker Desktop Installation</a></p>
</li>
<li><p>Or simply search <strong>“Docker Desktop install”</strong> online, and it will take you to the installation page.</p>
</li>
<li><p><strong>Docker Desktop</strong> is a <strong>GUI (Graphical User Interface)</strong> that makes it easy to:</p>
<ul>
<li><p>Manage <strong>containers</strong></p>
</li>
<li><p>View and control <strong>Docker images</strong></p>
</li>
<li><p>Monitor <strong>volumes</strong> and <strong>networks</strong></p>
</li>
<li><p>Configure <strong>Docker settings</strong></p>
</li>
</ul>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738886863347/3504b267-b621-430f-a1b8-8f572ebaa961.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>It provides a user-friendly way to interact with Docker without using only command-line tools, making container management more accessible.</p>
</li>
<li><p>Once you are done with the installation, open the terminal of your choice and run <code>docker</code> command</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738887424428/741f9615-ccda-49c0-839e-6a0953f4d2bd.png" alt class="image--center mx-auto" /></p>
<ul>
<li>It should show a similar result. This tells us that docker is successfully installed in our system. You can also verify using.</li>
</ul>
<pre><code class="lang-go">docker --version
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738887737906/3e9037d6-d66d-4620-b8f5-3da748c9bc59.png" alt class="image--center mx-auto" /></p>
<ul>
<li>Now open your Docker desktop.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738887841617/c17eafdf-db9b-4a44-8337-9f976f355ff3.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>If you open <strong>Docker Desktop</strong>, you’ll notice several sections on the left-hand side:</p>
<ul>
<li><p><strong>Containers</strong> – Shows running and stopped containers.</p>
</li>
<li><p><strong>Images</strong> – Lists downloaded and built Docker images.</p>
</li>
<li><p><strong>Volumes</strong> – Manages persistent data storage for containers.</p>
</li>
<li><p><strong>Builds</strong> – Tracks built images and processes.</p>
</li>
</ul>
</li>
<li><p>We've already covered <strong>Containers</strong> and <strong>Images</strong>, and that’s all we need to get started!</p>
</li>
<li><p>Okay, so for our upcoming project, we’ll need <strong>MongoDB</strong> and <strong>Redis</strong> as dependencies. Instead of <strong>manually installing</strong> them and setting up everything from scratch (which is boring and time-consuming 😩), we can use <strong>Docker</strong> to simplify the process.</p>
</li>
<li><p>With Docker, we can:</p>
<ul>
<li><p>Pull <strong>pre-built images</strong> of MongoDB and Redis.</p>
</li>
<li><p>Run them as <strong>containers</strong>.</p>
</li>
<li><p>Avoid dependency conflicts or setup issues.</p>
</li>
</ul>
</li>
<li><p>This means <strong>no more frustrating installations</strong>, we just run a command, and everything works!</p>
</li>
<li><p>Before that, let’s understand what Docker hub is.</p>
</li>
</ul>
<hr />
<h1 id="heading-docker-hub">Docker Hub</h1>
<ul>
<li>You might be wondering when I say we need to pull Docker images for MongoDB and Redis, where these images actually come from. It comes from Docker Hub. Docker hub is similar to Github but for docker images.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738888750254/59ef5588-7192-4735-934c-9de49613bc84.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>It’s an online repository where developers can find and share Docker images.</p>
</li>
<li><p>You can also create your custom image on your system and push it to the docker hub for others to use. We are going to create a custom image for our Go application and push it to the docker hub in the future.</p>
</li>
<li><p>What we, as developers, have to do is just pull these images using the command <code>docker pull</code>, and we are all set.</p>
</li>
</ul>
<hr />
<h1 id="heading-running-mongodb-in-a-docker-container">Running MongoDB in a Docker Container</h1>
<ul>
<li>Instead of manually installing MongoDB, we can use <strong>Docker</strong> to set it up in seconds with the following command:</li>
</ul>
<pre><code class="lang-powershell">docker run <span class="hljs-literal">-d</span> -<span class="hljs-literal">-name</span> mongo<span class="hljs-literal">-demo</span> \
<span class="hljs-literal">-e</span> MONGO_INITDB_ROOT_USERNAME=admin \
<span class="hljs-literal">-e</span> MONGO_INITDB_ROOT_PASSWORD=admin \
<span class="hljs-literal">-p</span> <span class="hljs-number">27017</span>:<span class="hljs-number">27017</span>
mongo:<span class="hljs-number">8.0</span>
</code></pre>
<ul>
<li><p>Let’s understand this command:</p>
<ul>
<li><p><code>docker run -d</code> : It runs this container in a detached mode. This means that if we do not provide this tag, our terminal will be in use, and we won’t be able to perform any other operations. So, we need to run this container in the background</p>
</li>
<li><p><code>—name mongo-demo</code> : We are naming our container as ‘mongo-demo’</p>
</li>
<li><p>Now, for security purposes, we are setting the Username and Password for our database using <code>-e MONGO_INITDB_ROOT_USERNAME</code> and <code>-e MONGO_INITDB_ROOT_PASSWORD</code></p>
</li>
<li><p>We are also mentioning the <code>port</code> We want Mongo to run locally in our system.</p>
</li>
</ul>
</li>
</ul>
<blockquote>
<h2 id="heading-port-mapping">PORT MAPPING</h2>
<ul>
<li><p>When we run MongoDB inside a <strong>Docker container</strong>, it operates in an <strong>isolated environment</strong>. This means it <strong>does not</strong>automatically connect to our <strong>local machine</strong>.</p>
</li>
<li><p>If we try to access MongoDB <strong>from outside the container</strong>, it won’t work unless we <strong>explicitly expose its port</strong>.</p>
</li>
<li><p>This is where <strong>Port Mapping</strong> comes in!</p>
</li>
</ul>
<p>Here, <code>-p 27017:27017</code> means:</p>
<ul>
<li><p>The first <code>27017</code> (before the <code>:</code>) is the <strong>port on your local machine</strong> (host).</p>
</li>
<li><p>The second <code>27017</code> (after the <code>:</code>) is the <strong>port inside the container</strong>.</p>
</li>
</ul>
<p>By default, services running <strong>inside a Docker container</strong> are <strong>not accessible</strong> from outside.</p>
<ul>
<li><p>MongoDB <strong>inside the container</strong> is running on <strong>port 27017</strong>.</p>
</li>
<li><p>But our system (local machine) <strong>doesn’t know that</strong> unless we explicitly expose it.</p>
</li>
<li><p>By mapping <strong>MongoDB’s internal port (27017) to our system’s port (27017)</strong>, we make it accessible.</p>
</li>
</ul>
</blockquote>
<ul>
<li><p>At the end, we are specifying which image to pull, and that is <code>mongo:8.0</code></p>
</li>
<li><p>If you search Mongo in the docker hub, you will find its image shown below.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738889210463/8878f7c9-0c9e-437e-9b42-d01f65d206ec.png" alt class="image--center mx-auto" /></p>
</li>
</ul>
<ul>
<li><p>What that above command will do is :</p>
<ul>
<li><p>Pulls the <strong>MongoDB 8.0 image</strong> from Docker Hub (if not already downloaded).</p>
</li>
<li><p>Starts a <strong>MongoDB container</strong> with a <strong>default admin username and password</strong>.</p>
</li>
<li><p>Allows us to interact with MongoDB just like we would on a locally installed version, but without the manual setup!</p>
</li>
</ul>
</li>
<li><p>Once it is done, you will see this container running in your Docker Desktop.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738889497178/c0d717fa-f141-42ac-98bd-4451c91cece9.png" alt class="image--center mx-auto" /></p>
<ul>
<li>To check if it’s running or not, you can run the below command</li>
</ul>
<pre><code class="lang-powershell">docker <span class="hljs-built_in">ps</span>
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738889831317/34e37744-3aee-40ea-8aef-5fe88d886c3d.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>This will list all the running containers</p>
</li>
<li><p>Now, let’s run the Redis container</p>
</li>
</ul>
<hr />
<h1 id="heading-running-redis-in-docker-container">Running Redis in Docker Container</h1>
<ul>
<li>Just like MongoDB, we can set up <strong>Redis</strong> instantly using Docker without manually installing anything. Use the following command:</li>
</ul>
<pre><code class="lang-powershell">docker run <span class="hljs-literal">-d</span> -<span class="hljs-literal">-name</span> redis<span class="hljs-literal">-demo</span> redis:latest
</code></pre>
<ul>
<li><p>This will pull the <strong>latest Redis image</strong> (if not already available locally) and then start a <strong>Redis container</strong> that runs in the background.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738891888914/8ac8bb10-b5fd-4226-9d10-4b364b736d3c.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Again, to verify whether it’s running or not, let’s run <code>docker ps</code></p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738891941701/b5bcb82f-23d4-47ba-84c4-34e9e87d3ea4.png" alt class="image--center mx-auto" /></p>
<ul>
<li>As you can see, our Mongo and Redis containers are working pretty well.</li>
</ul>
<blockquote>
<p>Now that we have <strong>MongoDB and Redis running inside containers</strong>, we don’t need to install them manually on our system. Anytime we need them, we can just <strong>start the containers</strong>, and everything will work instantly.</p>
</blockquote>
<hr />
<h1 id="heading-wrapping-up">Wrapping Up</h1>
<ul>
<li>With Docker, we’ve reduced the hassle of manual installations and ensured a <strong>consistent</strong> and <strong>error-free</strong> development environment. No more dependency issues or version conflicts, just a simple setup in seconds!</li>
</ul>
<hr />
<h1 id="heading-whats-next">What’s Next?</h1>
<ul>
<li><p>This blog serves as the <strong>foundation</strong> for the next steps in our series. Now that you understand docker and how it works. We are ready to containerize our Go application.</p>
</li>
<li><p>Stay tuned for the next part! 🚀 Until then.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711803084752/aa266313-a315-41a8-9538-8ff4370577fb.gif?auto=format,compress&amp;gif-q=60&amp;format=webm&amp;auto=format,compress&amp;gif-q=60&amp;format=webm&amp;auto=format,compress&amp;gif-q=60&amp;format=webm&amp;auto=format,compress&amp;gif-q=60&amp;format=webm&amp;auto=format,compress&amp;gif-q=60&amp;format=webm" alt class="image--center mx-auto" /></p>
<ul>
<li>Connect with me on <a target="_blank" href="https://www.linkedin.com/in/dhruv-nakum-4b1054176/"><strong>LinkedIn</strong></a><strong>,</strong> <a target="_blank" href="https://github.com/red-star25"><strong>Github</strong></a><strong>, and</strong> <a target="_blank" href="https://twitter.com/dhruv_nakum"><strong>Twitter</strong></a><strong>.</strong></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Step-by-Step Guide to Secure Authentication in Go using JWT and Fiber]]></title><description><![CDATA[Introduction

After the last series on Go, I've been learning some new ideas, like Secure Authentication. I discovered JWT authentication and how it helps secure routes.

In this article, I'll explain how we can use JWT to authenticate users and secu...]]></description><link>https://dhruvnakum.xyz/step-by-step-guide-to-secure-authentication-in-go-using-jwt-and-fiber</link><guid isPermaLink="true">https://dhruvnakum.xyz/step-by-step-guide-to-secure-authentication-in-go-using-jwt-and-fiber</guid><category><![CDATA[Go Language]]></category><category><![CDATA[APIs]]></category><category><![CDATA[backend]]></category><category><![CDATA[JWT]]></category><category><![CDATA[authentication]]></category><category><![CDATA[development]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Developer]]></category><dc:creator><![CDATA[Dhruv Nakum]]></dc:creator><pubDate>Wed, 15 Jan 2025 20:48:28 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1736973584310/9933a1fc-6b91-4919-97b3-8cfb6e2ec3a0.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-introduction">Introduction</h1>
<ul>
<li><p>After the last series on Go, I've been learning some new ideas, like Secure Authentication. I discovered JWT authentication and how it helps secure routes.</p>
</li>
<li><p>In this article, I'll explain how we can use JWT to authenticate users and secure routes.</p>
</li>
<li><p>So, let's jump right in.</p>
</li>
</ul>
<hr />
<h1 id="heading-creating-a-base-folder-structure">Creating a Base Folder Structure</h1>
<ul>
<li><p>We'll start by setting up a project structure to clearly see how everything fits together.</p>
</li>
<li><p>First, create a directory and open your favorite code editor. I prefer VS Code.</p>
</li>
</ul>
<pre><code class="lang-bash">bmkdir go-auth
<span class="hljs-built_in">cd</span> go-auth
code .
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736891030948/06a8af76-f508-4a1f-9e37-31fce7059cf4.png" alt class="image--center mx-auto" /></p>
<ul>
<li>So this is our project structure :</li>
</ul>
<p><strong>cmd/server/main.go</strong></p>
<ul>
<li>This is the entry point of the application</li>
</ul>
<p><strong>controllers/auth_controller.go</strong></p>
<ul>
<li>This is where we place our controllers/handlers. In our case, it will be <strong>auth_controller</strong>. Here we will implement all the functions related to authentication like Register, Login, and Logout.</li>
</ul>
<p><strong>database/db.go</strong></p>
<ul>
<li>In this database, setup is done and the connection is made. We will be using <strong>MySQL</strong> as our database.</li>
</ul>
<p><strong>middleware/middleware.go</strong></p>
<ul>
<li>Middleware is simply a function that wraps around our HTTP handlers to add extra features. For example, once a user is logged in, we use middleware to secure other routes like /dashboard and /cart. We will learn about middleware in this blog.</li>
</ul>
<p><strong>models/user.go</strong></p>
<ul>
<li>The model represents the data structure of the application’s data. So for example, if you are building an application related to a library, models can be, a book, author, etc. In this project which we are building, we need <code>user</code> a model as we are dealing with the user’s authentication.</li>
</ul>
<p><strong>routes/routes.go</strong></p>
<ul>
<li>We define all the HTTP routes here. We are using <code>fiber</code> a package for managing the application routes. If you are familiar with express in Node.js then this package is inspired by it and provides flexible ways and functionalities to manage routes.</li>
</ul>
<p><strong>types/types.go</strong></p>
<ul>
<li>In this, we define our own custom struct types which we require in our application.</li>
</ul>
<p><strong>.env</strong></p>
<ul>
<li>This is an environmental file where we put all the secure stuff.</li>
</ul>
<hr />
<h1 id="heading-load-env-file">Load <code>.env</code> File</h1>
<ul>
<li><p>First of all, let’s load all the environmental variables in our application.</p>
</li>
<li><p>For that, we are using a package called <code>gotdotenv</code>. This helps us achieve this.</p>
</li>
</ul>
<pre><code class="lang-go"><span class="hljs-keyword">go</span> get github.com/joho/godotenv
</code></pre>
<ul>
<li>Let’s load this env file inside our application. Go to <code>main.go</code> and paste below code</li>
</ul>
<pre><code class="lang-go"><span class="hljs-keyword">import</span> <span class="hljs-string">"github.com/joho/godotenv"</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    err := godotenv.Load()
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        log.Fatal(<span class="hljs-string">"Error loading .env file"</span>)
    }
}
</code></pre>
<ul>
<li>We place it at the top of the main function because we need all the variables loaded before the server starts.</li>
</ul>
<hr />
<h1 id="heading-connect-database">Connect Database</h1>
<ul>
<li><p>We are using MySQL to work with the database. I was going through some queries and everything as It’s been a while since I wrote any. And then I came across the term ORM (Object-Relation Mapping). And I found it super convenient as we can use objects oriented approach to execute the queries.</p>
</li>
<li><p>So an <strong>ORM</strong> is a programming technique that allows us to interact with a relational database like MySQL using object-oriented programming principles, instead of writing raw SQL queries. It serves as a bridge between the object-oriented language and the database, mapping database tables to objects in the application.</p>
</li>
<li><p>And researching more about it I found this package called <code>gorm</code> for Go. So we will be using this to work with our database.</p>
</li>
<li><p>Let’s install it and also we need to install the mysql driver for it separately.</p>
</li>
</ul>
<pre><code class="lang-go"><span class="hljs-keyword">go</span> get -u gorm.io/gorm
<span class="hljs-keyword">go</span> get gorm.io/driver/mysql
</code></pre>
<ul>
<li>Let’s now create <code>Connect</code> function and write the logic to connect our application with the database.</li>
</ul>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> database

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"fmt"</span>
    <span class="hljs-string">"os"</span>

    <span class="hljs-string">"gorm.io/driver/mysql"</span>
    <span class="hljs-string">"gorm.io/gorm"</span>
)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Connect</span><span class="hljs-params">()</span> <span class="hljs-title">error</span></span> {
    dsn := fmt.Sprintf(<span class="hljs-string">"%s:%s@tcp(127.0.0.1:3306)/go_auth?charset=utf8mb4&amp;parseTime=True&amp;loc=Local"</span>, os.Getenv(<span class="hljs-string">"DB_USERNAME"</span>), os.Getenv(<span class="hljs-string">"DB_PASSWORD"</span>))
}
</code></pre>
<ul>
<li><p>Inside <code>Connect()</code> we first initilized <code>dsn</code> (Data Source Name). <code>dsn</code> is nothing but a string that provides all the information to connect the database. It includes information like database type, server location, username password, database name, etc.</p>
</li>
<li><p>Here we used <code>fmt.Sprintf</code> which returns a formatted string. We used two string <code>%s</code> placeholders for username and password. And we are getting those values from the <code>.env</code> file.</p>
</li>
<li><p>Let’s first add those values to our <code>.env</code></p>
</li>
</ul>
<pre><code class="lang-go"><span class="hljs-comment">// .env</span>
DB_USERNAME=root
DB_PASSWORD=root123
</code></pre>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Connect</span><span class="hljs-params">()</span> <span class="hljs-title">error</span></span> {
    dsn := fmt.Sprintf(<span class="hljs-string">"%s:%s@tcp(127.0.0.1:3306)/go_auth?charset=utf8mb4&amp;parseTime=True&amp;loc=Local"</span>, os.Getenv(<span class="hljs-string">"DB_USERNAME"</span>), os.Getenv(<span class="hljs-string">"DB_PASSWORD"</span>))
    db, err := gorm.Open(mysql.Open(dsn), &amp;gorm.Config{})
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> err
    }
}
</code></pre>
<ul>
<li><p>After that, we open the connection to the MySQL database using Gorm's <code>Open()</code> function. We use the <code>mysql</code> driver's <code>Open</code> function to connect with our <code>dsn</code> string. The second parameter is for gorm configuration if you have any.</p>
</li>
<li><p>This <code>gorm.Open</code> function returns two things: <code>db</code> instance and <code>error</code>. So what we need to do is, first we need to create a global <code>gorm.DB</code> variable that our application can use from anywhere inside the project.</p>
</li>
</ul>
<pre><code class="lang-go"><span class="hljs-keyword">var</span> DB *gorm.DB

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Connect</span><span class="hljs-params">()</span> <span class="hljs-title">error</span></span> {
    <span class="hljs-comment">// ...</span>
}
</code></pre>
<ul>
<li>And that’s why we create <code>var DB *gorm.DB</code> .It’s a pointer because we need whatever changes we made in this variable, it reflects the original one which is <code>db</code> inside the <code>Connect</code> function.</li>
</ul>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Connect</span><span class="hljs-params">()</span> <span class="hljs-title">error</span></span> {
    dsn := fmt.Sprintf(<span class="hljs-string">"%s:%s@tcp(127.0.0.1:3306)/go_auth?charset=utf8mb4&amp;parseTime=True&amp;loc=Local"</span>, os.Getenv(<span class="hljs-string">"DB_USERNAME"</span>), os.Getenv(<span class="hljs-string">"DB_PASSWORD"</span>))
    db, err := gorm.Open(mysql.Open(dsn), &amp;gorm.Config{})
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> err
    }

    log.Info(<span class="hljs-string">"Database connected successfully"</span>)
    DB = db

    db.AutoMigrate(&amp;models.User{})
    <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
}
</code></pre>
<ul>
<li><p>And that is why we assigned <code>db</code> to global <code>DB</code> variable.</p>
</li>
<li><p>Then we have this <code>db.AutoMigrate(&amp;User{})</code> function. What this AutoMirgrate function does is, it creates the same table with all the values defined inside this model <code>User</code> inside the database.</p>
</li>
<li><p>This means that GORM will ensure that the table corresponding to the <code>User</code> model in the database matches the structure defined in the model, creating or updating columns as necessary.</p>
</li>
<li><p>And so when you run the application for the first time, it will create a table for you automatically.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736931374997/8838aeb1-23a8-48ed-a0fb-11caa339f022.png" alt class="image--center mx-auto" /></p>
<ul>
<li>We haven’t created our User model yet so let’s create it first.</li>
</ul>
<hr />
<h1 id="heading-user-model">User Model</h1>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> models

<span class="hljs-keyword">type</span> User <span class="hljs-keyword">struct</span> {
    Id       
    Name    
    Email    
    Password
}
</code></pre>
<ul>
<li>Here we’ve created a simple User struct, which contains basic information like ID, Name Email, and Password.</li>
</ul>
<hr />
<ul>
<li>Now that we have the database file ready we can use the Connect function inside our main function to connect the database.</li>
</ul>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    err := godotenv.Load()
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        log.Fatal(<span class="hljs-string">"Error loading .env file"</span>)
    }

    <span class="hljs-keyword">if</span> err = database.Connect(); err != <span class="hljs-literal">nil</span> {
        log.Fatalf(<span class="hljs-string">"Could not connect to the database, %v"</span>, err)
    }
}
</code></pre>
<ul>
<li>Now let’s run the project to see if it’s working</li>
</ul>
<pre><code class="lang-go"><span class="hljs-keyword">go</span> run cmd/server/main.<span class="hljs-keyword">go</span>
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736895776659/89b1ca4e-edd0-445a-9f93-4883d4439220.png" alt class="image--center mx-auto" /></p>
<hr />
<h1 id="heading-setting-up-the-router">Setting Up the Router</h1>
<ul>
<li>Okay so for route management, as we discussed earlier we are using <code>fiber</code> package. To install run the following command inside your terminal</li>
</ul>
<pre><code class="lang-go"><span class="hljs-keyword">go</span> get github.com/gofiber/fiber/v2
</code></pre>
<ul>
<li>Let’s create an instance of fiber inside <code>main.go</code> to use it inside our application</li>
</ul>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    <span class="hljs-comment">//...</span>

    app := fiber.New()
}
</code></pre>
<ul>
<li><p>Using this <code>app</code> we can now create our routes.</p>
</li>
<li><p>To make things look clean and separate we will implement all the routes inside <code>routes.go</code>.</p>
</li>
</ul>
<pre><code class="lang-go"><span class="hljs-comment">// routes.go</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">SetupRoutes</span><span class="hljs-params">(app *fiber.App)</span></span> {
    app.Post(<span class="hljs-string">"/api/register"</span>, controllers.Register)
    app.Post(<span class="hljs-string">"/api/login"</span>, controllers.Login)
    app.Post(<span class="hljs-string">"/api/logout"</span>, controllers.Logout)
}
</code></pre>
<ul>
<li><p>Here we have created <code>SetupRoutes</code> a function that takes a <code>app</code> variable of type <code>*fiber.App</code>.</p>
</li>
<li><p>Inside this, we created three routes, <code>/register</code>, <code>login/</code> and <code>/logout</code>. (Don’t worry if you are seeing an error saying these functions are not found. We are going to implement them in a bit)</p>
</li>
<li><p>Now we call this function in <code>main.go</code> like this</p>
</li>
</ul>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    <span class="hljs-comment">//...</span>

    routes.SetupRoutes(app)
}
</code></pre>
<ul>
<li>Now let’s implement these handler functions inside controllers.</li>
</ul>
<hr />
<h1 id="heading-implementing-route-handlers">Implementing Route Handlers</h1>
<h2 id="heading-register">Register</h2>
<pre><code class="lang-go"><span class="hljs-comment">// controllers/auth_controller.go</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Register</span><span class="hljs-params">(c *fiber.Ctx)</span> <span class="hljs-title">error</span></span> {
    <span class="hljs-keyword">var</span> regReq types.RegisterRequest
}
</code></pre>
<ul>
<li><p>Since we receive the submitted data in an HTTP request, we need to create a custom struct type to convert all that data into a format that Go can understand.</p>
</li>
<li><p>Let’s create a RegisterRequest struct for the Register handler</p>
</li>
</ul>
<pre><code class="lang-go"><span class="hljs-comment">// types/types.go</span>

<span class="hljs-keyword">type</span> RegisterRequest <span class="hljs-keyword">struct</span> {
    Name    
    Email   
    Password
}
</code></pre>
<ul>
<li>Let’s now parse the request body of the register to this type</li>
</ul>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Register</span><span class="hljs-params">(c *fiber.Ctx)</span> <span class="hljs-title">error</span></span> {
    <span class="hljs-keyword">var</span> regReq types.RegisterRequest

    <span class="hljs-keyword">if</span> err := c.BodyParser(&amp;regReq); err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
            <span class="hljs-string">"message"</span>: <span class="hljs-string">"invalid request format"</span>,
        })
    }
}
</code></pre>
<ul>
<li><p>Function <code>BodyParser</code> helps bind the body fields to a struct field. It returns an <code>error</code> of type <code>ErrUnprocessableEntity</code> which means if BodyParser fails to bind the fields it will throw this error.</p>
</li>
<li><p>So we handled it by returning an error message “invalid request format“ with a status code 500 (StatusInternalServerError)</p>
</li>
<li><p>Now before we create our user and send it to the database to store, we can’t just send the <code>password</code> as it is. We need to secure it by converting it into some hash or something.</p>
</li>
<li><p>Go has this package called <code>bcrypt</code> which helps us achieve that. First, let’s install the package</p>
</li>
</ul>
<pre><code class="lang-go"><span class="hljs-keyword">go</span> get golang.org/x/crypto/bcrypt
</code></pre>
<ul>
<li><p><code>bcrypt</code> has a function <code>GenerateFromPassword</code> that takes a byte of <code>password</code> string as the first parameter and <code>cost</code> as the second parameter (<code>cost</code> determines the computational complexity of the hashing process. It is a measure of how long it takes to hash a password, directly affecting the time it takes to verify the hash later)</p>
</li>
<li><p>Let’s hash our password and then create a user object</p>
</li>
</ul>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Register</span><span class="hljs-params">(c *fiber.Ctx)</span> <span class="hljs-title">error</span></span> {
    <span class="hljs-comment">//...</span>

    password, _ := bcrypt.GenerateFromPassword([]<span class="hljs-keyword">byte</span>(regReq.Password), <span class="hljs-number">10</span>)

    user := models.User{
        Name:     regReq.Name,
        Email:    regReq.Email,
        Password: password,
    }
}
</code></pre>
<ul>
<li><p>This bcrypt function returns two values: encrypted password and error.</p>
</li>
<li><p>And after that, we can construct our user model by using <code>regReq</code> struct and encrypted <code>password</code>.</p>
</li>
<li><p>Now before we send this to the database, we need to first check whether this user already exists in the database or not.</p>
</li>
</ul>
<pre><code class="lang-go">
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Register</span><span class="hljs-params">(c *fiber.Ctx)</span> <span class="hljs-title">error</span></span> {
    <span class="hljs-comment">// ...   </span>

    database.DB.Where(<span class="hljs-string">"email = ?"</span>, user.Email).First(&amp;user)
}
</code></pre>
<ul>
<li><p>We can do this using the <code>Where</code> function of the MySQL database. Since each email is unique in our application, we wrote a query that returns a user if they exist.</p>
</li>
<li><p>Now we check if we found anything or not like this:</p>
</li>
</ul>
<pre><code class="lang-go">
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Register</span><span class="hljs-params">(c *fiber.Ctx)</span> <span class="hljs-title">error</span></span> {
    <span class="hljs-comment">// ...   </span>

    database.DB.Where(<span class="hljs-string">"email = ?"</span>, user.Email).First(&amp;user)

    <span class="hljs-keyword">if</span> user.Id != <span class="hljs-number">0</span> {
        <span class="hljs-keyword">return</span> c.Status(fiber.StatusConflict).JSON(fiber.Map{
            <span class="hljs-string">"message"</span>: <span class="hljs-string">"user already exists"</span>,
        })
    }
}
</code></pre>
<ul>
<li><p>If <code>user.Id</code> is not <code>0</code>, it means we found a user, so we return an error message saying "user already exists."</p>
</li>
<li><p>If we didn't find anything, we create a new user in the database using the <code>Create()</code> function of <code>DB</code>.</p>
</li>
</ul>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Register</span><span class="hljs-params">(c *fiber.Ctx)</span> <span class="hljs-title">error</span></span> {
    <span class="hljs-comment">// ...</span>

    database.DB.Create(&amp;user)
}
</code></pre>
<ul>
<li>And once everything is done, we send a message back to the API response saying "user created successfully" along with the user information.</li>
</ul>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Register</span><span class="hljs-params">(c *fiber.Ctx)</span> <span class="hljs-title">error</span></span> {
    <span class="hljs-comment">// ...</span>

    <span class="hljs-keyword">return</span> c.JSON(fiber.Map{
            <span class="hljs-string">"message"</span>: <span class="hljs-string">"user created successfully"</span>,
            <span class="hljs-string">"data"</span>:    user,
        })
}
</code></pre>
<ul>
<li>That was pretty straightforward, wasn’t it?</li>
</ul>
<h1 id="heading-running-the-server">Running the Server</h1>
<ul>
<li>To run the server, we use the <code>app</code>'s <code>Listen</code> function and specify the port number like this:</li>
</ul>
<pre><code class="lang-go"><span class="hljs-comment">// main.go</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    <span class="hljs-comment">//...</span>

    log.Println(<span class="hljs-string">"Starting server at: "</span>, <span class="hljs-number">3000</span>)
    err = app.Listen(<span class="hljs-string">":3000"</span>)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        log.Fatalln(<span class="hljs-string">"Error Starting Server at: "</span>, <span class="hljs-number">3000</span>)
    }
}
</code></pre>
<ul>
<li>Now let’s run the app</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736920984238/293f89d4-7edc-4ca8-94ce-205224c96e7e.png" alt class="image--center mx-auto" /></p>
<hr />
<h2 id="heading-login">Login</h2>
<pre><code class="lang-go"><span class="hljs-comment">// controllers/auth_controller.go</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Login</span><span class="hljs-params">(c *fiber.Ctx)</span> <span class="hljs-title">error</span></span> {
    <span class="hljs-keyword">var</span> loginReq types.LoginRequest

    <span class="hljs-keyword">if</span> err := c.BodyParser(&amp;loginReq); err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> err
    }

    <span class="hljs-keyword">if</span> err := validate.Struct(&amp;loginReq); err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
            <span class="hljs-string">"message"</span>: <span class="hljs-string">"Validation failed"</span>,
            <span class="hljs-string">"errors"</span>:  err.Error(),
        })
    }
}
</code></pre>
<ul>
<li>This part of the Login remains the same as the Register. We have created a LoginRequest structure inside <code>types.go</code></li>
</ul>
<pre><code class="lang-go"><span class="hljs-comment">// types/types.go</span>

<span class="hljs-keyword">package</span> types

...

<span class="hljs-keyword">type</span> LoginRequest <span class="hljs-keyword">struct</span> {
    Email 
    Password
}
</code></pre>
<ul>
<li><p>Now, we need to check if the user exists in the database when the client tries to log in with their credentials.</p>
</li>
<li><p>To do this, we will use the <code>Where()</code> function of <code>DB</code> to see if the email is in the database. If it exists (we check this by seeing if <code>user.Id</code> is not <code>0</code>), we proceed with further operations otherwise, we return an error.</p>
</li>
</ul>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Login</span><span class="hljs-params">(c *fiber.Ctx)</span> <span class="hljs-title">error</span></span> {    
     <span class="hljs-comment">// ...</span>

    <span class="hljs-keyword">var</span> user models.User

    database.DB.Where(<span class="hljs-string">"email = ?"</span>, loginReq.Email).First(&amp;user)

    <span class="hljs-keyword">if</span> user.Id == <span class="hljs-number">0</span> {
        c.Status(fiber.StatusNotFound)
        <span class="hljs-keyword">return</span> c.JSON(fiber.Map{
            <span class="hljs-string">"message"</span>: <span class="hljs-string">"user not found"</span>,
        })
    }
}
</code></pre>
<ul>
<li>If the user exists, we compare the incoming password with the one in the database. If they match, the credentials are correct. If they don't match, we return an error saying "incorrect password."</li>
</ul>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Login</span><span class="hljs-params">(c *fiber.Ctx)</span> <span class="hljs-title">error</span></span> {    
     <span class="hljs-comment">// ...</span>
     <span class="hljs-keyword">if</span> err := bcrypt.CompareHashAndPassword(user.Password, []<span class="hljs-keyword">byte</span>(loginReq.Password)); err != <span class="hljs-literal">nil</span> {
        c.Status(fiber.StatusBadRequest)
        <span class="hljs-keyword">return</span> c.JSON(fiber.Map{
            <span class="hljs-string">"message"</span>: <span class="hljs-string">"incorrect password"</span>,
        })
    }
}
</code></pre>
<ul>
<li><p><code>bcrypt</code> has a function called <code>CompareHashAndPassword</code> that checks the incoming password against the one stored in the database.</p>
</li>
<li><p>After that, we just send a JSON response with a success message and the data.</p>
</li>
</ul>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Login</span><span class="hljs-params">(c *fiber.Ctx)</span> <span class="hljs-title">error</span></span> {    
     <span class="hljs-comment">// ...</span>
    <span class="hljs-keyword">return</span> c.JSON(fiber.Map{
        <span class="hljs-string">"message"</span>: <span class="hljs-string">"success"</span>,
        <span class="hljs-string">"data"</span>:    loginReq,
    })
}
</code></pre>
<ul>
<li>Let’s check the Login handler by running the project.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736921033382/81a139f4-7fca-448a-b056-24cbff67bc86.png" alt class="image--center mx-auto" /></p>
<hr />
<h2 id="heading-logout">Logout</h2>
<ul>
<li><p>The logout feature is very simple. On the backend, there's not much to do. The frontend should handle the logout process.</p>
</li>
<li><p>So, when this route is accessed, we just return a success message.</p>
</li>
</ul>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Logout</span><span class="hljs-params">(c *fiber.Ctx)</span> <span class="hljs-title">error</span></span> {

    <span class="hljs-keyword">return</span> c.JSON(fiber.Map{
        <span class="hljs-string">"message"</span>: <span class="hljs-string">"Logged out successfully"</span>,
    })
}
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736920106216/9ba3f9b1-803b-443b-9f44-a5246f4ca2cd.png" alt class="image--center mx-auto" /></p>
<ul>
<li>One problem right now here is, that even if you aren’t logged in, you can still log out. And this issue we will resolve below by using JWT.</li>
</ul>
<hr />
<h1 id="heading-json-tags">JSON Tags</h1>
<ul>
<li>If you see the response of Login and Register…</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736921232447/a2098f0a-477a-4ca1-9a66-216cb61b47d5.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736921246555/a67e94b8-e1eb-4fe4-84f9-9bdafd0cb9d2.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>We notice that the field names are not in lowercase. To convert these fields into a JSON-friendly format, we use what are called <strong>Tags</strong> in Go.</p>
</li>
<li><p>Tags specify how struct fields should be encoded into or decoded from JSON. So, even if we have a struct field like <code>Name</code>, we can encode and decode it as <code>name</code> using an annotation.</p>
</li>
<li><p>To achieve this, we need to make changes inside <code>types.go</code> and <code>user.go</code>.</p>
</li>
</ul>
<pre><code class="lang-go"><span class="hljs-comment">// user.go</span>

<span class="hljs-keyword">package</span> models

<span class="hljs-keyword">type</span> User <span class="hljs-keyword">struct</span> {
    Id       <span class="hljs-keyword">uint</span>   <span class="hljs-string">`json:"id"`</span>
    Name     <span class="hljs-keyword">string</span> <span class="hljs-string">`json:"name"`</span>
    Email    <span class="hljs-keyword">string</span> <span class="hljs-string">`json:"email" gorm:"unique"`</span>
    Password []<span class="hljs-keyword">byte</span> <span class="hljs-string">`json:"-"`</span>
}
</code></pre>
<ul>
<li><p>Here, we made the email a unique key in the database. This means the same email address can't be used again.</p>
</li>
<li><p>Another thing to note is that we added <code>-</code> in front of <code>Password</code> because we don't want this field to appear in the response for security reasons.</p>
</li>
<li><p>Now, let's modify <code>types.go</code>.</p>
</li>
</ul>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> types

<span class="hljs-keyword">type</span> RegisterRequest <span class="hljs-keyword">struct</span> {
    Name     <span class="hljs-keyword">string</span> <span class="hljs-string">`json:"name"`</span>
    Email    <span class="hljs-keyword">string</span> <span class="hljs-string">`json:"email"`</span>
    Password <span class="hljs-keyword">string</span> <span class="hljs-string">`json:"password"`</span>
}

<span class="hljs-keyword">type</span> LoginRequest <span class="hljs-keyword">struct</span> {
    Email    <span class="hljs-keyword">string</span> <span class="hljs-string">`json:"email"`</span>
    Password <span class="hljs-keyword">string</span> <span class="hljs-string">`json:"password"`</span>
}
</code></pre>
<ul>
<li>Now if you run the project and see the response, it should be fixed</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736921680567/9a617c39-56bc-4048-9d37-20db3a10fe3f.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736921709635/ff22bb8a-a347-46ee-8d21-e8985780cdfd.png" alt class="image--center mx-auto" /></p>
<hr />
<h1 id="heading-jwt-authentication">JWT Authentication</h1>
<ul>
<li>Let’s first understand what JWT is before we dive into code.</li>
</ul>
<h2 id="heading-what-is-jwt">What is JWT?</h2>
<ul>
<li><p>JWT (JSON Web Token) is a way to safely send data between the client and server.</p>
</li>
<li><p>It is a token made up of three main parts:</p>
<ul>
<li><p><strong>Header:</strong> This includes information like the signing method and type of token.</p>
</li>
<li><p><strong>Payload:</strong> This has Claims, which are details about the user data, such as user ID and expiration time.</p>
</li>
<li><p><strong>Signature:</strong> This is made by signing the header and payload with a secret or private key. It checks if the token is real.</p>
</li>
</ul>
</li>
</ul>
<h2 id="heading-flow-of-jwt-authentication">Flow of JWT Authentication</h2>
<ul>
<li><p>The client sends credentials to the server.</p>
</li>
<li><p>If the credentials are valid, the server generates a JWT containing the user's information and signs it.</p>
</li>
<li><p>The server sends the JWT to the client, typically as part of the HTTP response.</p>
</li>
<li><p>The client stores the token (e.g., in local storage or an HTTP-only cookie).</p>
</li>
<li><p>The client includes the JWT in the <code>Authorization</code> header (ex, <code>Bearer &lt;token&gt;</code>).</p>
</li>
<li><p>The server validates the token’s signature and checks its claims (e.g., expiration time, roles).</p>
</li>
<li><p>If valid, the server processes the request; otherwise, it rejects it.</p>
</li>
</ul>
<h2 id="heading-implement-jwt-authentication">Implement JWT Authentication</h2>
<ul>
<li>To use JWT in our app, let’s first add the package</li>
</ul>
<pre><code class="lang-go"><span class="hljs-keyword">go</span> get github.com/dgrijalva/jwt-<span class="hljs-keyword">go</span>/v4
</code></pre>
<ul>
<li>In our app, when a user logs in successfully, we create a JWT token. After checking the password, we start by creating a claim to build the token.</li>
</ul>
<pre><code class="lang-go"><span class="hljs-keyword">if</span> err := bcrypt.CompareHashAndPassword(...); err != <span class="hljs-literal">nil</span> {<span class="hljs-comment">//...}</span>

claims := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.StandardClaims{
        Issuer: strconv.Itoa(<span class="hljs-keyword">int</span>(user.Id)),
        ExpiresAt: &amp;jwt.Time{
            Time: time.Now().Add(time.Hour * <span class="hljs-number">24</span>),
        },
    })
</code></pre>
<ul>
<li><p><code>jwt</code> provides a function called <code>NewWithClaims</code> to create a Claim. It takes two parameters:</p>
<ul>
<li><p><strong>Signing Method:</strong> We can choose from several secure hashing algorithms, but we are using HS256.</p>
</li>
<li><p><strong>Claim:</strong> We include the claims we mentioned earlier, like user ID and expiry time, using the <code>jwt.StandardClaims</code> interface. We set the user ID in <code>Issuer</code> as a string and <code>ExpiresAt</code> with a time value, set to 24 hours. This means the token will expire after 24 hours.</p>
</li>
<li><p>Now that we have the claims ready, we will create a token.</p>
</li>
</ul>
</li>
</ul>
<pre><code class="lang-go">token, err := claims.SignedString([]<span class="hljs-keyword">byte</span>(os.Getenv(<span class="hljs-string">"JWT_SECRET"</span>)))
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        c.Status(fiber.StatusInternalServerError)
        <span class="hljs-keyword">return</span> c.JSON(fiber.Map{
            <span class="hljs-string">"message"</span>: <span class="hljs-string">"could not log in"</span>,
        })
    }
</code></pre>
<ul>
<li>As you can see claims have a method named <code>SignedString</code> which we use to sign the JWT secret. We typically store the secret inside <code>.env</code> file.</li>
</ul>
<pre><code class="lang-go"><span class="hljs-comment">// .env</span>

<span class="hljs-comment">// ... other keys</span>
JWT_SECRET=secret
</code></pre>
<ul>
<li><p>Use a strong secret key for better security. I have used <code>secret</code> for testing purposes.</p>
</li>
<li><p>And after everything’s done we get the token.</p>
</li>
<li><p>If you remember this line:</p>
</li>
</ul>
<p>“ A JWT token is simply a mix of signed claims and secret/private keys. I hope this explanation makes it clear.”</p>
<ul>
<li>And as mentioned in the <strong>Flow of JWT part</strong> we send this token to the client so that they can send it back to the backend to access secure/private routes</li>
</ul>
<pre><code class="lang-go"><span class="hljs-comment">// auth_controller.go</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Login</span><span class="hljs-params">(c *fiber.Ctx)</span> <span class="hljs-title">error</span></span> {

<span class="hljs-comment">// ...</span>

<span class="hljs-keyword">return</span> c.JSON(fiber.Map{
        <span class="hljs-string">"message"</span>: <span class="hljs-string">"success"</span>,
        <span class="hljs-string">"token"</span>:   token,
        <span class="hljs-string">"data"</span>:    loginReq,
    })

}
</code></pre>
<ul>
<li>Okay so, now that we created the JWT token. Whenever a user tries to access private routes, we need to first validate the user, whether he is logged in or not. To do that we create a middleware</li>
</ul>
<hr />
<h1 id="heading-middlewares">Middlewares</h1>
<ul>
<li><p>In Go, we create middleware that acts as a layer between the HTTP request and the handler that processes it.</p>
</li>
<li><p>Middleware is simply a function that takes an HTTP handler and returns an HTTP handler. In our app, we need middleware to ensure the user accessing the route is authorized to access private routes before entering.</p>
</li>
<li><p>To implement it, go to <code>middleware/middlewares.go</code> and paste the code below.</p>
</li>
</ul>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> middleware

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"os"</span>

    jwtware <span class="hljs-string">"github.com/gofiber/contrib/jwt"</span>
    <span class="hljs-string">"github.com/gofiber/fiber/v2"</span>
)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Protected</span><span class="hljs-params">()</span> <span class="hljs-title">fiber</span>.<span class="hljs-title">Handler</span></span> {
    <span class="hljs-keyword">return</span> jwtware.New(jwtware.Config{
        SigningKey: jwtware.SigningKey{
            JWTAlg: <span class="hljs-string">"HS256"</span>,
            Key:    []<span class="hljs-keyword">byte</span>(os.Getenv(<span class="hljs-string">"JWT_SECRET"</span>)),
        },
        TokenLookup:  <span class="hljs-string">"header:Authorization"</span>,
        AuthScheme:   <span class="hljs-string">"Bearer"</span>,
        ErrorHandler: jwtError,
    })
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">jwtError</span><span class="hljs-params">(c *fiber.Ctx, err error)</span> <span class="hljs-title">error</span></span> {
    <span class="hljs-keyword">return</span> c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
        <span class="hljs-string">"message"</span>: <span class="hljs-string">"Unauthorized"</span>,
    })
}
</code></pre>
<ul>
<li><p>Starting with imports, we are using <code>"github.com/gofiber/contrib/jwt"</code> packages and gave aliases as <code>jwtware</code>.</p>
</li>
<li><p>Here we have created a function <code>Protected()</code> that returns an HTTP handler. Inside this we are returning <code>jwtware.New()</code> handler which is used  to create a new middleware instance for validating JWT</p>
</li>
<li><p>Here we pass different keys:</p>
<ul>
<li><p>SigningKey: This specifies the algorithm and key for verifying JWT’s signature.</p>
</li>
<li><p>TokenLookup: Defines where the middleware should look for JWT</p>
</li>
<li><p>AuthScheme: It defines which scheme to use in <code>Authorization</code></p>
</li>
<li><p>ErrorHandler: It is a custom function to handle errors. So whenever an unauthorized user access any routes which require authorization, this throws an error message: “Unauthorized “</p>
</li>
</ul>
</li>
<li><p>In our case, we need two routes: Log in and Register as Public routes and Logout as Private route. How do we do that?</p>
</li>
</ul>
<pre><code class="lang-go"><span class="hljs-comment">// routes.go</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">SetupRoutes</span><span class="hljs-params">(app *fiber.App)</span></span> {
    <span class="hljs-comment">// Public routes</span>
    app.Post(<span class="hljs-string">"/api/register"</span>, controllers.Register)
    app.Post(<span class="hljs-string">"/api/login"</span>, controllers.Login)

    app.Use(middleware.Protected())

    <span class="hljs-comment">// Protected routes</span>
    app.Post(<span class="hljs-string">"/api/logout"</span>, controllers.Logout)
}
</code></pre>
<ul>
<li><p>As you can see, to use any middleware, fiber provides a very handy method called <code>Use()</code> . So what will happen now is whatever routes defined after <code>app.Use(middleware.Protected())</code> this will require a JWT Token to get access.</p>
</li>
<li><p>Let’s see it in action.</p>
</li>
</ul>
<ol>
<li><p>Open Postman</p>
</li>
<li><p>Try to logout directly</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736929382431/16d32da0-e63f-4720-b158-736f19cf49d8.png" alt class="image--center mx-auto" /></p>
<p>As you can see, Now it is throwing an error saying “Unauthorized“.</p>
<ol start="3">
<li><p>Now login with valid credentials</p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736929416766/a3ac0569-311d-4ac7-a5f3-df92654c88b2.png" alt class="image--center mx-auto" /></p>
<p> Copy the token and paste it inside the Authorization Bearer field and then try to logout.</p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736929513628/c7f57d57-c17e-4f5a-aa5d-cdd106e002c9.png" alt class="image--center mx-auto" /></p>
</li>
</ol>
<p><img src="https://media1.tenor.com/m/9PItHYqz9gwAAAAd/yay-moinyin.gif" alt="a group of minions are standing next to each other and one of them is saying yay ." class="image--center mx-auto" /></p>
<hr />
<h1 id="heading-code">Code</h1>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/red-star25/jwt-auth-go">https://github.com/red-star25/jwt-auth-go</a></div>
<p> </p>
<hr />
<h1 id="heading-conclusion">Conclusion</h1>
<ul>
<li><p>In this article, we've explored how to implement secure user authentication in a Go application using JWTs and the Fiber framework.</p>
</li>
<li><p>We saw how to set up a project structure, connect to a MySQL database with GORM, and define essential routes and middleware.</p>
</li>
<li><p>We've built handlers for managing user registration, login, and protected routes. This approach not only enhances the security of your application but also provides a scalable framework for future development.</p>
</li>
<li><p>I hope you liked this article and learned something from it. If you did consider leaving a like and feedback in the comment section.</p>
</li>
<li><p>See you in the next one, until then….</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711803084752/aa266313-a315-41a8-9538-8ff4370577fb.gif?auto=format,compress&amp;gif-q=60&amp;format=webm&amp;auto=format,compress&amp;gif-q=60&amp;format=webm&amp;auto=format,compress&amp;gif-q=60&amp;format=webm&amp;auto=format,compress&amp;gif-q=60&amp;format=webm" alt class="image--center mx-auto" /></p>
<ul>
<li>Connect with me on <a target="_blank" href="https://www.linkedin.com/in/dhruv-nakum-4b1054176/"><strong>LinkedIn</strong></a><strong>,</strong> <a target="_blank" href="https://github.com/red-star25"><strong>Github</strong></a><strong>, and</strong> <a target="_blank" href="https://twitter.com/dhruv_nakum"><strong>Twitter</strong></a><strong>.</strong></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[From Zero to Go Hero: Learn Go Programming with Me - Part 5]]></title><description><![CDATA[Introduction

Welcome to the last part of this Learn Go Programming series. In the previous parts, we learned lots of new stuff about Go and saw how easy and fast it is to build a backend. In the last blog, we created a Read function for getting all ...]]></description><link>https://dhruvnakum.xyz/from-zero-to-go-hero-learn-go-programming-with-me-part-5</link><guid isPermaLink="true">https://dhruvnakum.xyz/from-zero-to-go-hero-learn-go-programming-with-me-part-5</guid><category><![CDATA[Go Language]]></category><category><![CDATA[backend]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Developer]]></category><category><![CDATA[REST API]]></category><dc:creator><![CDATA[Dhruv Nakum]]></dc:creator><pubDate>Sat, 11 Jan 2025 23:15:36 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1736490292529/1c56d7bd-8960-48bf-9724-52a3870d9d77.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-introduction">Introduction</h1>
<ul>
<li><p>Welcome to the last part of this Learn Go Programming series. In the previous parts, we learned lots of new stuff about Go and saw how easy and fast it is to build a backend. In the last blog, we created a Read function for getting all the books that are there in the database.</p>
</li>
<li><p>In this part, we are going to implement the remaining operations which are:</p>
<ul>
<li><p>Create Book</p>
</li>
<li><p>Update Book</p>
</li>
<li><p>Delete Book and</p>
</li>
<li><p>Get a Specific Book</p>
</li>
</ul>
</li>
<li><p>So let’s get started without wasting any more time.</p>
</li>
</ul>
<hr />
<h1 id="heading-create-a-book-post">Create a Book (POST)</h1>
<h2 id="heading-setting-up-the-route">Setting up the route</h2>
<ul>
<li><p>Now to create a book, first, let’s configure the route for it.</p>
</li>
<li><p>Go to <code>routes.go</code> and add a new handler called <code>CreateBookHandler</code></p>
</li>
</ul>
<pre><code class="lang-go"><span class="hljs-comment">// routes.go</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">SetupRouter</span><span class="hljs-params">()</span> *<span class="hljs-title">chi</span>.<span class="hljs-title">Mux</span></span> {
    ...
    r.Post(<span class="hljs-string">"/book"</span>, books.CreateBookHandler) <span class="hljs-comment">// Yet to be created inside handler.go</span>
    ...
}
</code></pre>
<ul>
<li>As we need to create something inside the database, we need to use the POST method of HTTP.</li>
</ul>
<h2 id="heading-creating-a-handler">Creating a Handler</h2>
<ul>
<li>Head over to <code>handler.go</code> and paste the below code:</li>
</ul>
<pre><code class="lang-go"><span class="hljs-comment">// handler.go</span>

...
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">CreateBookHandler</span><span class="hljs-params">(w http.ResponseWriter, r *http.Request)</span></span> {
    <span class="hljs-keyword">var</span> book Book
    err := json.NewDecoder(r.Body).Decode(&amp;book)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        http.Error(w, <span class="hljs-string">"Failed to decode book"</span>, http.StatusInternalServerError)
        <span class="hljs-keyword">return</span>
    }

    err = CreateBook(book) <span class="hljs-comment">// Yet to be created inside service.go</span>
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        http.Error(w, <span class="hljs-string">"Failed to create book"</span>, http.StatusInternalServerError)
        <span class="hljs-keyword">return</span>
    }

    w.WriteHeader(http.StatusCreated)
}
...
</code></pre>
<ul>
<li><p>So when we are creating something, we are most probably getting the data from the client side. In our case where we are creating a book, let’s say the admin wants to add a book to the website. What he will do is, add the details of the book in text fields on the front side and then press the Submit button.</p>
</li>
<li><p>So when he does that, all the data that were filled by the admin comes to the backend inside a request Body in a JSON format</p>
</li>
<li><p>So what we need to do here is, we just need to <code>decode</code> this data into our Go structure format.</p>
</li>
<li><p>For that, we used this <code>json.Decoder()</code> function which takes <code>json</code> string and then <code>Decode()</code> takes a struct in which we need to fill the converted data.</p>
</li>
<li><p>Then we need to call <code>CreateBook()</code> service of database which will add a new entry for the new book.</p>
</li>
<li><p>Let’s write this function inside <code>service.go</code></p>
</li>
</ul>
<h2 id="heading-creating-a-createbook-service">Creating a <code>CreateBook</code> Service</h2>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">CreateBook</span><span class="hljs-params">(book Book)</span> <span class="hljs-title">error</span></span> {
    _, err := database.DB.Exec(<span class="hljs-string">"INSERT INTO books (title, author, year) VALUES (?, ?, ?)"</span>, book.Title, book.Author, book.Year)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> err
    }
    <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
}
</code></pre>
<ul>
<li><p>In <code>service.go</code> file we need to create a function called <code>CreateBook</code>. This function takes a struct of type Book which we will pass from the handler. And it will return an error.</p>
</li>
<li><p>Inside the function, we are writing a create query for MySQL database. It’s self-explanatory so I am not going into details.</p>
</li>
<li><p>In the end, we are returning an error if anything goes wrong.</p>
</li>
<li><p>It was pretty simple, right?</p>
</li>
<li><p>Let’s run the project and test it inside Postman.</p>
</li>
</ul>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/i6Uhx99q4LY">https://youtu.be/i6Uhx99q4LY</a></div>
<p> </p>
<ul>
<li>I already created two books for testing before so you are also seeing those in output.</li>
</ul>
<hr />
<h1 id="heading-update-a-book-put">Update a Book (PUT)</h1>
<h2 id="heading-setting-up-the-route-1">Setting up the Route</h2>
<ul>
<li>Now that we have created a book. We can now create an Update functionality. Let’s go to the <code>routes.go</code> and create a PUT router</li>
</ul>
<pre><code class="lang-go">...
r.Put(<span class="hljs-string">"/book/{id}"</span>, books.UpdateBookHandler)
...
</code></pre>
<ul>
<li><p>We need an <code>id</code> to update a particular book, right? To do that we can use a unique identifier which is <code>id</code> for our book. So When the user/admin updates a particular book client sends the <code>id</code> of it with the request to our backend.</p>
</li>
<li><p>So if you see, after <code>/book/</code> we have <code>id</code> inside curly braces. This is a dynamic value. When a client hits a URL with this path like, <code>/book/1</code> or <code>/book/20</code>, we can get this book dynamic <code>id</code> and perform update operations.</p>
</li>
<li><p>Let’s now create <code>UpdateBook</code> handler function</p>
</li>
</ul>
<h2 id="heading-creating-a-handler-1">Creating a Handler</h2>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">UpdateBookHandler</span><span class="hljs-params">(w http.ResponseWriter, r *http.Request)</span></span> {
    id, err := strconv.Atoi(r.PathValue(<span class="hljs-string">"id"</span>))
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        http.Error(w, <span class="hljs-string">"Failed to convert id"</span>, http.StatusInternalServerError)
        <span class="hljs-keyword">return</span>
    }

    <span class="hljs-keyword">var</span> book Book
    err = json.NewDecoder(r.Body).Decode(&amp;book)

    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        http.Error(w, <span class="hljs-string">"Failed to decode book"</span>, http.StatusInternalServerError)
        <span class="hljs-keyword">return</span>
    }

    err = UpdateBook(id, book)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        http.Error(w, <span class="hljs-string">"Failed to update book"</span>, http.StatusInternalServerError)
        <span class="hljs-keyword">return</span>
    }

    w.WriteHeader(http.StatusOK)
}
</code></pre>
<ul>
<li><p>Okay so in the first line, we are getting <code>id</code> using request objects <code>PathValue</code> function. Here we pass the exact value that we passed inside the route which is <code>id</code>.</p>
</li>
<li><p>But we need to convert them <code>id</code> to <code>int</code> because our database accepts the ID of type integer.</p>
</li>
<li><p>For that, we are using <code>strconv</code> package. And it has <code>Atoi</code> (ASCII to Integer) function which takes a string and converts it to an integer. It also returns <code>err</code> if it isn’t able to convert. So we are handling that error too.</p>
</li>
<li><p>Then we decode the sent JSON data to our book struct.</p>
</li>
<li><p>In the end, we call the <code>UpdateBook</code> a function where we pass the <code>id</code> and <code>book</code> struct.</p>
</li>
<li><p>Let’s create a database service for Updating a book in the database.</p>
</li>
</ul>
<h2 id="heading-creating-a-updatebook-service">Creating a <code>UpdateBook</code> Service</h2>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">UpdateBook</span><span class="hljs-params">(id <span class="hljs-keyword">int</span>, book Book)</span> <span class="hljs-title">error</span></span> {
    _, err := database.DB.Exec(<span class="hljs-string">"UPDATE books SET title = ?, author = ?, year = ? WHERE id = ?"</span>, book.Title, book.Author, book.Year, id)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> err
    }
    <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
}
</code></pre>
<ul>
<li><p>Here we are just executing an update query.</p>
</li>
<li><p>Let’s run the project and see if it’s working.</p>
</li>
</ul>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/08qVfHK3yts">https://youtu.be/08qVfHK3yts</a></div>
<p> </p>
<hr />
<ul>
<li><p>Okay, so I changed my mind and thought to give you some exercise.</p>
</li>
<li><p>I want you to create <code>DELETE</code> a route for deleting a book. And <code>GET</code> route for getting “a single book“ from the database.</p>
</li>
<li><p>I will share the code with you but before that, I recommend trying it out on your own.</p>
</li>
<li><p>No cheating…</p>
</li>
</ul>
<hr />
<p>.</p>
<p>.</p>
<p>.</p>
<p>.</p>
<p>.</p>
<ul>
<li>You didn’t try, didn’t you…</li>
</ul>
<p><img src="https://media.tenor.com/Ab18aZakEuIAAAAM/dog-sussy.gif" alt="a small brown and white dog with a mustache on its face ." class="image--center mx-auto" /></p>
<hr />
<ul>
<li>Anyway here is the code..</li>
</ul>
<h1 id="heading-deleting-a-book">Deleting a Book</h1>
<pre><code class="lang-go"><span class="hljs-comment">// routes.go</span>
r.Delete(<span class="hljs-string">"/book/{id}"</span>, books.DeleteBookHandler)
</code></pre>
<pre><code class="lang-go"><span class="hljs-comment">// handler.go</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">DeleteBookHandler</span><span class="hljs-params">(w http.ResponseWriter, r *http.Request)</span></span> {
    id, err := strconv.Atoi(r.PathValue(<span class="hljs-string">"id"</span>))
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        http.Error(w, <span class="hljs-string">"Failed to convert id"</span>, http.StatusInternalServerError)
        <span class="hljs-keyword">return</span>
    }

    err = DeleteBook(id)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        http.Error(w, <span class="hljs-string">"Failed to delete book"</span>, http.StatusInternalServerError)
        <span class="hljs-keyword">return</span>
    }

    w.WriteHeader(http.StatusOK)
}
</code></pre>
<pre><code class="lang-go"><span class="hljs-comment">//service.go</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">DeleteBook</span><span class="hljs-params">(id <span class="hljs-keyword">int</span>)</span> <span class="hljs-title">error</span></span> {
    _, err := database.DB.Exec(<span class="hljs-string">"DELETE FROM books WHERE id = ?"</span>, id)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> err
    }
    <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
}
</code></pre>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/dNMDLiC5Wj0">https://youtu.be/dNMDLiC5Wj0</a></div>
<p> </p>
<hr />
<h1 id="heading-getting-book-by-id">Getting Book by <code>id</code></h1>
<pre><code class="lang-go"><span class="hljs-comment">// routes.go</span>
...
r.Get(<span class="hljs-string">"/book/{id}"</span>, books.GetBookHandler)
...
</code></pre>
<pre><code class="lang-go"><span class="hljs-comment">//handler.go</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">GetBookHandler</span><span class="hljs-params">(w http.ResponseWriter, r *http.Request)</span></span> {
    id, err := strconv.Atoi(r.PathValue(<span class="hljs-string">"id"</span>))
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        http.Error(w, <span class="hljs-string">"Failed to convert id"</span>, http.StatusInternalServerError)
        <span class="hljs-keyword">return</span>
    }

    book, err := GetBook(id)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        http.Error(w, <span class="hljs-string">"Failed to fetch book"</span>, http.StatusInternalServerError)
        <span class="hljs-keyword">return</span>
    }

    w.Header().Set(<span class="hljs-string">"Content-Type"</span>, <span class="hljs-string">"application/json"</span>)
    json.NewEncoder(w).Encode(book)
}
</code></pre>
<pre><code class="lang-go"><span class="hljs-comment">// service.go</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">GetBook</span><span class="hljs-params">(id <span class="hljs-keyword">int</span>)</span> <span class="hljs-params">(Book, error)</span></span> {
    <span class="hljs-keyword">var</span> book Book
    err := database.DB.QueryRow(<span class="hljs-string">"SELECT id, title, author, year FROM books WHERE id = ?"</span>, id).Scan(&amp;book.ID, &amp;book.Title, &amp;book.Author, &amp;book.Year)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> book, err
    }
    <span class="hljs-keyword">return</span> book, <span class="hljs-literal">nil</span>
}
</code></pre>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/lfy6amgY5ho">https://youtu.be/lfy6amgY5ho</a></div>
<p> </p>
<hr />
<h1 id="heading-modifications">Modifications</h1>
<ul>
<li><p>We did finish our project but there are a few things that need to be fixed in this project. For example, When we are Updating a book, we must pass the whole object with every value instead of just a value that we need to update.</p>
</li>
<li><p>There are no clear messages in the console after we perform operations.</p>
</li>
<li><p>We should add proper logs inside each error-handling block.</p>
</li>
<li><p>I want you to do a little bit of research on these points and update the code.</p>
</li>
<li><p>It is pretty easy and I think with a little bit of research and work you will be able to complete it.</p>
</li>
</ul>
<hr />
<h1 id="heading-github-repository">GitHub Repository</h1>
<ul>
<li>Here is the Git repository that contains all the code.</li>
</ul>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/red-star25/crud-api">https://github.com/red-star25/crud-api</a></div>
<p> </p>
<hr />
<h1 id="heading-wrapping-up">Wrapping Up</h1>
<ul>
<li><p>That’s it. We have officially completed this series. I hope you learned something from this series.</p>
</li>
<li><p>If you have any queries or concerns regarding any concepts, please feel free to comment it. I’ll try to answer it as per my knowledge.</p>
</li>
<li><p>In future blogs, I am planning to experiment more with Golang and create new projects with it. So stay tuned and follow to get the notifications.</p>
</li>
<li><p>See you in the next one, until then…</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711803084752/aa266313-a315-41a8-9538-8ff4370577fb.gif?auto=format,compress&amp;gif-q=60&amp;format=webm&amp;auto=format,compress&amp;gif-q=60&amp;format=webm&amp;auto=format,compress&amp;gif-q=60&amp;format=webm" alt class="image--center mx-auto" /></p>
]]></content:encoded></item><item><title><![CDATA[From Zero to Go Hero: Learn Go Programming with Me - Part 4]]></title><description><![CDATA[Introduction

Heyy Gophers!!! Welcome to the Part 4 of Learning Go with Me. Thanks for coming alone to till here. I hope you are learning and enjoying this series.

Till now we almost covered the fundamentals of Go and now we are ready to write out t...]]></description><link>https://dhruvnakum.xyz/from-zero-to-go-hero-learn-go-programming-with-me-part-4</link><guid isPermaLink="true">https://dhruvnakum.xyz/from-zero-to-go-hero-learn-go-programming-with-me-part-4</guid><category><![CDATA[Go Language]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[General Programming]]></category><category><![CDATA[Beginner Developers]]></category><category><![CDATA[Developer]]></category><category><![CDATA[programming languages]]></category><category><![CDATA[learning]]></category><category><![CDATA[development]]></category><category><![CDATA[backend]]></category><category><![CDATA[REST API]]></category><dc:creator><![CDATA[Dhruv Nakum]]></dc:creator><pubDate>Fri, 10 Jan 2025 21:03:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1736228159917/fee94889-26b2-4966-80bb-cbdc14d95be9.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-introduction">Introduction</h1>
<ul>
<li><p>Heyy Gophers!!! Welcome to the Part 4 of Learning Go with Me. Thanks for coming alone to till here. I hope you are learning and enjoying this series.</p>
</li>
<li><p>Till now we almost covered the fundamentals of Go and now we are ready to write out the first API.</p>
</li>
<li><p>It’s gonna be exciting as we will learn a lot of new things which will help you in your first programming career</p>
</li>
<li><p>Let’s get started without wasting any more time.</p>
</li>
</ul>
<hr />
<ul>
<li>We already have the project structure ready for us</li>
</ul>
<pre><code class="lang-plaintext">crud-api/
│
├── cmd/
│   └── main/
│       └── main.go           # Entry point for the application
│
├── config/
│   └── config.go             # Environment variable management
│
├── internal/
│   ├── routes/
│   │   └── routes.go         # API routes definition
│   ├── books/
│   │   ├── handler.go        # HTTP handlers for book operations
│   │   ├── model.go          # Book database model and queries
│   │   └── service.go        # Business logic for books
│   ├── database/
│   │   └── db.go             # Database connection logic
│
├── .env                      # Environment variables
├── go.mod                    # Go module dependencies
└── .gitignore                # Ignored files
</code></pre>
<ul>
<li>Also we have dependencies installed in our project from Part 1 of this series</li>
</ul>
<pre><code class="lang-go"><span class="hljs-keyword">go</span> get github.com/<span class="hljs-keyword">go</span>-chi/chi/v5
<span class="hljs-keyword">go</span> get github.com/<span class="hljs-keyword">go</span>-sql-driver/mysql
<span class="hljs-keyword">go</span> get github.com/joho/godotenvfg
</code></pre>
<ul>
<li>And lastly, we have the environment file ready for us</li>
</ul>
<pre><code class="lang-go"><span class="hljs-comment">// .env</span>
PORT=<span class="hljs-number">8080</span>
DB_USER=root
DB_PASSWORD=password
DB_HOST=<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">3306</span>
DB_NAME=crud_api
</code></pre>
<ul>
<li><p>Now let’s go step by step. As we already have the .env file ready for us, why not create a function to load it inside our project?</p>
</li>
<li><p>Before that let’s see the main.go file</p>
</li>
</ul>
<pre><code class="lang-go"><span class="hljs-comment">// main.go</span>

<span class="hljs-keyword">package</span> main

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    <span class="hljs-comment">// <span class="hljs-doctag">TODO:</span> Load .env file</span>

    <span class="hljs-comment">// <span class="hljs-doctag">TODO:</span> Setup database connection</span>

    <span class="hljs-comment">// <span class="hljs-doctag">TODO:</span> Setup router</span>

    <span class="hljs-comment">// <span class="hljs-doctag">TODO:</span> Run server</span>
}
</code></pre>
<ul>
<li>These are our TODOs which we need to complete</li>
</ul>
<hr />
<h1 id="heading-loading-env-variables">Loading <code>.env</code> Variables</h1>
<ul>
<li><p>To load values inside our <code>.go</code> files we are using <code>godotenv</code> package. This package helps us get environmental values inside our file.</p>
</li>
<li><p>We implement this function inside <code>config.go</code> file. So head over to <code>config.go</code> and let’s write <code>LoadEnv</code> function. But before that let’s import a few packages</p>
</li>
</ul>
<pre><code class="lang-go"><span class="hljs-comment">// config/config.go</span>

<span class="hljs-keyword">package</span> config

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"log"</span>
    <span class="hljs-string">"os"</span>

    <span class="hljs-string">"github.com/joho/godotenv"</span>
)
</code></pre>
<ul>
<li>We are using <code>log</code>, <code>os</code> and <code>godotenv</code> packages here.</li>
</ul>
<hr />
<h2 id="heading-fmt-and-log-packages"><code>fmt</code> and <code>log</code> Packages</h2>
<ul>
<li><p>In Go we mostly use these two packages to print out values and logs in the terminal. At first, these two may look similar but they both serve different purposes.</p>
</li>
<li><p><code>log</code> is specifically designed for logging messages. This means we can get more information like time stamping and error reporting with this. So we mostly use it during application runtime like debugging, informational logs, and error logs.</p>
<ul>
<li>It includes functions like <code>log.Fatal</code>(Exists the program after logging) and <code>log.Panic</code> (Logs and then panic)</li>
</ul>
</li>
<li><p>Whereas <code>fmt</code> is used for general-purpose printing text. It is commonly used for printing in a console with formatted strings. So we use it mostly when we are printing user-facing messages.</p>
</li>
</ul>
<h3 id="heading-example">Example</h3>
<pre><code class="lang-go">fmt.Println(<span class="hljs-string">"Hello World"</span>) <span class="hljs-comment">// Hello World</span>
fmt.Printf(<span class="hljs-string">"Hello %s"</span>, <span class="hljs-string">"World"</span>) <span class="hljs-comment">// Hello World</span>

log.Fatalf(<span class="hljs-string">"This is a fatal error."</span>) <span class="hljs-comment">// This is a fatal error. (After this program stops)</span>
log.Panic(<span class="hljs-string">"This is a panic error."</span>) <span class="hljs-comment">// This is a panic error. (After this program stops)</span>
</code></pre>
<ul>
<li><p>More on <code>fmt</code> visit: <a target="_blank" href="https://pkg.go.dev/fmt">https://pkg.go.dev/fmt</a></p>
</li>
<li><p>More on <code>log</code> visit: <a target="_blank" href="https://pkg.go.dev/log">https://pkg.go.dev/log</a></p>
</li>
</ul>
<hr />
<h2 id="heading-os-package"><code>os</code> Package</h2>
<ul>
<li><p>The <code>os</code> package in Go is used for tasks related to the operating system. For example accessing the files, and environment variable.</p>
</li>
<li><p>In our example, we are focused on accessing the .env file and it has <code>Getenv()</code> a function that receives the values of environment variables named by <code>key</code></p>
</li>
</ul>
<hr />
<ul>
<li>Now let’s write <code>LoadEnv()</code> function</li>
</ul>
<pre><code class="lang-go"><span class="hljs-keyword">import</span> (...)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">LoadEnv</span><span class="hljs-params">()</span></span> {
    err := godotenv.Load(<span class="hljs-string">"../.env"</span>)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        log.Fatal(<span class="hljs-string">"Error loading .env file"</span>)
    }
}
</code></pre>
<ul>
<li><p>We get <code>Load()</code> function inside <code>godotenv</code> package. <code>Load</code> will read the <code>.env</code> file and load them into ENV for this process. Notice that you pass the path of your .env file.</p>
</li>
<li><p>This function returns an error so we are collecting it inside <code>err</code> variable and then checking if the <code>err</code> has anything. If the <code>err</code> is not <code>nil</code> which means something went wrong. If so, we need to show the error message and exit the program. Because if the env file is not loaded then there is no point in starting the program.</p>
</li>
<li><p>Now to get a specific value from the env file we need to make one function that will get us the value of the asked key from the env file</p>
</li>
</ul>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> config

<span class="hljs-keyword">import</span> (...)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">LoadEnv</span><span class="hljs-params">()</span></span> {
    ...
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">GetEnv</span><span class="hljs-params">(key, fallback <span class="hljs-keyword">string</span>)</span> <span class="hljs-title">string</span></span> {
    <span class="hljs-keyword">if</span> value, ok := os.LookupEnv(key); ok {
        <span class="hljs-keyword">return</span> value
    }
    <span class="hljs-keyword">return</span> fallback
}
</code></pre>
<ul>
<li><p>We created the <code>GetEnv</code> function, which takes <code>key</code> and <code>fallback</code> strings as parameters and returns a string.</p>
</li>
<li><p>The <code>key</code> is the string for which we need the value in our code. For example, inside our <code>.env</code> file, we have different values. If we need only <code>DB_USER</code>, we pass this key to the function, and it returns the related value.</p>
</li>
<li><p>We also accept a fallback string, so if the requested value isn’t found, we use the default fallback value.</p>
</li>
<li><p>Here, we are using the <code>os</code> package, which includes the <code>LookupEnv</code> function. This function takes a <code>key</code> as an argument and returns two things: a <code>string</code> and a <code>bool</code>. If the value is found, <code>ok</code> will be true; otherwise, it will be false. By checking <code>ok</code>, we can determine if a value was returned and decide whether to return the fallback value.</p>
</li>
<li><p>Let’s check if it is working or not</p>
</li>
</ul>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"fmt"</span>

    <span class="hljs-string">"github.com/red-star25/crud-api/config"</span>
)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    config.LoadEnv()
    fmt.Println(config.GetEnvValue(<span class="hljs-string">"DB_USER"</span>, <span class="hljs-string">"root"</span>))

       <span class="hljs-comment">// <span class="hljs-doctag">TODO:</span> Setup database connection</span>

    <span class="hljs-comment">// <span class="hljs-doctag">TODO:</span> Setup router</span>

    <span class="hljs-comment">// <span class="hljs-doctag">TODO:</span> Run server</span>
}
</code></pre>
<p><strong>Output:</strong></p>
<pre><code class="lang-go">root
</code></pre>
<ul>
<li>That’s awesome. Let’s now set out the Database Connection</li>
</ul>
<hr />
<h1 id="heading-setting-up-the-database-connection">Setting Up the Database Connection</h1>
<h2 id="heading-importing-packages">Importing Packages</h2>
<ul>
<li><p>As you know we are using MySQL for storing our application’s data. We need to first set up the connection and create an instance of the SQL DB.</p>
</li>
<li><p>Head over to <code>db.go</code> file and let’s first import all the required packages</p>
</li>
</ul>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> database

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"database/sql"</span>
    <span class="hljs-string">"fmt"</span>
    <span class="hljs-string">"log"</span>
    <span class="hljs-string">"os"</span>

    _ <span class="hljs-string">"github.com/go-sql-driver/mysql"</span>
)
</code></pre>
<ul>
<li><p>We have seen the usage of <code>fmt</code>, <code>log</code> and <code>os</code>. Let’s see what are the remaining ones.</p>
</li>
<li><p><code>database/sql</code>: This is a built-in library for SQL database interactions.</p>
</li>
<li><p><code>_ "</code><a target="_blank" href="http://github.com/go-sql-driver/mysql"><code>github.com/go-sql-driver/mysql</code></a><code>"</code>: Imports the MySQL driver as a side effect to register it with <code>database/sql</code>. Here <code>_</code> (underscore) represent that this import is used as a side effect. Which means this package is not directly used.</p>
</li>
</ul>
<h2 id="heading-creating-database">Creating Database</h2>
<pre><code class="lang-go"><span class="hljs-keyword">var</span> DB *sql.DB
</code></pre>
<ul>
<li><p>Here type DB is a pointer to <code>sql.DB</code> an object. We are making it globally accessible so that anyone can reuse it without creating it again and again on each query</p>
</li>
<li><p>You must be wondering Why it is a type of pointer.</p>
<ul>
<li><p>So, If <code>DB</code> were a simple variable (not a pointer), any changes made to it inside a function, wouldn't affect the global variable itself. Instead, Go would create a copy of the value, and the global <code>DB</code> would remain unchanged.</p>
</li>
<li><p>By using a pointer, it updates the global <code>DB</code> directly. This makes the database connection immediately available to all parts of the application.</p>
</li>
</ul>
</li>
</ul>
<h2 id="heading-connect-function">Connect function</h2>
<ul>
<li>Now let’s create a function <code>Connect</code> that will be responsible for opening the database connection and making a connection.</li>
</ul>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Connect</span><span class="hljs-params">()</span></span> {
    dsn := fmt.Sprintf(<span class="hljs-string">"%s:%s@tcp(%s)/%s"</span>,
        config.GetEnvValue(<span class="hljs-string">"DB_USER"</span>, <span class="hljs-string">"root"</span>),
        config.GetEnvValue(<span class="hljs-string">"DB_PASSWORD"</span>, <span class="hljs-string">""</span>),
        config.GetEnvValue(<span class="hljs-string">"DB_HOST"</span>, <span class="hljs-string">"localhost"</span>),
        config.GetEnvValue(<span class="hljs-string">"DB_NAME"</span>, <span class="hljs-string">"crud_api"</span>),
    )
}
</code></pre>
<ul>
<li><p>Here we have created a variable <code>dsn</code>(Data Source Name) which is a type of string. So before we connect to MySQL we need to have a URL to which we want to connect.</p>
</li>
<li><p>So here we have used a formatted string from <code>fmt</code> package. Here function <code>Sprintf</code> returns a formatted string without printing it to the console. <code>%s</code> is a placeholder for <code>string</code> values here.</p>
</li>
<li><p>And we are getting all the values from our <code>.env</code> file using config’s <code>GetEnvValue</code> function.</p>
</li>
<li><p>Example <code>dsn</code></p>
</li>
</ul>
<pre><code class="lang-plaintext">username:password@tcp(localhost)/mydatabase
</code></pre>
<h2 id="heading-opening-connection">Opening Connection</h2>
<ul>
<li><p>Now that our data source URL is ready, we are ready to open our SQL connection.</p>
</li>
<li><p>To do that <code>mysql</code> provides <code>Open()</code> function which takes</p>
<ul>
<li><p><code>driverName</code> (which is nothing but a database name, ex: MySQL, MongoDB, Postgres, etc). You can provide any driver here and open the connection and</p>
</li>
<li><p><code>dataSourceName</code> is a URL of the database that we just created above.</p>
</li>
</ul>
</li>
</ul>
<pre><code class="lang-go"><span class="hljs-keyword">var</span> err error
DB, err = sql.Open(<span class="hljs-string">"mysql"</span>, dsn)
<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
    log.Fatalf(<span class="hljs-string">"Error connecting to database: %v"</span>, err)
}
</code></pre>
<ul>
<li><p>This function returns two things: <code>db</code> object and <code>err</code>.</p>
</li>
<li><p>So what we are doing here is, we are assigning the returned SQL db object to our globally defined <strong>DB</strong>.</p>
</li>
<li><p>And then we check if there is any error occurred or not.</p>
</li>
</ul>
<blockquote>
<p>You will see this type of error handling thoughtout any Go project. This is a convention in Go. It might seems little overdoing but it is a good practice that we are handling error just after calling function.</p>
<p>Also note that there is no <code>try</code> <code>catch</code> or <code>finally</code> keywords in Go. You can read why they are using this type of error handling in this article : <a target="_blank" href="https://go.dev/doc/faq#exceptions">https://go.dev/doc/faq#exceptions</a></p>
</blockquote>
<h2 id="heading-verifying-the-db-connection">Verifying the DB Connection</h2>
<ul>
<li>To verify whether the connection is successful or not we have <code>Ping</code> function that verifies if the database connection is still alive, and it also establishes the connection if necessary.</li>
</ul>
<pre><code class="lang-go"><span class="hljs-keyword">if</span> err := DB.Ping(); err != <span class="hljs-literal">nil</span> {
    log.Fatalf(<span class="hljs-string">"Error verifying database connection: %v"</span>, err)
}
</code></pre>
<ul>
<li>That’s pretty much it. We can log the message after this that the Database is connected successfully.</li>
</ul>
<h2 id="heading-final-dbgo-code">Final <code>db.go</code> code</h2>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> database

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"database/sql"</span>
    <span class="hljs-string">"fmt"</span>
    <span class="hljs-string">"log"</span>

    _ <span class="hljs-string">"github.com/go-sql-driver/mysql"</span>
    <span class="hljs-string">"github.com/red-star25/crud-api/config"</span>
)

<span class="hljs-keyword">var</span> DB *sql.DB

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Connect</span><span class="hljs-params">()</span></span> {
    dsn := fmt.Sprintf(<span class="hljs-string">"%s:%s@tcp(%s)/%s"</span>,
        config.GetEnvValue(<span class="hljs-string">"DB_USER"</span>, <span class="hljs-string">"root"</span>),
        config.GetEnvValue(<span class="hljs-string">"DB_PASSWORD"</span>, <span class="hljs-string">""</span>),
        config.GetEnvValue(<span class="hljs-string">"DB_HOST"</span>, <span class="hljs-string">"localhost"</span>),
        config.GetEnvValue(<span class="hljs-string">"DB_NAME"</span>, <span class="hljs-string">"crud_api"</span>),
    )

    <span class="hljs-keyword">var</span> err error
    DB, err = sql.Open(<span class="hljs-string">"mysql"</span>, dsn)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        log.Fatalf(<span class="hljs-string">"Error connecting to database: %v"</span>, err)
    }

    <span class="hljs-keyword">if</span> err := DB.Ping(); err != <span class="hljs-literal">nil</span> {
        log.Fatalf(<span class="hljs-string">"Error verifying database connection: %v"</span>, err)
    }

    log.Println(<span class="hljs-string">"Database connected successfully!"</span>)

}
</code></pre>
<ul>
<li>And now call this <code>Connect</code> function inside <code>main.go</code></li>
</ul>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"github.com/red-star25/crud-api/config"</span>
    <span class="hljs-string">"github.com/red-star25/crud-api/database"</span>
)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    config.LoadEnv()

    database.Connect()

    <span class="hljs-comment">// <span class="hljs-doctag">TODO:</span> Setup router</span>

    <span class="hljs-comment">// <span class="hljs-doctag">TODO:</span> Run server</span>

}
</code></pre>
<ul>
<li><p>If everything goes right then you will see this message after you run the program.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736390331806/d9bc489e-a75f-4469-952b-122406a2af13.png" alt class="image--center mx-auto" /></p>
<blockquote>
<p>Make sure you have MySQL up and running on your computer.</p>
</blockquote>
</li>
</ul>
<hr />
<h1 id="heading-creating-book-model">Creating <code>Book</code> model</h1>
<ul>
<li>We are creating CRUD API for managing Books. So in order to view data in a structured manner with all the required fields we need to create a Book model using <code>struct</code></li>
</ul>
<blockquote>
<p>In Go, the term <strong>"model"</strong> refers to a structured representation of data used within an application, typically to map data between a program and an external data source, such as a database or an API. Models are often implemented as <code>structs</code> in Go, which are a way to define and organize related data fields.</p>
</blockquote>
<ul>
<li>Let’s create our Book model</li>
</ul>
<pre><code class="lang-go"><span class="hljs-comment">// internal/books/model.go</span>
<span class="hljs-keyword">package</span> books

<span class="hljs-keyword">type</span> Book <span class="hljs-keyword">struct</span> {
    ID     <span class="hljs-keyword">int</span>
    Title  <span class="hljs-keyword">string</span>
    Author <span class="hljs-keyword">string</span>
    Year   <span class="hljs-keyword">int</span>
}
</code></pre>
<ul>
<li><p>Here we have four fields inside our Book struct. <code>ID</code>, <code>Title</code>, <code>Author</code>, <code>Year</code>.</p>
</li>
<li><p>That’s easy, right? Yes. But there is one more thing which we need to do. So as we know that when we deal with APIs, we use JSON to serialize and deserialize the data that we are getting from the user and data we are sending to the user.</p>
</li>
<li><p>And generally, they are represented in lowercase like <code>id</code>, <code>name</code>, <code>author</code>, <code>year</code> like that. But if you see our Book struct we have the first letter capital for each field. And that is intentional. Because we need those struct’s fields outside to use. And to make it globally accessible we capitalize the first letter of any variable.</p>
</li>
<li><p>So to resolve this issue, we use what we call in go “ <strong>struct tags “</strong>. In other programming languages, we refer to this as “ <strong>annotations</strong> “.</p>
</li>
<li><p>This helps us convert these struct fields to proper naming conventions. Let’s see how we do that</p>
</li>
</ul>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> books

<span class="hljs-keyword">type</span> Book <span class="hljs-keyword">struct</span> {
    ID     <span class="hljs-keyword">int</span>    <span class="hljs-string">`json:"id"`</span>
    Title  <span class="hljs-keyword">string</span> <span class="hljs-string">`json:"title"`</span>
    Author <span class="hljs-keyword">string</span> <span class="hljs-string">`json:"author"`</span>
    Year   <span class="hljs-keyword">int</span>    <span class="hljs-string">`json:"year"`</span>
}
</code></pre>
<ul>
<li>So what we do is after giving the type of the struct field we use backticks ` `. And in there we write json: and then whatever name we want.</li>
</ul>
<hr />
<h1 id="heading-implement-database-queries-crud">Implement Database Queries (CRUD)</h1>
<ul>
<li><p>Now that our database is up and running let’s write database queries for <strong>C</strong>reate, <strong>R</strong>ead, <strong>U</strong>pdate, and <strong>D</strong>elete.</p>
</li>
<li><p>First of all, let’s import two required packages</p>
</li>
</ul>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> books

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"crud-api/internal/database"</span>
    <span class="hljs-string">"errors"</span>
)
</code></pre>
<ul>
<li><p>We are importing a database for accessing the <strong>DB</strong> object. Using this we can perform all the queries.</p>
</li>
<li><p>And <code>errors</code> we use for handling errors.</p>
</li>
</ul>
<h2 id="heading-the-getallbooks-function">The <code>GetAllBooks</code> Function</h2>
<ul>
<li>Firstly we are creating a function to get all the books from the database</li>
</ul>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">GetAllBooks</span><span class="hljs-params">()</span> <span class="hljs-params">([]Book, error)</span></span> {
    rows, err := database.DB.Query(<span class="hljs-string">"SELECT * FROM books"</span>)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, err
    }
    <span class="hljs-keyword">defer</span> rows.Close()

    ...
}
</code></pre>
<ul>
<li><p>Here we created a function called <code>GetAllBooks</code> which is responsible for getting all the books from the database.</p>
</li>
<li><p>It returns two values:</p>
<ul>
<li><p><strong>[ ]Book:</strong> A slice of struct Book.</p>
</li>
<li><p><strong>error:</strong> Go’s error object.</p>
</li>
</ul>
</li>
<li><p>Inside the function body, we are making a query to the SQL database using the DB variable’s <code>Query</code> function. It takes a string where we write a raw SQL query.</p>
</li>
<li><p>In this function, we need all the books with data. And to do that we wrote this query <strong>“SELECT * FROM books“.</strong> Which will get all the books from the database.</p>
</li>
<li><p>And then we checked if it threw any errors</p>
</li>
<li><p>After that, we defer call the rows.Close() to close it once this function is finished.</p>
</li>
</ul>
<hr />
<ul>
<li><p>This <code>rows</code> object allows us to iterate over the results we get from the Query. And to iterate over the results we have <code>rows.Next()</code> function which goes over all the results.</p>
</li>
<li><p>We can take advantage of this function and create a for loop inside which we can fill all the data inside a slice of the book</p>
</li>
</ul>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">GetAllBooks</span><span class="hljs-params">()</span> <span class="hljs-params">([]Book, error)</span></span> {    
    ...
    <span class="hljs-keyword">var</span> books []Book
    <span class="hljs-keyword">for</span> rows.Next() {
        <span class="hljs-keyword">var</span> book Book
        <span class="hljs-keyword">if</span> err := rows.Scan(&amp;book.ID, &amp;book.Title, &amp;book.Author, &amp;book.Year); err != <span class="hljs-literal">nil</span> {
            <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, err
        }
        books = <span class="hljs-built_in">append</span>(books, book)
    }
    <span class="hljs-keyword">return</span> books, <span class="hljs-literal">nil</span>
}
</code></pre>
<ul>
<li><p>Here we have created a temporary slice of the book variable. In this, we will fill in all the data.</p>
</li>
<li><p>Then we looped through <code>rows.Next()</code> which will go over all the rows and inside the for the body we are getting all mapping the data in a row to the Book struct using <code>rows.Scan</code></p>
</li>
<li><p>And after that, we are appending that book to the <code>books</code> slice</p>
</li>
<li><p>And once every row is scanned we return it from the function.</p>
</li>
</ul>
<blockquote>
<p>NOTE: Make sure you have the book table in MySQL database: If you don’t have one then create it and then run the project, otherwise it will throw an error</p>
<p>You can run this query inside your MySQL Workbench</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> books (
    <span class="hljs-keyword">id</span> <span class="hljs-built_in">INT</span> AUTO_INCREMENT PRIMARY <span class="hljs-keyword">KEY</span>,
    title <span class="hljs-built_in">VARCHAR</span>(<span class="hljs-number">255</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>,
    author <span class="hljs-built_in">VARCHAR</span>(<span class="hljs-number">255</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>,
    <span class="hljs-keyword">year</span> <span class="hljs-built_in">INT</span> <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>
);
</code></pre>
</blockquote>
<ul>
<li>Let’s test <code>GetAllBooks</code>.</li>
</ul>
<pre><code class="lang-go"><span class="hljs-comment">// main.go</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    config.LoadEnv()

    database.Connect()

    data, err := books.GetAllBooks()
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        log.Fatal(err)
    }
    fmt.Println(<span class="hljs-string">"Books:"</span>,data)
    <span class="hljs-comment">// <span class="hljs-doctag">TODO:</span> Setup router</span>

    <span class="hljs-comment">// <span class="hljs-doctag">TODO:</span> Run server</span>

}
</code></pre>
<p><strong>Output:</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736402535605/d8d513dd-ea92-4969-b88d-14a69dd7b53a.png" alt class="image--center mx-auto" /></p>
<ul>
<li>It ran!!!!</li>
</ul>
<p><img src="https://media.tenor.com/jfufeww3U04AAAAM/jeff-blim-starkid.gif" alt="a man in a suit and black turtleneck is giving a yes sign" class="image--center mx-auto" /></p>
<hr />
<ul>
<li><p>This is great! Now let’s create a Handler function for this function. Because we need to send this data to the user too, right? Till now we just got the data from the database.</p>
</li>
<li><p>To do that head over to <code>handler.go</code> file</p>
</li>
</ul>
<hr />
<h1 id="heading-setting-up-http-handlers">Setting up HTTP Handlers</h1>
<ul>
<li>Let’s first import the required packages</li>
</ul>
<pre><code class="lang-go"><span class="hljs-comment">// handler.go</span>

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"encoding/json"</span>
    <span class="hljs-string">"net/http"</span>
)
</code></pre>
<ul>
<li><p>Here <code>encoding/json</code> is used. This is used for converting JSON data into Go structures and Go data structures into JSON.</p>
</li>
<li><p><code>net/http</code> is used for handling HTTP requests.</p>
</li>
<li><p>I’ll first show all the code to you and then we will understand what it does</p>
</li>
</ul>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> books

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"encoding/json"</span>
    <span class="hljs-string">"net/http"</span>
)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">GetAllBooksHandler</span><span class="hljs-params">(w http.ResponseWriter, r *http.Request)</span></span> {
    books, err := GetAllBooks()
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        http.Error(w, <span class="hljs-string">"Failed to fetch books"</span>, http.StatusInternalServerError)
        <span class="hljs-keyword">return</span>
    }

    w.Header().Set(<span class="hljs-string">"Content-Type"</span>, <span class="hljs-string">"application/json"</span>)
    json.NewEncoder(w).Encode(books)
}
</code></pre>
<ul>
<li><p><code>func GetAllBooksHandler(w http.ResponseWriter, r *http.Request) {</code></p>
<ul>
<li><p>Here we have two parameters:</p>
<ul>
<li><p><code>w http.ResponseWriter</code>: This is used for sending data back to the client.</p>
</li>
<li><p><code>r *http.Request</code>: This is used for getting data from the client.</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="heading-fetching-the-books">Fetching the Books</h2>
<ul>
<li><p><code>books, err := GetAllBooks()</code></p>
<ul>
<li>Here, the function <code>GetAllBooks</code> is called to retrieve a list of books. As we saw, that function will get the data from the database and return all the books with an err object.</li>
</ul>
</li>
</ul>
<h2 id="heading-error-handling">Error Handling</h2>
<pre><code class="lang-go"><span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
    http.Error(w, <span class="hljs-string">"Failed to fetch books"</span>, http.StatusInternalServerError)
    <span class="hljs-keyword">return</span>
}
</code></pre>
<ul>
<li><p>If that function returns any error, we check it inside this if block and send back the HTTP error of <code>InternalServerError</code>.</p>
</li>
<li><p>And then exiting the function with <code>return</code></p>
</li>
<li><p>Here, The <code>http.Error</code> function simplifies sending error responses. This has all the different types of errors you can throw according to your functionalities.</p>
</li>
</ul>
<h2 id="heading-setting-the-response-header">Setting the Response Header</h2>
<pre><code class="lang-go">w.Header().Set(<span class="hljs-string">"Content-Type"</span>, <span class="hljs-string">"application/json"</span>)
</code></pre>
<ul>
<li>This line sets the <code>Content-Type</code> header of the response to <code>application/json</code>. It informs the client that the response body contains JSON data.</li>
</ul>
<h2 id="heading-encoding-and-sending-the-response">Encoding and Sending the Response</h2>
<pre><code class="lang-go">json.NewEncoder(w).Encode(books)
</code></pre>
<ul>
<li><p>Now we need to encode the slice of a book in JSON format for the client. To do data we use json Encoder method.</p>
</li>
<li><p>This sent the data as the HTTP response. And it ensures compatibility with clients expecting data in JSON format.</p>
</li>
</ul>
<hr />
<h1 id="heading-configuring-routes">Configuring Routes</h1>
<ul>
<li><p>Now, how these handlers are going to trigger? Of course when the user hits a certain URL, or to be specific an Endpoint from their end.</p>
</li>
<li><p>And so we want to return data for that particular endpoint/route right? For that, we need to create a route called <code>/book</code>.</p>
</li>
<li><p>And to create routes we are using the <strong>Chi package</strong>.</p>
</li>
<li><p>Go to <code>routes.go</code> and paste the below code</p>
</li>
</ul>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> routes

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"crud-api/internal/books"</span>

    <span class="hljs-string">"github.com/go-chi/chi/v5"</span>
)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">SetupRouter</span><span class="hljs-params">()</span> *<span class="hljs-title">chi</span>.<span class="hljs-title">Mux</span></span> {
    r := chi.NewRouter()

    r.Get(<span class="hljs-string">"/books"</span>, books.GetAllBooksHandler)

    <span class="hljs-keyword">return</span> r
}
</code></pre>
<ul>
<li><p>Here the SetupRouter function will configure all the routes.</p>
</li>
<li><p>First, we need to create an instance of chi router. This router will handle the incoming HTTP requests and route them to the appropriate handlers.</p>
</li>
<li><p><code>r := chi.NewRouter()</code> - This line does that</p>
</li>
<li><p>And now using this variable <code>r</code> we can create a map of the routes to the handlers.</p>
</li>
</ul>
<pre><code class="lang-go">r.Get(<span class="hljs-string">"/books"</span>, books.GetAllBooksHandler)
</code></pre>
<ul>
<li><p>For different HTTP request, we have different HTTP Methods in <code>r</code>. POST, UPDATE, DELETE,etc</p>
</li>
<li><p>Here we need GET as we are getting the data from the database. Inside <code>r.Get()</code>, first, we need to give the route as <code>/books</code>. This means once this endpoint is hit by the client, the handler defined in the second parameter will be triggered.</p>
</li>
<li><p>And at the end, we are returning the pointer to the chi.Mux because we need it for the server in the <code>main.go</code>. You will see why below.</p>
</li>
</ul>
<hr />
<h1 id="heading-finalizing-the-maingo-file">Finalizing the <code>main.go</code> file</h1>
<ul>
<li>Now that we have everything set up we can start the server and listen to the endpoints.</li>
</ul>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"log"</span>
    <span class="hljs-string">"net/http"</span>

    <span class="hljs-string">"github.com/red-star25/crud-api/config"</span>
    <span class="hljs-string">"github.com/red-star25/crud-api/database"</span>
    <span class="hljs-string">"github.com/red-star25/crud-api/internal/routes"</span>
)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    config.LoadEnv()
    database.Connect()

    router := routes.SetupRouter()
    port := config.GetEnvValue(<span class="hljs-string">"PORT"</span>, <span class="hljs-string">"8080"</span>)
    log.Printf(<span class="hljs-string">"Server running on port %s"</span>, port)
    log.Fatal(http.ListenAndServe(<span class="hljs-string">":"</span>+port, router))
}
</code></pre>
<ul>
<li><p>Inside the main function, we are loading the environment variables.</p>
</li>
<li><p>Then we connect to the database.</p>
</li>
<li><p>And then we call SetupRouter to configure all the routes.</p>
</li>
<li><p>To run the server we have <code>ListenAndServe</code> function inside <code>http</code> package. Inside we have to first pass the address and then the router that we set up. And we also have to check for any errors. So we are wrapping this function inside <code>log.Fatal</code></p>
</li>
</ul>
<hr />
<h1 id="heading-running-the-server">Running The Server</h1>
<ul>
<li><p>Now let’s cross our fingers and check if everything is working. To check the functionality of our program. We are using the Postman application.</p>
</li>
<li><p>So open it up and run it</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736405665069/0aeaacae-70a0-400d-abec-0600f04fdd56.png" alt class="image--center mx-auto" /></p>
<p><img src="https://media1.tenor.com/m/J8GV21cQO3QAAAAd/bb-baby.gif" alt="a baby is sitting in a crowd with a woman holding him and screaming yes ." class="image--center mx-auto" /></p>
<ul>
<li>You will see an empty slice because we have not added any data yet. We will be implementing other HTTP methods in the next article.</li>
</ul>
<hr />
<h1 id="heading-wrapping-up">Wrapping Up</h1>
<ul>
<li><p>If you came this far to the end of this article, then congratulations for making it.</p>
</li>
<li><p>In the next article, we will be implementing the remaining CRUD operations which are Update, Delete, and Create. It will be fun because now we have the base setup for us, so now we just need to work on the functionalities.</p>
</li>
<li><p>Hope you learned something from this article. There are lots of blogs coming in the future with more amazing topics and projects and I am so excited to write about all of it.</p>
</li>
<li><p>See you in the next blog, until then….</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711803084752/aa266313-a315-41a8-9538-8ff4370577fb.gif?auto=format,compress&amp;gif-q=60&amp;format=webm&amp;auto=format,compress&amp;gif-q=60&amp;format=webm" alt class="image--center mx-auto" /></p>
<ul>
<li>Connect with me on <a target="_blank" href="https://twitter.com/dhruv_nakum"><strong>Twitter</strong></a>, <a target="_blank" href="https://www.linkedin.com/in/dhruv-nakum-4b1054176/"><strong>LinkedIn</strong></a>, and <a target="_blank" href="https://github.com/red-star25"><strong>Github</strong></a></li>
</ul>
]]></content:encoded></item></channel></rss>