Go – Struct and Interface

Hi Everyone,

Let’s try to understand, Struct and Interface in Go programming language

Struct –

struct is also called as structure, so normally we have some built in data types which are provided by the language, such as int, float64, string, bool etc

but suppose we want to create our own custom data type, how can we achieve this ? so in order to solve this problem we can use struct which helps us to create our own custom data type in which related data is grouped together, once we have this new custom data type, we can use it in our programs, like when when declaring a variable or when declaring and assigning the value

what problem structs are solving ?

If we want to represent a real-world entity in our program we don’t have any type for it, so to solve this problem, Golang provides us structs, which allow us to group related data together and create a custom type.

for example –

suppose we want to store data of 100 users, so we have a program which ask us to enter details of a user like name, email, phone and address and then storing it, and after that it will ask us whether we want add the data of another user or not, this we way we need to add the data of 100 users.

Now question is where we are storing this data ?

one approach is we create separate arrays or slices for each property and then store the data , but the problem is, the data is not grouped together as it is stored separately

But what we wanted to achieve, is to keep the related data together right, hence we use use structs which helps us to create a type of similar data

type User struct {
    name    string 
    email   string
    phone   string
    address string
}

so to solve above problem of storing data of 100 users, what we can do is since we have a new type “User”, so we can create an array or a slice of “User” type

var users [100]User

var users []User

Interview Answer –

Golang provides us built-in data types such as int, float64, string, bool etc. But when we want to represent a real-world entity in our program we don’t have any type for it, so to solve this problem, Golang provides us structs, which allow us to group related data together and create a custom type.

Interface –

An interface focuses on behavior instead of the actual type.

example 1:

suppose we have –

Dog
Cat
Human

All of them are different types, but all of them can perform one common action – Speak()

Now instead of writing separate code for Dog, Cat, and Human, we can write code that works with anything that can Speak().

This idea of focusing on behavior is called an interface.


package main 

import "fmt"

type Speaker interface {
    speak()
}

type Dog struct {}

func (d Dog) speak() {
    fmt.Println("Dog")
}

type Cat struct {}

func (c Cat) speak() {
    fmt.Println("Cat")
}

type Human struct {}

func (h Human) speak() {
    fmt.Println("Human")
}

func speaking(s Speaker) {
    s.speak()
}

func main() {
    speaking(Dog{})
    speaking(Cat{})
    speaking(Human{})
}

Handling JSON Requests in Go

Previously, we learned how to return JSON responses from a Go server.

But APIs do not only send data — they also receive it.

Frontend applications, mobile apps, and clients commonly send JSON data to servers through HTTP requests.

In this tutorial, we will learn how to handle JSON requests in Go by decoding request bodies into structs using Go’s standard library.

By the end, you will understand:

  • how JSON request bodies work
  • how to decode JSON in Go
  • how POST requests work
  • how to process incoming client data

Prerequisites

To follow along, you should have:

  • Go installed
  • basic familiarity with Go syntax
  • understanding of the net/http package
  • basic understanding of JSON responses

You can confirm if Go is installed by running:

go version

Step 1 — Create the Project

Create a new folder for the project:

mkdir go-json-requests
cd go-json-requests

Now initialize a Go module:

go mod init go-json-requests

This creates a go.mod file for managing project dependencies.

Step 2 — Create the Server File

Create a file called main.go.

Your project structure should now look like this:

go-json-requests/
├─ go.mod
└─ main.go

Step 3 — Write the Server

Open main.go and add the following code:

package main

import (
    "encoding/json"
    "fmt"
    "net/http"
)

type User struct {
    Name string `json:"name"`
}

func userHandler(w http.ResponseWriter, r *http.Request) {
    var user User

    err := json.NewDecoder(r.Body).Decode(&user)
    if err != nil {
        http.Error(w, "Invalid JSON", http.StatusBadRequest)
        return
    }

    fmt.Fprintf(w, "Hello, %s!n", user.Name)
}

func main() {
    http.HandleFunc("/user", userHandler)

    fmt.Println("Server running on :8080")

    http.ListenAndServe(":8080", nil)
}

