Skip to content

Instantly share code, notes, and snippets.

@ArchTaqi
Last active February 8, 2019 20:08
Show Gist options
  • Save ArchTaqi/799713ced685bdbccdd0b953dbdbfcc6 to your computer and use it in GitHub Desktop.
Save ArchTaqi/799713ced685bdbccdd0b953dbdbfcc6 to your computer and use it in GitHub Desktop.
My Journey to Go App

Installation

Install Using Snap

sudo snap install go --classic
# open these files `nano ~/.profile` or `nano ~/.zshrc` and add below at the end
export GOPATH=$HOME/goproj
export PATH=$PATH:/usr/local/go/bin:$GOPATH/bin
# refresh file using 
`source ~/.profile` or `source ~/.zshrc`

Install by yourself

go1.11.2.linux-amd64.tar.gz

wget https://dl.google.com/go/go1.11.2.linux-amd64.tar.gz
sha256sum go1.11.2.linux-amd64.tar.gz
sudo tar -xvf go1.11.2.linux-amd64.tar.gz
sudo mv go /usr/local

Setup Go Workspaces

You need to set GOROOT, GOPATH and PATH variables.

  • GOROOT is the location where Go package system binaries are installed.
  • GOPATH is your project work directory.
  • PATH is where your system will look for go binaries when you type the go command.

Add the following lines in your ~/.profile with any editor gedit ~/.profile or gedit ~/.zshrc

export GOROOT="/usr/local/go"
export GOPATH="$HOME/goproj"
export PATH="$GOPATH/bin:$GOROOT/bin:$PATH"

and save it and then refresh the source file with source ~/.profile or source ~/.zshrc

To verify it's install ?

$ go version

Now verify the variables are set correctly by running go env :

$ go env
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/muhammadtaqi/.cache/go-build"
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GOPATH="/home/muhammadtaqi/go"
GORACE=""
GOROOT="/usr/local/go"
<output truncated for brevity>

Project Setup

After workspace setup, we can create our first project. Each of our subdirectories inside the src directory will represent a separate package or command, and each will contain at least one .go file.

To get started we’ll make a simple Hello World package:

mkdir src/helloWorld
cd src/helloWorld
touch main.go

added below code to main.go.

package main

import "fmt"

func main() {
	fmt.Println("Hello World")
}
  • go run main.go (runs the package)
  • go build (creates a standalone executable that you can run without the Go tool)
  • go install (same as go build, but it places the executable in the workspaces /bin directory so you can run it from anywhere in your filesystem*)

For this to work your PATH must include your workspaces /bin directory.

imports are relative to the src directory

If you have your helloWorld package in src/helloWorld, and you decide to create a sub-package in src/helloWord/anotherPackage, the import statement is relative to the src directory so import “helloWorld/anotherPackage”

Files in the same directory can be used as part of the same package if they have the same name

Make another file anotherfile.go in the same directory where main.go is;

package main

func privateHelperFunc() int {
  return 25
}

The main.go file will have access to the privateHelperFunc function as they act as though they are part of the same package.

  • Private functions

Functions that start with a lowercase letter (e.g. privateHelperFunc) are private and cannot be used outside the package.

  • Public functions

Functions that start with a uppercase letter (e.g. PublicHelperFunc) are public and are exported for use.

Assigning imported packages an alias When importing a package you can alias that package. In the below example, the fmt package is aliased as notfmt:

package main
import (
  notfmt "fmt"
)
func main() {
  notfmt.Println("Hello World")
}

Three folders

In your Go Workspace you will have three folders:

  • src (where your Go projects and source code sits)
  • bin (where your executables go when you install)
  • pkg (where packages you import compile to)

Go Api App

  • Navigate to your Go workspace (the default is ~/goproj)
  • Make a new directory under src (mkdir src/goApi) (goApi will be our package)

We will be building our Go API using iris (which is the Go equivalent of Express).

Iris is a fast, simple yet fully featured and very efficient web framework for Go.

install the iris package

