Daaang Amy
open main menu
Part of series: Learning the Backend

Let's Go: Ch 1-4

/ 4 min read
Last updated:

As I’m going through this book (Let’s Go), I’m going to be making a few notes about what I’ve learned from it. Mostly things that I haven’t encountered before.

The Internal Folder

internal is a keyword! It has special meaning and behavior in Go. Any packages that are created within a folder named internal CANNOT be imported by code OUTSIDE the project. This is great for structuring your project in a way that your package is intended to be used.

Thoughts on Interfaces

Interfaces are so interesting. It’s wild that you can implicitly satisfy a interface and just create your own version of an object and you can pass it into a function that uses a different “type” i.e. interface. It gives you so much flexibility to build out custom functionality!

So for web servers, I can make my own http handler object just by satisfying the http.Handler interface.

The best part about the net/http library is all of the automatic sanitation go does for you for routes and paths. This makes our web servers much safer from attacks.

Command Line Flags

Go provides a quick and easy way to parse command line arguments and flags into a variable. It even takes a default value for you and there’s even a automated help flag.

addr := flag.String("addr", ":4000", "HTTP network address")

Leveled Logging

Leveled logging logs messages into the console with specific “levels” to give us more detailed information. Levels like info, error, or warning.

Decoupled Logging

The reason why you would want to log messages to standard streams (stderr and stdout) is to decouple the logging from your application. This lets us manage our logging outputs to another service or to a file on the disk drive.

go run ./cmd/web >> /tmp/info.log 2>>/tmp/error.log

>> is used to append to a file rather than overwrite it.

Rule of Thumb

Avoid using Panic() and Fatal() variations outside of the main function. Return errors instead. Only panic or exit directly from main().

How to Make Our Custom Logging a Dependency

So now we have custom logging in our code but the problem is we are only using it in main(). We are not using it anywhere else by default. The simple way to solve this is by making a global variable. This is what I usually do but this could lead to more errors and make your code look like hand waving magic. Another way is to inject dependency. I’ve heard of term thrown around but never really had a clear picture of it.

I think the best way to describe this is: all functions that would need some functionality to run needs to be injected with a dependency so that this functionality is available to be used at all times without having to import said extra functionality.

Actually here’s an analogy:

Desktop computers are dependent on monitors, mouse and keyboard to be used. So instead of carrying it around with us every time, we bake it into the design.

Which is a laptop. Portable, minimal and explicit.

So in go we can inject dependency by creating a struct and update functions so that they become methods of the struct.

Example:

type application struct {
  errorLog *log.Logger
  infoLog *log.Logger
}

func (app *application) yourHandler(w http.ResponseWriter, r *http.Request) {
  app.infoLog.Print("dependency injected!") 
}

Databases

It’s not a good idea to connect to a database as root. Instead, create a database user with limited permissions. For this project, the client should ONLY be able to insert, select, update or read on the snippetbox database.

Executing Safe SQL Statements

To execute a SQL statement, we first connect to the database and then run it with sql.DB.Exec(). It is important to write your statements with placeholders. The ? indicates a placeholder parameter. It is used to construct queries and help avoid SQL injection attacks.

INSERT INTO snippets (title, content, created, expires)
VALUES(?, ?, UTC_TIMESTAMP(), DATE_ADD(UTC_TIMESTAMP(), INTERVAL ? DAY))

How this works:

  1. When the database executes the command, it first pares & compiles the statement and then stores it away to be executed later. This is called a prepared statement.
  2. Exec() then passes the parameter values to the database. The database then executes the prepared statement with these values.

Since the values are delivered after the statement has been compiled, the database would not be able to change the intent of the statement.

Why We Create Our Own Error Messages

Why not use built in errors from packages? The reason is to encapsulate your packages entirely. You don’t want to be reliant on a dependency’s specific errors. Creating and returning your own errors provide better context for its behavior.

Closing Thoughts

I like how this book rotates between adding new features and refactoring. It models how production application development works.

The book does not go very in depth in SQL queries or modeling data tables. This makes sense because otherwise the book would be too long. It’s best to learn the basics of SQL queries and data modeling later.

Relevant Posts