Now let’s unpack what is happening.

Understanding POST Requests

In the previous examples, we mainly worked with routes that returned data.

This time, the client sends data to the server.

This commonly happens using a POST request.

POST requests are used when:

  • submitting forms
  • sending JSON data
  • creating resources
  • uploading information to a server

Understanding the Struct

This struct defines the shape of the JSON data we expect:

type User struct {
    Name string `json:"name"`
}

If the client sends:

{
  "name": "Steve"
}

Go can decode that JSON into the struct.

The json:"name" tag tells Go which JSON field maps to the struct field.

Understanding Request Bodies

When a client sends data to a server, the data is stored inside the request body.

In Go, we access it using:

r.Body

The Body contains the incoming JSON data from the client.

Decoding JSON Requests

This line is the heart of the server:

json.NewDecoder(r.Body).Decode(&user)

Here’s what happens:

  • NewDecoder(r.Body) reads the incoming request body
  • Decode(&user) converts the JSON into the struct
  • &user passes a pointer so Go can modify the struct value

After decoding, the user variable contains the client data.

So Why Do We Use &user?

You may notice this part:

&user

The & symbol means:

“Use the memory address of this variable.”

The decoder needs direct access to the struct so it can fill in the fields with incoming JSON data.

Without the pointer, decoding would not work correctly.

Understanding Error Handling

This section checks whether the JSON request is valid:

if err != nil {
    http.Error(w, "Invalid JSON", http.StatusBadRequest)
    return
}

If decoding fails:

  • the server sends an error response
  • the request stops processing

This helps prevent invalid client data from crashing the application.

Step 4 — Run the Server

Start the application:

go run main.go

You should see:

Server running on :8080

Step 5 — Test the API

This server expects a POST request containing JSON data.

We can test it using curl.

curl -X POST http://localhost:8080/user 
-H "Content-Type: application/json" 
-d '{"name":"Steve"}'

You should receive:

Hello, Steve!

What Happens When a Request Is Made?

Here’s the flow:

  1. The client sends a POST request
  2. JSON data is placed inside the request body
  3. Go reads the request body
  4. The JSON is decoded into a struct
  5. The server processes the data
  6. A response is sent back to the client

This is the foundation of many real-world APIs.

Where to Go Next

Now that we can decode JSON requests, we could extend this server by adding:

  • multiple API endpoints
  • JSON responses
  • CRUD operations — again, because every backend journey eventually leads there
  • databases
  • request validation
  • authentication

This is where backend development starts becoming much more interactive and dynamic.

Final Thoughts

We started with a simple server that only returned data.

Now our server can also receive and process JSON requests from clients.

Along the way, we learned about:

  • POST requests
  • request bodies
  • JSON decoding
  • structs
  • pointers
  • error handling

These concepts are essential when building APIs in Go.

Thanks for reading!

Happy coding!

Dijital pazar yerlerinin temsilcileri Bursa iş dünyasıyla bir araya geldi

Bursa Ticaret ve Sanayi Odası öncülüğünde düzenlenen ‘Dijital Pazaryeri B2B Networking 2026’ etkinliği, 60’tan fazla firmayı Türkiye’nin önde gelen 10 dijital pazar yeri platformuyla buluşturdu. BTSO Başkanı İbrahim Burkay, e-ticaret hacminin 2025’te 4,5 trilyon liraya ulaştığını belirterek dijital dönüşümün önemine vurgu yaptı.

How I Caught and Fixed an N+1 Query in My Django REST API

Every performant API eventually runs into the same silent killer: the N+1 query problem. It doesn’t crash your app. It doesn’t throw errors. It just quietly makes every list endpoint slower as your data grows — and it’s almost invisible until Sentry flags it in production.

Today, Sentry caught one on my /api/blog-posts/ endpoint. Here’s exactly what happened and how I fixed it in three lines of code.

What Is an N+1 Query?

An N+1 query happens when your code fetches a list of N records, then fires an additional query per record to fetch related data — totalling 1 + N database hits instead of a flat 2 or 3.