`go get -u github.com/kataras/iris'

package main

import (
	"github.com/kataras/iris"
	"github.com/kataras/iris/middleware/logger"
	"github.com/kataras/iris/middleware/recover"
)

func main() {
	app := iris.New()
	/**
	* Logging
	 */
	app.Logger().SetLevel("debug")
	// Recover from panics and log the panic message to the application's logger ("Warn" level).
	app.Use(recover.New())
	// logs HTTP requests to the application's logger ("Info" level)
	app.Use(logger.New())

	/**
	* Routes
	 */

	// GET http://localhost:8080/regex/id
	// 'id' must be a string which contacts 10 or more numbers
	app.Get("/regex/{id:string regexp(^[0-9]{10}.*$)}", func(ctx iris.Context) {
		ctx.Writef("Hello %s", ctx.Params().Get("id"))
	})
	// POST http://localhost:8080/panic
	app.Post("/panic", func(ctx iris.Context) {
		// app will handle this panic message thanks to the app.use statement above
		panic("recover will log this panic message to the logger")
	})
	// GET http://localhost:8080/html
	app.Get("/html", func(ctx iris.Context) {
		// Return HTML
		ctx.HTML("<h1>Welcome</h1>")
	})
	// Put http://localhost:8080/ping
	app.Put("/ping", func(ctx iris.Context) {
		// Return a string
		ctx.WriteString("pong")
	})
	// Delete http://localhost:8080/hello
	app.Delete("/hello", func(ctx iris.Context) {
		// Return JSON
		ctx.JSON(iris.Map{"message": "Hello Iris!"})
	})

	// Start the server on port 8080
	// ignore server closed errors ([ERRO] 2018/04/09 12:25 http: Server closed)
	app.Run(iris.Addr(":8080"), iris.WithoutServerError(iris.ErrServerClosed))
}

The above example sets up 5 endpoints and starts the server on port 8080. Here are some important things I learnt while putting it together:

  • A panic typically means something went unexpectedly wrong. This is basically the equivalent of throw new Error() in Node. We use app.Use(recover.New()) so our sever can handle any panics thrown by the endpoints, stopping our application from going down in flames.

  • We can use Regex expressions in route definitions. In the GET request example above we have specified that an id has to be a string that contains 10 or more numbers.

  • Ignore server closed errors. We use iris.WithoutServerError(iris.ErrServerClosed) to prevent errors showing on the console when we close (CTRL + C) our server. If we don’t include this we’ll see [ERRO] 2018/04/09 12:25 http: Server closed every time we close it.

    Now let’s make our main package file (touch src/goApi/main.go).

package main

import (
	"github.com/kataras/iris"
	"github.com/kataras/iris/middleware/logger"
	"github.com/kataras/iris/middleware/recover"
	mgo "gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

// ToDo type
type ToDo struct {
	Title     string `bson:"title"`
	Completed bool   `bson:"completed"`
}

func main() {
	app := iris.New()
	/**
	* Logging
	 */
	app.Logger().SetLevel("debug")
	// Recover from panics and log the panic message to the application's logger ("Warn" level).
	app.Use(recover.New())
	// logs HTTP requests to the application's logger ("Info" level)
	app.Use(logger.New())

	/**
	* Mongo
	 */

	// Connection variables
	const (
		Host       = ""
		Username   = ""
		Password   = ""
		Database   = ""
		Collection = ""
	)

	// Mongo connection
	session, err := mgo.DialWithInfo(&mgo.DialInfo{
		Addrs:    []string{Host},
		Username: Username,
		Password: Password,
		Database: Database,
	})
	// If there is an error connecting to Mongo - panic
	if err != nil {
		panic(err)
	}
	// Close session when surrounding function has returned/ended
	defer session.Close()
	// mongogo is the database name
	db := session.DB(Database)
	// todos is the collection name
	collection := db.C(Collection)

	/**
	* Routes
	 */

	// Create todo using POST Request body
	app.Post("/addOne", func(ctx iris.Context) {
		// Create a new ToDo
		var todo ToDo
		// Pass the pointer of todo so it is updated with the result
		// which is the POST data
		err := ctx.ReadJSON(&todo)
		// If there is an error or no Title in the POST Body
		if err != nil || todo.Title == "" {
			ctx.StatusCode(iris.StatusBadRequest)
			ctx.JSON(iris.Map{"message": "Post body must be a JSON object with at least a Title!"})
			return
		}
		// Insert into the database
		collection.Insert(todo)
	})

	// Get todo by Title
	app.Get("/title/{title:string}", func(ctx iris.Context) {
		title := ctx.Params().Get("title")
		var results []ToDo
		err := collection.Find(bson.M{"title": title}).All(&results)
		if err != nil {
			ctx.StatusCode(iris.StatusBadRequest)
			ctx.JSON(iris.Map{"message": "An error occured", "error": err})
			return
		}
		ctx.JSON(iris.Map{"results": results})
	})
	// Get todo by Completed status
	app.Get("/completed/{completed:boolean}", func(ctx iris.Context) {
		completed, _ := ctx.Params().GetBool("completed")
		var results []ToDo
		err := collection.Find(bson.M{"completed": completed}).All(&results)
		if err != nil {
			ctx.StatusCode(iris.StatusBadRequest)
			ctx.JSON(iris.Map{"message": "An error occured", "error": err})
			return
		}
		ctx.JSON(iris.Map{"results": results})
	})
	// Update one todo by title
	app.Put("/title/{title:string}", func(ctx iris.Context) {
		// Get the title from the URL Parameter
		title := ctx.Params().Get("title")
		// Construct the query object
		query := bson.M{"title": title}
		// Create a new ToDo
		var change ToDo
		// Update change with the PUT body
		ctx.ReadJSON(&change)
		// Only update one record
		collection.Update(query, change)
	})
	// Update todo by completed
	app.Put("/completed/{completed:boolean}", func(ctx iris.Context) {
		// Get the completed status from the URL parameter
		completed, _ := ctx.Params().GetBool("completed")
		query := bson.M{"completed": completed}
		var change ToDo
		ctx.ReadJSON(&change)
		// Update all records
		collection.UpdateAll(query, bson.M{"$set": bson.M{"completed": change.Completed}})
	})
	// Remove by Title
	app.Delete("/title/{title:string}", func(ctx iris.Context) {
		title := ctx.Params().Get("title")
		collection.Remove(bson.M{"title": title})
	})
	// Remove by completed
	app.Delete("/completed/{completed:boolean}", func(ctx iris.Context) {
		completed, _ := ctx.Params().GetBool("completed")
		collection.RemoveAll(bson.M{"completed": completed})
	})

	// Run app on port 8080
	// ignore server closed errors ([ERRO] 2018/04/09 12:25 http: Server closed)
	app.Run(iris.Addr(":8080"), iris.WithoutServerError(iris.ErrServerClosed))
}

Here we’ve made seven endpoints and we’ve connected our application to MongoDB using mgo.

One of the most important concepts to learnt while making this API was Pointers.

Pointers allow you to pass a reference to the memory location of a variable, rather than the value itself. This would be, in a Node.js example, like amending a primitive value to a reference value.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment