Go Programming — Block 3: Build Useful Applications

Go Programming — Block 3: Build Useful Applications

Goal: Move from basic Go programs to applications that store information, communicate over the internet, interact with databases, and serve real users.

Block 3 covers Chapters 21–30. We will focus on practical questions, MCQs, complete working code, file structures, terminal commands, and line-by-line explanations. The examples build on Block 1 and assume you have learned the file handling, JSON, packages, and error-handling basics from Block 2.

Chapter 21 — Command-line applications

A command-line application is a program you operate by typing commands into a terminal rather than clicking buttons on a website.

MCQ 1

Which Go package is commonly used to read command-line arguments supplied when starting a program?

  • A. fmt

  • B. os

  • C. strings

  • D. math

Answer: B — os. The os.Args slice contains the program name followed by the arguments supplied when the program starts.

Practical task: Build a simple greeting command

Folder structure:

greeting/
└── main.go

main.go:

Go

package main

import (
    "fmt"
    "os"
)

func main() {
    if len(os.Args) < 2 {
        fmt.Println("Usage: go run main.go <name>")
        return
    }

    name := os.Args[1]
    fmt.Println("Hello,", name)
}

Run:

Bash

go run main.go Pramod

Output:

Hello, Pramod

How it works

  • os.Args contains the command-line arguments.

  • len(os.Args) < 2 checks whether a name was supplied.

  • os.Args[1] retrieves the first argument after the program name.

  • return stops the program if the required argument is missing.

Try running go run main.go without a name. The program prints usage instructions rather than attempting to access a missing argument.

MCQ 2

Why check the number of command-line arguments before accessing os.Args[1]?

  • A. To make the program run faster in every case.

  • B. To prevent accessing an element that does not exist.

  • C. To install Go.

  • D. To convert a string into a number.

Answer: B. Always validate that required input exists before using it.

Chapter 22 — Configuration and environment variables

Applications often need settings such as a server port, database address, or secret key. Instead of writing these settings directly into the code, we can read them from the computer's environment.

MCQ 3

Why might a programmer use an environment variable for a server port?

  • A. To store configuration outside the source code.

  • B. To make all variables public.

  • C. To replace every function.

  • D. To automatically create a database.

Answer: A. Environment variables let you change settings without editing the source code.

Practical task: Make a configurable greeting

main.go:

Go

package main

import (
    "fmt"
    "os"
)

func main() {
    name := os.Getenv("APP_NAME")

    if name == "" {
        name = "Go Application"
    }

    fmt.Println("Welcome to", name)
}

Run it with a default setting:

Bash

go run main.go

Output:

Welcome to Go Application

On macOS or Linux, set a custom value for a single run:

Bash

APP_NAME=MyStore go run main.go

Output:

Welcome to MyStore

Key idea: os.Getenv() reads an environment variable. If it is missing or empty, this program uses a default value.

Never place real passwords or secret API keys in source code or commit them to Git. Environment variables are one configuration option, although production systems may use dedicated secret-management services.

MCQ 4

What does os.Getenv("PORT") return when PORT is not set?

  • A. Always 8080

  • B. An empty string

  • C. An integer zero

  • D. A compilation error

Answer: B. os.Getenv() returns an empty string if the variable is absent. If your application needs a default port, you must supply it yourself.

Chapter 23 — Databases and SQL basics

A database stores information so it remains available after your program stops.

Imagine an online store. It needs to remember products, prices, orders, and customer details. A database provides a structured way to store and retrieve that information.

MCQ 5

Which SQL command retrieves records from a database?

  • A. SELECT

  • B. DELETE

  • C. DROP

  • D. STOP

Answer: A — SELECT.

Essential SQL commands

CommandPurposeExample
CREATE TABLECreate a tableDefine a products table
INSERTAdd a recordAdd a new product
SELECTRead recordsFind all products
UPDATEChange recordsChange a product's price
DELETERemove recordsDelete a product

Practical task: Create and query a product table

The following is SQL, not Go. You can run it in a SQLite database tool or another compatible SQL environment.

SQL

CREATE TABLE products (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    price REAL NOT NULL
);

INSERT INTO products (name, price)
VALUES ('Notebook', 50);

SELECT id, name, price
FROM products;

UPDATE products
SET price = 60
WHERE name = 'Notebook';

SELECT name, price
FROM products
WHERE price >= 60;

DELETE FROM products
WHERE name = 'Notebook';

What happens?

  1. A table named products is created.

  2. A notebook is inserted with a price of 50.

  3. The records are retrieved.

  4. The price is changed to 60.

  5. The notebook is retrieved using a condition.

  6. The record is deleted.

Real-life use: These are the fundamental operations behind product catalogs, user records, inventory systems, and order management.

MCQ 6

Why is this SQL query dangerous if you intend to update only one product?

SQL

UPDATE products SET price = 100;
  • A. It updates every row in the table.

  • B. It retrieves records only.

  • C. It creates a new table.

  • D. It updates only the first row automatically.

Answer: A. Without a WHERE condition, the update applies to every row. Always check the target records before making important changes.

Chapter 24 — Connect Go to a database

Knowing SQL is not enough to build a database-backed application. Your Go program must establish a connection, execute queries, and handle errors.

For a practical example, we'll use SQLite with the pure-Go driver modernc.org/sqlite. It stores the database in a local file, so you do not need to run a separate database server.

MCQ 7

What is the purpose of Go's database/sql package?

  • A. It provides a standard interface for working with SQL databases.

  • B. It creates websites automatically.

  • C. It replaces SQL queries with HTML.

  • D. It stores all application data permanently in variables.

Answer: A. A database driver supplies the database-specific implementation.

Practical task: Save a product in a database

Folder structure:

product-store/
├── go.mod
└── main.go

Open a terminal inside product-store and initialize the project:

Bash

go mod init product-store
go get modernc.org/sqlite

Create main.go:

Go

package main

import (
    "database/sql"
    "fmt"
    "log"

    _ "modernc.org/sqlite"
)

func main() {
    db, err := sql.Open("sqlite", "products.db")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    _, err = db.Exec(`
        CREATE TABLE IF NOT EXISTS products (
            id INTEGER PRIMARY KEY,
            name TEXT NOT NULL,
            price REAL NOT NULL
        )
    `)
    if err != nil {
        log.Fatal(err)
    }

    result, err := db.Exec(
        "INSERT INTO products (name, price) VALUES (?, ?)",
        "Notebook", 50,
    )
    if err != nil {
        log.Fatal(err)
    }

    id, err := result.LastInsertId()
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println("Saved product with ID:", id)
}

Run:

Bash

go run .

Example output on the first run:

Saved product with ID: 1

A file named products.db will be created in the working directory. Each run inserts another notebook, so the ID will increase.

Understand the important lines

  • database/sql provides Go's standard database interface.

  • The blank import _ "modernc.org/sqlite" registers the SQLite driver without directly referring to its package name.

  • sql.Open(...) prepares a database handle. Actual connection activity may occur when a query executes.

  • defer db.Close() schedules the database handle to close when main returns.

  • db.Exec(...) executes a statement that does not need to return rows.

  • The ? placeholders represent values supplied separately to the SQL statement. This is safer than joining user input directly into SQL text.

  • log.Fatal(err) prints the error and exits the program.

For a real application, you'll also need to retrieve records with Query or QueryRow, validate input, and manage database transactions where appropriate.

MCQ 8

Which approach should you use when inserting user-provided values into SQL?

  • A. Concatenate all values directly into the SQL string.

  • B. Use parameterized queries with placeholders.

  • C. Remove all spaces from the input.

  • D. Put the values inside comments.

Answer: B. Parameterized queries help prevent SQL injection, a vulnerability where malicious input changes the meaning of a query.

Chapter 25 — HTTP and how the web works

HTTP is a communication system used by browsers and servers.

When you visit a website, your browser sends a request. The server processes it and returns a response.

Client

Browser or another application

HTTP request ↔ HTTP response

Server

Go program that handles the request

MCQ 9

What does HTTP status code 404 generally mean?

  • A. The request succeeded.

  • B. The requested resource was not found.

  • C. The server has started.

  • D. The user is automatically authenticated.

Answer: B. A server commonly returns 404 Not Found when the requested resource does not exist.

MCQ 10

Which HTTP method is conventionally used to retrieve a resource?

  • A. GET

  • B. DELETE

  • C. PATCH

  • D. POST

Answer: A — GET. HTTP methods communicate the intended operation. POST commonly submits data, PUT replaces a resource, PATCH modifies part of a resource, and DELETE requests deletion.

Chapter 26 — Build a web server with Go

Go includes a built-in net/http package for creating web servers. You can start with a simple server before introducing a web framework.

Practical task: Your first web server

Folder structure:

hello-server/
└── main.go

main.go:

Go

package main

import (
    "fmt"
    "log"
    "net/http"
)

func home(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "Welcome to my Go website!")
}

func main() {
    http.HandleFunc("/", home)

    log.Println("Server running at http://localhost:8080")

    log.Fatal(http.ListenAndServe("localhost:8080", nil))
}