In Django, this usually happens silently because the ORM is lazy by default. Accessing a related object on a model instance that wasn’t eagerly loaded triggers a fresh SELECT on the spot. With 30 blog posts, that’s 30 silent queries you never wrote.

The Offending Code

The BlogPostViewSet looked clean on the surface:

class BlogPostViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = BlogPost.objects.all()
    serializer_class = BlogPostSerializer
    lookup_field = "uid"

And the serializer:

class BlogPostSerializer(serializers.ModelSerializer):
    tags = BlogTagSerializer(many=True, read_only=True)
    series = BlogSeriesSerializer(read_only=True)
    ...

Spot the problem? BlogPost has two relations:

  • series — a ForeignKey to BlogSeries
  • tags — a ManyToManyField to BlogTag

When DRF serializes a list of 30 posts, it accesses post.series and post.tags on each one. Without eager loading, Django fires two extra queries per post — one to fetch the series, one to fetch the tags. That’s 1 + 60 queries for a 30-post list.

The featured action had the same issue:

@action(detail=False, methods=["get"])
def featured(self, request):
    queryset = BlogPost.objects.filter(date_published__isnull=False).order_by(
        "-date_published",
    )[:3]

A fresh BlogPost.objects call with no eager loading.

The Fix

Django gives me two tools for this:

  • select_related() — for ForeignKey and OneToOne relations. Issues a SQL JOIN and fetches everything in a single query.
  • prefetch_related() — for ManyToMany and reverse FK relations. Issues a second query and caches the results in Python.

The fix:

class BlogPostViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = BlogPost.objects.select_related("series").prefetch_related("tags")
    serializer_class = BlogPostSerializer
    lookup_field = "uid"

    @action(detail=False, methods=["get"])
    def featured(self, request):
        queryset = (
            BlogPost.objects.select_related("series")
            .prefetch_related("tags")
            .filter(date_published__isnull=False)
            .order_by("-date_published")[:3]
        )
        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)

With 30 posts, the list endpoint now costs 3 queries regardless of dataset size:

  1. SELECT * FROM core_blogpost ...
  2. SELECT * FROM core_blogseries WHERE id IN (...)
  3. SELECT * FROM core_blogtag INNER JOIN core_blogpost_tags WHERE blogpost_id IN (...)

The Bonus Fix

While auditing the blog endpoint, I spotted the same pattern in TestimonialViewSet. Its serializer accesses project.title and project.slug, but the queryset had no select_related:

# Before
queryset = Testimonial.objects.all()

# After
queryset = Testimonial.objects.select_related("project")

One extra line, one less N+1.

How to Spot This in Your Own Code

The pattern is always the same — look for any ViewSet or view where:

  1. The queryset has no select_related or prefetch_related
  2. The serializer accesses a related field (source="relation.field", nested serializers, SerializerMethodField that touches obj.relation)

Tools that help catch this before Sentry does:

  • django-debug-toolbar — shows query counts per request in the browser
  • nplusone — raises exceptions in tests when N+1 queries are detected
  • Sentry Performance — catches it in production with query traces

The best time to catch an N+1 is during code review. Any time you write a nested serializer, ask: does the queryset for this view eagerly load this relation?

Takeaway

The Django ORM’s lazy evaluation is a feature, not a bug — but it requires discipline at the queryset layer. A clean-looking viewset with objects.all() is often hiding a query storm one serializer away.

The rule of thumb: every relation accessed in a serializer needs a corresponding select_related or prefetch_related on the queryset. Make it a checklist item on every PR that touches a ViewSet.

How to Start Coding as a Student: A Complete Beginner’s Guide 🚀

Hey everyone! 👋

If you are a student and thinking about stepping into the world of coding, you are in the exact right place. At first, coding can look intimidating with all those complex lines of text, but here is the truth: coding is like a superpower that anyone can learn—no matter your age or background!
In this article, I’m sharing a simple, no-nonsense roadmap to help any student kickstart their programming journey without getting overwhelmed.

