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.
fmtB.
osC.
stringsD.
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.gomain.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 PramodOutput:
Hello, PramodHow it works
os.Argscontains the command-line arguments.len(os.Args) < 2checks whether a name was supplied.os.Args[1]retrieves the first argument after the program name.returnstops 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.goOutput:
Welcome to Go ApplicationOn macOS or Linux, set a custom value for a single run:
Bash
APP_NAME=MyStore go run main.goOutput:
Welcome to MyStoreKey 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
8080B. 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.
SELECTB.
DELETEC.
DROPD.
STOP
Answer: A — SELECT.
Essential SQL commands
| Command | Purpose | Example |
|---|---|---|
CREATE TABLE | Create a table | Define a products table |
INSERT | Add a record | Add a new product |
SELECT | Read records | Find all products |
UPDATE | Change records | Change a product's price |
DELETE | Remove records | Delete 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?
A table named
productsis created.A notebook is inserted with a price of 50.
The records are retrieved.
The price is changed to 60.
The notebook is retrieved using a condition.
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.goOpen a terminal inside product-store and initialize the project:
Bash
go mod init product-store
go get modernc.org/sqliteCreate 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: 1A 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/sqlprovides 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 whenmainreturns.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.
GETB.
DELETEC.
PATCHD.
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.gomain.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.goOpen http://localhost:8080 in your browser.
Expected page:
Welcome to my Go website!How it works
net/httpcontains tools for handling HTTP requests and responses.homeis a handler function that runs when a matching request arrives.http.ResponseWriterlets the handler write the response.*http.Requestcontains 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.goOpen http://localhost:8080/api/product.
Example response:
JSON
{"name":"Notebook","price":50}Important concepts:
Productis 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.gomain.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.goTest 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/productsRetrieve product 1:
Bash
curl http://localhost:8080/products/1Update 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/1What you just built
POST /productscreates a product.GET /productslists products.GET /products/1retrieves one product.PUT /products/1replaces the product's data.DELETE /products/1deletes it.sync.Mutexprotects 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.goTry a request without a token:
Bash
curl http://localhost:8080/privateIt 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 grantedWhat 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 runruns the program;go buildcompiles it into an executable.C.
go buildcreates a database automatically.D.
go runonly 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.goFrom inside the project directory, run:
Bash
go build -o hello-serverOn macOS or Linux, run:
Bash
./hello-serverOn Windows, the executable will normally be named hello-server.exe, so run:
PowerShell
.\hello-server.exeFor 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:
A Go HTTP server exposes
/products.POST /productsaccepts a product name and price as JSON.GET /productsreturns saved products as JSON.GET /products/1retrieves a product by its ID.PUT /products/1updates a product.DELETE /products/1deletes a product.Products are stored in SQLite, so they survive application restarts.
Invalid JSON, invalid prices, and nonexistent product IDs produce appropriate error responses.
SQL queries use parameters rather than concatenating user input.
The application uses configuration and sensible error handling.
Suggested folder structure:
product-api/
├── go.mod
├── go.sum
├── main.go
├── database.go
├── handlers.go
└── models.goDo 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.