Run:

Bash

go run main.go

Open http://localhost:8080 in your browser.

Expected page:

Welcome to my Go website!

How it works

  • net/http contains tools for handling HTTP requests and responses.

  • home is a handler function that runs when a matching request arrives.

  • http.ResponseWriter lets the handler write the response.

  • *http.Request contains details about the incoming request.

  • http.HandleFunc("/", home) registers the handler for the root path.

  • http.ListenAndServe(...) starts listening for incoming requests.

Keep the terminal open while testing the server. Stop it with Ctrl+C.

This example is for local learning. Before exposing a server publicly, configure appropriate timeouts, error handling, security controls, and deployment settings.

Chapter 27 — REST APIs and JSON

A website might display data to a human, while an API provides data that another program can use.

JSON is a common format for transferring structured data.

Example response:

JSON

{
  "name": "Notebook",
  "price": 50
}

MCQ 11

Why might an application return JSON instead of a plain sentence?

  • A. JSON provides a structured format that software can parse.

  • B. JSON automatically creates a database.

  • C. JSON prevents every security problem.

  • D. JSON can only contain numbers.

Answer: A. JSON supports structured objects, arrays, strings, numbers, Booleans, and null values.

Practical task: Build a JSON API

Create main.go:

Go

package main

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

type Product struct {
    Name  string  `json:"name"`
    Price float64 `json:"price"`
}

func productHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")

    product := Product{
        Name:  "Notebook",
        Price: 50,
    }

    if err := json.NewEncoder(w).Encode(product); err != nil {
        log.Println("Could not encode product:", err)
    }
}

func main() {
    http.HandleFunc("/api/product", productHandler)

    log.Println("Open http://localhost:8080/api/product")
    log.Fatal(http.ListenAndServe("localhost:8080", nil))
}

Run:

Bash

go run main.go

Open http://localhost:8080/api/product.

Example response:

JSON

{"name":"Notebook","price":50}

Important concepts:

  • Product is a struct, a custom type that groups related values.

  • The json:"name" tag specifies the JSON field name.

  • json.NewEncoder(w).Encode(product) converts the Go value into JSON and writes it to the response.

  • The HTTP header tells the client that the response contains JSON.

For an actual API, also validate methods, handle encoding errors appropriately, return meaningful status codes, and avoid exposing private data.

MCQ 12

What is the purpose of json:"name" in the Product struct?

  • A. It renames the Go variable itself.

  • B. It tells the JSON encoder to use the field name name.

  • C. It creates a database column automatically.

  • D. It makes the field private.

Answer: B. It is a struct tag that controls the field's JSON representation.

Chapter 28 — Forms, validation, and CRUD

CRUD means Create, Read, Update, Delete. These four operations appear in product management, student records, blog posts, and countless other applications.

We'll create a small API that stores products in memory. This keeps the example focused on HTTP and CRUD without repeating the database setup from Chapter 24.

MCQ 13

Which CRUD operation corresponds to changing an existing product's price?

  • A. Create

  • B. Read

  • C. Update

  • D. Delete

Answer: C — Update.

Practical task: Build a small product API

Folder structure:

product-api/
└── main.go

main.go:

Go

package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "strconv"
    "strings"
    "sync"
)

type Product struct {
    ID    int     `json:"id"`
    Name  string  `json:"name"`
    Price float64 `json:"price"`
}

var (
    products = map[int]Product{}
    nextID   = 1
    mu       sync.Mutex
)

func productsHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")

    switch r.Method {
    case http.MethodGet:
        mu.Lock()
        defer mu.Unlock()

        list := []Product{}
        for _, p := range products {
            list = append(list, p)
        }

        json.NewEncoder(w).Encode(list)

    case http.MethodPost:
        var p Product

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

        p.Name = strings.TrimSpace(p.Name)
        if p.Name == "" || p.Price < 0 {
            http.Error(w, "Invalid product", http.StatusBadRequest)
            return
        }

        mu.Lock()
        p.ID = nextID
        nextID++
        products[p.ID] = p
        mu.Unlock()

        w.WriteHeader(http.StatusCreated)
        json.NewEncoder(w).Encode(p)

    default:
        w.Header().Set("Allow", "GET, POST")
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
    }
}