🗺️ The Simple Roadmap
Step 1: Build Logic (Scratch): If you are an absolute beginner, start with Scratch (scratch.mit.edu). It uses drag-and-drop blocks to teach you how coding logic works through simple games.

Step 2: Build Websites (HTML & CSS): Want to make websites? Learn HTML (to add text and images) and CSS (to make it look colorful and beautiful).

Step 3: Try Python: If you want to build apps or explore AI, start with Python. It is the easiest language because it reads just like English.

🛠️ Best Free Resources
You don’t need to spend any money! Use these free platforms:

freeCodeCamp.org (Great for interactive coding)

W3Schools (Perfect for quick HTML/CSS notes)

YouTube (Search for “Free HTML/CSS beginner tutorials”)

💡 3 Golden Rules

Don’t Memorize: Coding is about logic, not learning lines by heart. Google is your best friend!

Expect Mistakes: Your code will break, and that is completely normal. Fixing bugs is how we learn.

Be Consistent: Coding for 30 minutes every day is much better than coding for 5 hours once a week.

Storing Kamal secrets in AWS Secrets Manager and deploying to a cheap Hetzner VPS

I ran into a problem with Kamal. My .kamal/secrets file was full of API keys sitting in plaintext on my laptop. Anyone with access could read them all.

TLDR; Use Kamal with AWS Secrets Manager and deploy to a Hetzner VPS. No plaintext secrets, cheap hosting, compliance happy.

The problem

Kamal is great for deploying apps. But by default secrets are in a plaintext file. For SOC 2 and GDPR that does not work. You need a managed store. I went with AWS Secrets Manager.

But then I hit another issue. The kamal secrets fetch --adapter aws_secrets_manager command with --from expects each key to be its own AWS secret. If you store everything as one JSON blob (like I did), you get:

ERROR (RuntimeError): myapp/production/secrets//DEEPGRAM_API_KEY: Secrets Manager can't find the specified secret.

Step 1: Hetzner VPS

Hetzner CAX series starts at around 4 euro a month. I use the CX22 with 2 vCPUs and 4GB RAM. Enough for production.

# On your Hetzner server
apt update && apt install -y docker.io

# Copy your SSH key so Kamal can connect
ssh-copy-id root@your-server-ip

Your config/deploy.yml:

servers:
  web:
    hosts:
      - runtime.yourdomain.com

proxy:
  ssl: true
  hosts:
    - runtime.yourdomain.com
  healthcheck:
    path: /health/ready

registry:
  server: docker.io
  username: your-docker-user
  password:
    - KAMAL_REGISTRY_PASSWORD

You need a Docker Hub account and a personal access token for KAMAL_REGISTRY_PASSWORD.

Step 2: Create the secret in AWS

In the AWS Secrets Manager Console:

  1. Go to Secrets Manager > Store a new secret
  2. Select “Other type of secret”
  3. Switch to plaintext tab and paste your JSON
{
  "DEEPGRAM_API_KEY": "your_deepgram_key",
  "ASSEMBLY_AI_API_KEY": "your_assemblyai_key",
  "REDIS_URL": "redis://:password@your-redis:6379",
  "KAMAL_REGISTRY_PASSWORD": "your_docker_token"
}
  1. Name it myapp/production/secrets
  2. Click Store

Pick a region close to your server. If your Hetzner box is in Germany, use eu-central-1 (Frankfurt). Keeps latency low and GDPR happy.

Step 3: IAM user for your laptop

Your laptop needs permission to read the secret during deploy.

  1. Go to IAM > Users > Create user
  2. Name it kamal-deploy
  3. Uncheck console access (CLI only)
  4. Create a group called secrets-manager with the SecretsManagerReadWrite policy
  5. Add an inline policy for batch reading:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "secretsmanager:GetSecretValue",
        "secretsmanager:DescribeSecret",
        "secretsmanager:BatchGetSecretValue",
        "secretsmanager:ListSecrets"
      ],
      "Resource": "*"
    }
  ]
}
  1. Add your user to the group

IAM policies can take a minute to propagate. If it fails at first, wait 30 seconds and try again.

Step 4: Configure AWS CLI