func productHandler(w http.ResponseWriter, r *http.Request) {
    idText := strings.TrimPrefix(r.URL.Path, "/products/")
    id, err := strconv.Atoi(idText)

    if err != nil || id <= 0 {
        http.Error(w, "Invalid product ID", http.StatusBadRequest)
        return
    }

    switch r.Method {
    case http.MethodGet:
        mu.Lock()
        p, exists := products[id]
        mu.Unlock()

        if !exists {
            http.Error(w, "Product not found", http.StatusNotFound)
            return
        }

        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(p)

    case http.MethodPut:
        var p Product

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

        p.Name = strings.TrimSpace(p.Name)
        if p.Name == "" || p.Price < 0 {
            http.Error(w, "Invalid product", http.StatusBadRequest)
            return
        }

        mu.Lock()
        _, exists := products[id]
        if exists {
            p.ID = id
            products[id] = p
        }
        mu.Unlock()

        if !exists {
            http.Error(w, "Product not found", http.StatusNotFound)
            return
        }

        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(p)

    case http.MethodDelete:
        mu.Lock()
        _, exists := products[id]
        delete(products, id)
        mu.Unlock()

        if !exists {
            http.Error(w, "Product not found", http.StatusNotFound)
            return
        }

        w.WriteHeader(http.StatusNoContent)

    default:
        w.Header().Set("Allow", "GET, PUT, DELETE")
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
    }
}

func main() {
    http.HandleFunc("/products", productsHandler)
    http.HandleFunc("/products/", productHandler)

    fmt.Println("Product API running at http://localhost:8080")
    http.ListenAndServe("localhost:8080", nil)
}

Run:

Bash

go run main.go

Test the API

Create a product:

Bash

curl -X POST http://localhost:8080/products \
  -H "Content-Type: application/json" \
  -d '{"name":"Notebook","price":50}'

Example response:

JSON

{"id":1,"name":"Notebook","price":50}

Retrieve all products:

Bash

curl http://localhost:8080/products

Retrieve product 1:

Bash

curl http://localhost:8080/products/1

Update product 1:

Bash

curl -X PUT http://localhost:8080/products/1 \
  -H "Content-Type: application/json" \
  -d '{"name":"Notebook","price":60}'

Delete product 1:

Bash

curl -X DELETE http://localhost:8080/products/1

What you just built

  • POST /products creates a product.

  • GET /products lists products.

  • GET /products/1 retrieves one product.

  • PUT /products/1 replaces the product's data.

  • DELETE /products/1 deletes it.

  • sync.Mutex protects the shared map from simultaneous unsafe access.

Important limitation: The data is stored in memory. It disappears when the program stops. A real product API would normally use a database and should include request-size limits, stronger validation, appropriate error handling, and authentication where needed. This learning example also uses separate handlers for the collection and individual-product paths; it does not implement every REST convention.

MCQ 14

Why does this example validate a product's name and price?

  • A. To prevent obviously invalid data from entering the application.

  • B. To make the server install Go automatically.

  • C. To make the data survive a restart.

  • D. To replace the database.

Answer: A. Validation checks whether submitted data meets the application's rules. Persistence requires a database or another durable storage mechanism.

Chapter 29 — Authentication and security basics

Authentication answers: Who are you?

Authorization answers: What are you allowed to do?

For example, logging in identifies a user. Checking whether that user may delete a product is an authorization decision.

MCQ 15

How should an application store user passwords?

  • A. As plain text in a database.

  • B. In source code.

  • C. Using a suitable password-hashing algorithm with a unique salt.

  • D. In a public JSON file.

Answer: C. Passwords should be processed with a dedicated password-hashing algorithm, such as Argon2id or bcrypt, rather than stored as readable text or protected only by ordinary fast hashing.

MCQ 16

A user is logged in but is not allowed to delete products. Which concept determines whether the deletion should be permitted?

  • A. Authentication alone

  • B. Authorization

  • C. JSON formatting

  • D. A loop

Answer: B — Authorization. Being logged in does not automatically grant permission to perform every action.

Practical task: Protect an endpoint with a simple token

This example demonstrates the basic idea of checking a token. It is a learning exercise, not a complete production authentication system.

Go

package main

import (
    "net/http"
    "os"
)