aws configure
# AWS Access Key ID: paste from IAM user
# AWS Secret Access Key: paste
# Default region name: eu-central-1
# Default output format: json

Test it:

aws secretsmanager get-secret-value --secret-id myapp/production/secrets --query SecretString --output text | head -c 50

You should see the start of your JSON.

Step 5: Format your .kamal/secrets file

This is where I got stuck. The --from flag wants one AWS secret per key. Having 20 separate secrets is annoying. Check the Kamal secrets docs for more on this.

Instead I use the AWS CLI with Python extraction. Each line is self contained:

# AWS Secrets Manager: myapp/production/secrets (eu-central-1)
DEEPGRAM_API_KEY=$(python3 -c "import json,sys; print(json.loads(sys.argv[1])['DEEPGRAM_API_KEY'])" "$(aws secretsmanager get-secret-value --secret-id myapp/production/secrets --query SecretString --output text)")
ASSEMBLY_AI_API_KEY=$(python3 -c "import json,sys; print(json.loads(sys.argv[1])['ASSEMBLY_AI_API_KEY'])" "$(aws secretsmanager get-secret-value --secret-id myapp/production/secrets --query SecretString --output text)")
REDIS_URL=$(python3 -c "import json,sys; print(json.loads(sys.argv[1])['REDIS_URL'])" "$(aws secretsmanager get-secret-value --secret-id myapp/production/secrets --query SecretString --output text)")
KAMAL_REGISTRY_PASSWORD=$(python3 -c "import json,sys; print(json.loads(sys.argv[1])['KAMAL_REGISTRY_PASSWORD'])" "$(aws secretsmanager get-secret-value --secret-id myapp/production/secrets --query SecretString --output text)")

Each line fetches the full JSON and extracts one key. Kamal evaluates each line in its own subshell so there are no shared variables between lines. This works.

You can also use jq if you prefer:

DEEPGRAM_API_KEY=$(aws secretsmanager get-secret-value --secret-id myapp/production/secrets --query SecretString --output text | jq -r '.DEEPGRAM_API_KEY')

Step 6: Deploy

kamal deploy

Kamal fetches secrets from AWS during deploy and injects them into your container. No plaintext file ever touches the server.

Production and staging

I use a different AWS secret per environment. Both pull from AWS no plaintext anywhere.

# .kamal/secrets  (used by kamal deploy)
DEEPGRAM_API_KEY=$(python3 -c "import json,sys; print(json.loads(sys.argv[1])['DEEPGRAM_API_KEY'])" "$(aws secretsmanager get-secret-value --secret-id myapp/production/secrets --query SecretString --output text)")
KAMAL_REGISTRY_PASSWORD=$(python3 -c "import json,sys; print(json.loads(sys.argv[1])['KAMAL_REGISTRY_PASSWORD'])" "$(aws secretsmanager get-secret-value --secret-id myapp/production/secrets --query SecretString --output text)")

# .kamal/secrets.staging  (used by kamal deploy -d staging)
DEEPGRAM_API_KEY=$(python3 -c "import json,sys; print(json.loads(sys.argv[1])['DEEPGRAM_API_KEY'])" "$(aws secretsmanager get-secret-value --secret-id myapp/staging/secrets --query SecretString --output text)")
KAMAL_REGISTRY_PASSWORD=$(python3 -c "import json,sys; print(json.loads(sys.argv[1])['KAMAL_REGISTRY_PASSWORD'])" "$(aws secretsmanager get-secret-value --secret-id myapp/staging/secrets --query SecretString --output text)")

Only the secret name changes between files. myapp/production/secrets for production, myapp/staging/secrets for staging. Run kamal deploy -d staging and Kamal reads from the staging file.

Both secrets live in AWS. No staging credentials in plaintext either. This matters for SOC 2 because auditors check every environment.

Done

No more secrets in plaintext. SOC 2 and GDPR requirements met. Hetzner bill stays under 5 euro a month.

Big thanks to the AWS docs team, the Kamal maintainers, and Hetzner for keeping hosting affordable. Hope this saves you the same headaches I ran into. Now back to building.