func main() {
    secret := os.Getenv("APP_TOKEN")
    if secret == "" {
        panic("APP_TOKEN must be configured")
    }

    http.HandleFunc("/private", func(w http.ResponseWriter, r *http.Request) {
        if r.Header.Get("Authorization") != "Bearer "+secret {
            http.Error(w, "Unauthorized", http.StatusUnauthorized)
            return
        }

        w.Write([]byte("Access granted"))
    })

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

On macOS or Linux, start it with:

Bash

APP_TOKEN='replace-with-a-local-test-token' go run main.go

Try a request without a token:

Bash

curl http://localhost:8080/private

It should return an unauthorized response.

Try again with the test token:

Bash

curl http://localhost:8080/private \
  -H 'Authorization: Bearer replace-with-a-local-test-token'

Expected response:

Access granted

What this teaches: The server checks whether the request contains the expected credential before granting access.

For real applications, use HTTPS, proper user identity and authorization checks, secure token management, expiration and revocation strategies, and established authentication libraries or identity providers. A single shared token is not a substitute for a complete user login system.

Chapter 30 — Deploying an application

Deployment means making your application available outside your development environment, usually on a server.

MCQ 17

What is the difference between go run and go build?

  • A. They are identical in every respect.

  • B. go run runs the program; go build compiles it into an executable.

  • C. go build creates a database automatically.

  • D. go run only works with web servers.

Answer: B. Both compile code as needed, but go build produces an executable you can run separately.

Practical task: Build and run an executable

Assume your project has:

hello-server/
└── main.go

From inside the project directory, run:

Bash

go build -o hello-server

On macOS or Linux, run:

Bash

./hello-server

On Windows, the executable will normally be named hello-server.exe, so run:

PowerShell

.\hello-server.exe

For the web server from Chapter 26, the built executable starts the same HTTP server when run.

MCQ 18

Before exposing a Go web server to the public internet, which action is important?

  • A. Remove all error handling.

  • B. Publish private credentials in the source code.

  • C. Configure security, appropriate network access, and reliable operation.

  • D. Disable all input validation.

Answer: C. A working program is not automatically ready for public use. Deployment requires attention to security, configuration, networking, logging, and operational reliability.

A simple deployment checklist

Before deploying a Go application, check the following:

  • It builds successfully with go build.

  • Configuration is supplied through the environment rather than hardcoded secrets.

  • Errors are handled and useful logs are available.

  • The application listens on the appropriate network interface and port for its hosting environment.

  • The database and any required files are available to the running application.

  • Access controls and HTTPS are configured appropriately.

  • The application can be restarted and monitored.

One important detail: the earlier web-server examples bind to localhost:8080, which makes them accessible only from the same machine. A public deployment commonly needs to bind to an appropriate interface and use the hosting platform's assigned port. Do not expose a development server publicly without reviewing its security and operational configuration.

Block 3 — Final skill test

Try these without looking back at the chapter explanations.

Block 3 assessment

0/5 answered

1. Which Go feature reads an environment variable?

A. os.Getenv

B. fmt.Println

C. strings.Contains

D. json.NewEncoder

2. Which SQL command reads records?

A. INSERT

B. SELECT

C. UPDATE

D. DELETE

3. Which HTTP method is conventionally used to create a resource?

A. GET

B. POST

C. DELETE

D. OPTIONS

4. Where should an application store durable product records?

A. Only in a local variable

B. Only in an in-memory map

C. In persistent storage such as a database

D. Only in a printed message

5. What is the purpose of authentication?

A. Determine a user's identity

B. Calculate a discount

C. Compile a program

D. Format JSON

Check answers

Final practical challenge — Build a persistent product API

This is the most important exercise in Block 3. Combine the concepts rather than studying them independently.

Build an application with the following requirements:

  1. A Go HTTP server exposes /products.

  2. POST /products accepts a product name and price as JSON.

  3. GET /products returns saved products as JSON.

  4. GET /products/1 retrieves a product by its ID.

  5. PUT /products/1 updates a product.

  6. DELETE /products/1 deletes a product.

  7. Products are stored in SQLite, so they survive application restarts.

  8. Invalid JSON, invalid prices, and nonexistent product IDs produce appropriate error responses.

  9. SQL queries use parameters rather than concatenating user input.

  10. The application uses configuration and sensible error handling.

Suggested folder structure:

product-api/
├── go.mod
├── go.sum
├── main.go
├── database.go
├── handlers.go
└── models.go

Do not create all these files just to make the project look professional. Each file should have a clear responsibility: database setup, HTTP handlers, data models, or application startup.

Completion test: Stop and restart the application. If the products are still available, your persistence requirement works. Then test invalid input and verify that one user's request cannot accidentally modify or delete another product's record.

What you should now understand

Block 3 moves you from writing standalone programs to understanding the building blocks of real applications:

  • Command-line tools and configurable programs.

  • SQL databases and Go database connections.

  • HTTP requests, web servers, and JSON APIs.

  • CRUD operations and input validation.

  • Authentication and authorization basics.

  • Building and deploying executable applications.

You do not need to master every production detail at once. First, make a simple API work. Then add database storage. Then validate requests and secure the application. That sequence is easier to understand and debug than trying to build everything simultaneously.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.