{"id":15631352,"url":"https://github.com/andriisoldatenko/go-blog","last_synced_at":"2025-04-30T03:47:23.489Z","repository":{"id":75906649,"uuid":"158413872","full_name":"andriisoldatenko/go-blog","owner":"andriisoldatenko","description":"This tutorial explains how to build a web application using Go in less than 20 minutes.","archived":false,"fork":false,"pushed_at":"2019-01-21T18:47:45.000Z","size":5877,"stargazers_count":10,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-30T03:47:15.213Z","etag":null,"topics":["database","docker","go","golang","module","okta","postgresql","template-engine","web"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/andriisoldatenko.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-11-20T15:47:10.000Z","updated_at":"2025-03-27T01:55:28.000Z","dependencies_parsed_at":null,"dependency_job_id":"97ab8690-d08d-4799-b1b3-225477519e01","html_url":"https://github.com/andriisoldatenko/go-blog","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andriisoldatenko%2Fgo-blog","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andriisoldatenko%2Fgo-blog/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andriisoldatenko%2Fgo-blog/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andriisoldatenko%2Fgo-blog/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/andriisoldatenko","download_url":"https://codeload.github.com/andriisoldatenko/go-blog/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251638756,"owners_count":21619662,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["database","docker","go","golang","module","okta","postgresql","template-engine","web"],"created_at":"2024-10-03T10:40:01.016Z","updated_at":"2025-04-30T03:47:23.467Z","avatar_url":"https://github.com/andriisoldatenko.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"---\nlayout: blog_post\ntitle: \"Go web app in 20 minutes\"\nauthor: a_soldatenko\ndescription: \"This tutorial explains how to build a web application using Go in less than 20 minutes.\"\ntags: [go, html, blog]\n---\n\n# Go web app in 20 minutes\n\n## Go and web applications\n\nBuilding web apps in Go is simple, fun, and most of all: performant. If you come from an interpreted background (like myself, with Python), you might be surprised as how fast your Go apps can be! If you're all about minimizing your server-side latencies while still having a fun time writing code: you'll probably love Go as much as I do! =)\n\nIn this tutorial, I'll walk you through building a simple blog in Go using popular open source libraries (and an API service). If you'd like to see how to craft a simple, real-world Go application, continue reading!\n\n## Prerequisites\n\nBefore we dig into building web app in Go, please make sure you have installed the latest version of Go on your operating system. To see which version you have installed you can run:\n\n```bash\n$ go version\ngo version go1.11.2 darwin/amd64\n```\n\nIf you don't have Go installed, please go [Install the Go tools](https://golang.org/doc/install#install) and make sure you download the [latest stable version](https://golang.org/dl/) for your operating system. In this tutorial I'll be using Go 1.11.2, any version of Go 1.11 or higher should be OK though.\n\nOnce you've got Go installed, try running `$ go version` in your terminal again and make sure you've got it working properly.\n\n## Write Your First Go Web App\n\nAs I mentioned before, in this tutorial, you'll be building a blog. So the first thing you'll want to do is go create a `go-blog` folder to hold your project source and then create your first go file, `server.go`, in that folder.\n\n```bash\nmkdir go-blog\ncd go-blog\ntouch server.go\n```\n\nNow, copy the code below and paste it into your new `server.go` file. This file will contain the main source of your web server (app).\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n)\n\nfunc handler(writer http.ResponseWriter, request *http.Request) {\n\tfmt.Fprintf(writer, \"Hello %s!\", request.URL.Path[1:])\n}\n\nfunc main() {\n\thttp.HandleFunc(\"/\", handler)\n\thttp.ListenAndServe(\":8081\", nil)\n}\n```\n\nA *handler* receives and processes HTTP requests sent from the client. In this case, your handler function also uses the Go template engine (which is built into the language) to generate some HTML and finally bundles data into the HTTP response which is eventually sent back to the client.\n\nSimple, right?\n\nNow that your code is ready to roll, you can build and run your first go web app!\n\n```bash\ngo run server.go\n```\n\n**NOTE**: `go run` will both build (compile) and run your go program. It's a great way to test your application while in development, since you don't need to compile and then run your app, you can just do it all at once!\n\nNow, if you open your browser and go to `http://localhost:8081/okta`, you should see:\n\n```\nHello okta!\n```\n\n### Sidenote: Go Modules\n\nSince Go 1.11, there is *finally* an official solution for dependency management: [Go modules](https://github.com/golang/go/wiki/Modules)! While this feature is still technically experimental, it's already been widely adopted by the Go community and is considered a best practice.\n\nThe rest of this tutorial assumes you're using Go 1.11, primarily because you'll be creating your Go blog as a module, which means you won't need to place your project folder in a special location: you can store your Go code anywhere you want!\n\n## Initialize Your Go Project\n\nSince you've already created a `go-blog` project folder above, let's continue using this folder. All you need to do to initialize it is to run:\n\n```bash\n$ go mod init github.com/\u003cyour-github-username\u003e/go-blog\n```\n\nThe `go mod init` command (and be sure to substitute in your proper GitHub username) will initialize this folder as a Go module, meaning all of your future dependency management operations will work properly.\n\nWhen you run the `go mod init` command, Go will create a new `go.mod` file in your project folder. This file is what Go uses to detect and work with module dependencies.\n\nNow run the following commands to prepare your environment:\n\n```bash\nrm server.go\ntouch main.go\n```\n\nYour blog's main function (where everything starts) will be placed in the `main.go` file as we go along. The old `server.go` file you had created before can be safely deleted as we won't need it any longer.\n\n## Create Your Go Database Models\n\nIn Go, you will typically use a `struct` for mapping data models to a database. In this blog app, you'll need two models:  `Author` (which represents a blog author), and `Post` (which represents a blog post).\n\nGo ahead and create a `main.go` file and paste in the following code:\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"github.com/go-pg/pg\"\n\t\"github.com/go-pg/pg/orm\"\n)\n\ntype Author struct {\n\tName        string\n\tEmail       string\n}\n\nfunc (u Author) String() string {\n\treturn fmt.Sprintf(\"Author\u003c%s %s\u003e\", u.Name, u.Email)\n}\n\ntype Post struct {\n\tId          int64\n\tTitle       string\n\tContent     string\n\tAuthorEmail string\n}\n\nfunc (s Post) String() string {\n\treturn fmt.Sprintf(\"Post\u003c%d %s %s\u003e\", s.Id, s.Title, s.AuthorEmail)\n}\n\n\nfunc DBConn() (db *pg.DB) {\n\tdb = pg.Connect(\u0026pg.Options{\n\t\tDatabase: \"blog_db\",\n\t\tUser: \"blog\",\n\t\tPassword: \"blog_secret_password\",\n\t})\n\treturn db\n}\n\nfunc init() {\n\tdb := DBConn()\n\tcreateSchema(db)\n}\n\nfunc createSchema(db *pg.DB) error {\n\tfor _, model := range []interface{}{(*Author)(nil), (*Post)(nil)} {\n\t\terr := db.CreateTable(model, \u0026orm.CreateTableOptions{})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc createAuthorProfile(db *pg.DB, profile map[string]string) {\n\tfmt.Println(profile)\n\tauthor := \u0026Author{\n\t\tName: profile[\"name\"],\n\t\tEmail: profile[\"email\"],\n\t}\n\tdb.Model(author).Where(\"email = ?\", author.Email).SelectOrInsert()\n}\n```\n\nYou might have noticed that in this file we're using the [go-pg](https://github.com/go-pg/pg) library, which is a popular ORM for working with [Postgres](https://www.postgresql.org/) in Go. I \u003c3 Postgres and hope you do too!\n\n**PS**: If you're looking for a way to scale your existing Postgres databases for very high throughput loads, be sure to check out [Citus Data](https://www.citusdata.com/). They've built a very cool Postgres extension which makes scaling Postgres simple. I highly recommend it.\n\n\nNow that your database code has been written, why not try to run it? Go to your terminal and try to run the `main.go` program you just built.\n\n```bash\ngo run main.go\n```\n\nIf you can see error like this:\n\n```bash\nmain.go:7:5: cannot find package \"github.com/go-pg/pg\" in any of:\n```\n\nTry to run `go mod init github.com/andriisoldatenko/go-blog` and than run again `go run main.go`. Please don't forget to replace `andriisoldatenko` to you username.\nAHA! Did you see what happened there? If all went well, you should have seen output like the following:\n\n```bash\ngo run main.go\ngo: finding github.com/go-pg/pg/orm latest\ngo: downloading github.com/go-pg/pg v6.15.1+incompatible\ngo: finding github.com/jinzhu/inflection latest\n...\n```\n\nEven though you didn't explicitly install the `go-pg` library, when Go looked at your code and attempted to build your project, it noticed that the `go-pg` dependency was missing, and automatically downloaded it from GitHub for you! Pretty amazing, right?\n\nAnyhow, you should also have gotten an error, however, that looks something like this:\n\n```bash\npanic: dial tcp [::1]:5432: connect: connection refused\n```\n\nThis is expected since you didn't configure your database at all, and (probably) didn't have a Postgres database running already. Let's get this all set up.\n\n## Set Up a Postgres Database\n\n\nYou have a couple of options on how to run Postgres.\n\nIf you're on a Mac, you can always use Postgres via [Postgres app](https://postgresapp.com/). It's the simplest way to get Postgres running on your Mac.\n\nIf you're on some sort of *nix OS, you can always install Postgres via your favorite package manager (on Debian-based distributions this means you'd likely install the `postgresql` package).\n\nFinally, if you're super hip and into [containerizing everything](https://www.youtube.com/watch?v=gES4-X6y278), you might want to just install Postgres via [Docker](https://www.docker.com/).\n\nInstalling and managing Postgres is a bit out of scope for this article, so I'll let you decide which method to use. But… For simplicity's sake, I'll show you how to set up Postgres in a Docker container (because that's how I roll).\n\n```bash\n$ docker run --name postgresql -itd --restart always \\\n    --publish 5432:5432 \\\n    --volume /Users/andrii/docker/postgresql:/var/lib/postgresql \\\n    --env 'DB_USER=blog' --env 'DB_PASS=blog_secret_password' \\\n    --env 'DB_NAME=blog_db' \\\n    sameersbn/postgresql:10\n```\n\n**NOTE**: You'll notice that in my `docker` command I'm using some specific folders to store my data. I'm also defining a database username and password. You'll want to adjust these settings accordingly for your environment.\n\nOnce this command has finished running, you can then check to ensure your shiny new Postgres database is ready to accept incoming connections by running the command below.\n\n```bash\ndocker logs -f postgresql\n# few missing lines\n2018-11-21 16:20:04.714 UTC [1] LOG:  listening on IPv4 address \"0.0.0.0\", port 5432\n2018-11-21 16:20:04.714 UTC [1] LOG:  listening on IPv6 address \"::\", port 5432\n2018-11-21 16:20:04.718 UTC [1] LOG:  listening on Unix socket \"/var/run/postgresql/.s.PGSQL.5432\"\n2018-11-21 16:20:04.801 UTC [1105] LOG:  database system was shut down at 2018-11-21 16:20:04 UTC\n2018-11-21 16:20:04.834 UTC [1] LOG:  database system is ready to accept connections\n```\n\nBecause I'm paranoid and like to make sure things are working as expected, I also like to connect to my Postgres DB manually using the `psql` CLI tool as well:\n\n```bash\npsql -h localhost -U blog -d blog_db -p 5432\nPassword for user blog:\npsql (10.5, server 10.4 (Ubuntu 10.4-2.pgdg18.04+1))\nType \"help\" for help.\n\nblog_db=\u003e \\dt;\nDid not find any relations.\nblog_db=\u003e\n```\n\nAs you can see above, I was able to connect to Postgres and verify that there are no tables in the `blog` DB just yet. So… Let's work on fixing that. =)\n\n## Initialize Postgres Tables\n\n\nNow that you have Postgres running, you need to do initialize your database tables. Once that's done, things should be workable!\n\nIn order to get this working, you need to tell your application how to properly connect to your shiny new Postgres database. To do this, open up `main.go` and find your DB connection settings (they should look like this):\n\n```go\nfunc DBConn() (db *pg.DB) {\n\tdb = pg.Connect(\u0026pg.Options{\n\t\tDatabase: \"blog_db\",\n\t\tUser: \"blog\",\n\t\tPassword: \"blog_secret_password\",\n\t})\n\treturn db\n}\n```\n\nNow, substitute in the appropriate connection settings for your Postgres instance.\n\nIf you now run `go run main.go`, you can see that your tables will now have been created to hold your application's authors and posts.\n\n```bash\nAuthor\u003c1 admin \u003e\n[Author\u003c1 admin \u003e]\nPost\u003c1 Cool story Author\u003c1 admin \u003e\u003e\n```\n\n## Create the Go Handlers\n\nNow that the basic database models are ready and functional, let's go ahead and create the handlers. The handlers are the Go functions that will support the blog's functionality: creating posts, viewing posts, etc.\n\nYou'll need to create a handler for each piece of functionality in the app.\n\nTo get started, copy and paste the code below into a new file named `handlers.go`.\n\n```go\npackage main\n\nimport (\n\t\"github.com/gorilla/context\"\n\t\"html/template\"\n\t\"log\"\n\t\"net/http\"\n\t\"strconv\"\n)\n\n\n// Get all blog posts and render template\nfunc AllPosts(w http.ResponseWriter, r *http.Request) {\n\tdb := DBConn()\n\tvar posts []Post\n\terr := db.Model(\u0026posts).Select()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tt, _ := template.ParseFiles(\"templates/layout.html\", \"templates/index.html\")\n\tdata := context.Get(r, \"data\")\n\textra := struct {\n\t\tCustomData\n\t\tPosts []Post\n\t}{CustomData: data.(CustomData), Posts: posts}\n\tt.Execute(w, extra)\n\tdefer db.Close()\n}\n\n// Return new blog Post html form on GET\nfunc NewPost(w http.ResponseWriter, r *http.Request) {\n\tdata := context.Get(r, \"data\")\n\tt, _ := template.ParseFiles(\"templates/layout.html\", \"templates/new.html\")\n\tt.Execute(w, data)\n}\n\n// Create new blog post post using form submit\nfunc InsertPost(w http.ResponseWriter, r *http.Request) {\n\tdb := DBConn()\n\tif r.Method == \"POST\" {\n\t\ttitle := r.FormValue(\"title\")\n\t\tcontent := r.FormValue(\"content\")\n\t\temail := context.Get(r, \"email\").(string)\n\t\tpost1 := \u0026Post{\n\t\t\tTitle: title,\n\t\t\tContent: content,\n\t\t\tAuthorEmail: email,\n\t\t}\n\t\terr := db.Insert(post1)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}\n\tdefer db.Close()\n\thttp.Redirect(w, r, \"/\", 301)\n}\n\n// Update post details\nfunc EditPost(w http.ResponseWriter, r *http.Request) {\n\tdb := DBConn()\n\tnId, err := strconv.ParseInt(r.URL.Query().Get(\"id\"), 10, 64)\n\t// Select user by primary key.\n\tpost := \u0026Post{Id: nId}\n\terr = db.Select(post)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tt, _ := template.ParseFiles(\"templates/layout.html\", \"templates/edit.html\")\n\tt.Execute(w, post)\n\tdefer db.Close()\n}\n```\n\n\nYou can see by looking through the code above that:\n\n- The `AllPosts` function displays all posts by pulling them out of the database and displaying them in a template (which hasn't yet been built).\n- The `NewPost` function displays the HTML form which allows a user to create a new post.\n- The `InsertPost` function takes a new post and stores it in the database.\n- Finally, the `EditPost` function allows a user to edit a post.\n\n**NOTE**: I didn't implement post deletion here. This is something you should try to implement yourself! It'll be fun! Hint: here's a [documentation link](https://godoc.org/github.com/go-pg/pg#DB.Delete) you may find useful.\n\n## Create Go Templates\n\nOne of the things I love most about Go is that it comes with an out-of-the-box template engine that is really powerful.\n\nTo make use of Go's templating engine, all you need to do is create a new folder named `templates` in your project directory and define your template files inside.\n\nFirstly, create `templates/layout.html` and paste the following code into this file.\n\n```go\n\u003c!doctype html\u003e\n\u003chtml lang=\"en\"\u003e\n\u003chead\u003e\n\t\u003cmeta charset=\"utf-8\"\u003e\n\t\u003cmeta http-equiv=\"X-UA-Compatible\" content=\"IE=9\"\u003e\n\t\u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\"\u003e\n\t\u003clink href=\"/static/css/bootstrap.min.css\" rel=\"stylesheet\"\u003e\n\t\u003clink href=\"/static/css/font-awesome.min.css\" rel=\"stylesheet\"\u003e\n\n  \t\u003ctitle\u003eBlog\u003c/title\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n\u003cnav class=\"navbar navbar-expand-lg navbar-light bg-light\"\u003e\n\t\u003cdiv class=\"collapse navbar-collapse\" id=\"navbarSupportedContent\"\u003e\n\t\t\u003cul class=\"navbar-nav mr-auto\"\u003e\n\t\t\t\u003cli class=\"nav-item active\"\u003e\n\t\t\t\t\u003ca class=\"nav-link\" href=\"#\"\u003eHome \u003cspan class=\"sr-only\"\u003e(current)\u003c/span\u003e\u003c/a\u003e\n\t\t\t\u003c/li\u003e\n\t\t\t\u003cli class=\"nav-item active\"\u003e\n\t\t\t\t\u003ca class=\"nav-link\" href=\"/new/\"\u003eCreate Post\u003c/a\u003e\n\t\t\t\u003c/li\u003e\n\t{{ if .IsAuthenticated }}\n\t\t\t\u003cli class=\"nav-item\"\u003e\n\t\t\t\t\u003ca class=\"nav-link\" href=\"/logout/\"\u003eLogout\u003c/a\u003e\n\t\t\t\u003c/li\u003e\n\t\t\t\u003cli class=\"nav-item\"\u003e\n\t\t\t\t\u003ca class=\"nav-link\" href=\"/profile/\"\u003e{{ .Author.name }}\u003c/a\u003e\n\t\t\t\u003c/li\u003e\n\t{{ else }}\n\t\t\t\u003cli class=\"nav-item\"\u003e\n\t\t\t\t\u003ca class=\"nav-link\" href=\"/login/\"\u003eLogin\u003c/a\u003e\n\t\t\t\u003c/li\u003e\n\t{{ end }}\n\t\t\u003c/ul\u003e\n\t\u003c/div\u003e\n\u003c/nav\u003e\n\u003cdiv class=\"container\"\u003e\n\n{{ block \"content\" . }}{{ end }}\n\n\u003c/div\u003e \u003c!-- /container --\u003e\n\n\u003cscript src=\"/static/js/jquery-2.1.1.min.js\"\u003e\u003c/script\u003e\n\u003cscript src=\"/static/js/bootstrap.min.js\"\u003e\u003c/script\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\nThis is a base template that all other templates will inject their HTML into. Keeping it separated like this makes it easier to build complex UIs with minimal code reuse.\n\nNext, create a file named `templates/index.html` and paste the following code inside of it. This file will hold all of the app's home page markup. This page will be used to display all blog posts (ordered by date, descending).\n\n```html\n{{ define \"content\" }}\n\u003cdiv class=\"container\"\u003e\n    \u003cp\u003e All Posts\u003c/p\u003e\n\u003c/div\u003e\n\u003ctable class=\"table\"\u003e\n\t\u003cthead class=\"thead-dark\"\u003e\n\t\u003ctr\u003e\n\t\t\u003cth\u003eID\u003c/th\u003e\n\t\t\u003cth\u003eName\u003c/th\u003e\n\t\t\u003cth\u003eView\u003c/th\u003e\n\t\t\u003cth\u003eEdit\u003c/th\u003e\n\t\t\u003cth\u003eDelete\u003c/th\u003e\n\t\u003c/tr\u003e\n\t\u003c/thead\u003e\n\t\u003ctbody\u003e\n  {{ range .Posts }}\n\t\u003ctr\u003e\n\t\t\u003cth scope=\"col\"\u003e{{ .Id }}\u003c/th\u003e\n\t\t\u003cth scope=\"col\"\u003e {{ .Title }} \u003c/th\u003e\n\t\t\u003cth scope=\"col\"\u003e\u003ca href=\"/show?id={{ .Id }}\"\u003eView\u003c/a\u003e\u003c/th\u003e\n\t\t\u003cth scope=\"col\"\u003e\u003ca href=\"/edit?id={{ .Id }}\"\u003eEdit\u003c/a\u003e\u003c/th\u003e\n\t\t\u003cth scope=\"col\"\u003e\u003ca href=\"/delete?id={{ .Id }}\"\u003eDelete\u003c/a\u003e\u003ctd\u003e\n\t\u003c/tr\u003e\n  {{ end }}\n\t\u003c/tbody\u003e\n\u003c/table\u003e\n{{ end }}\n```\n\n\nNow, create a file named `templates/new.html` and paste in the following code. This markup will be displayed if a user wants to create a new post.\n\n```html\n{{ define \"content\" }}\n\u003cdiv class=\"container\"\u003e\n\t\u003cp\u003eNew Blog Post\u003c/p\u003e\n\t\u003cform action=\"/new/insert/\" method=\"post\"\u003e\n\t\t\u003cdiv class=\"form-group\"\u003e\n\t\t\t\u003cinput type=\"text\" name=\"title\" class=\"form-control\" placeholder=\"Enter title\"\u003e\n\t\t\u003c/div\u003e\n\t\t\u003cdiv class=\"form-group\"\u003e\n\t\t\t\u003ctextarea class=\"form-control\" rows=\"3\"  placeholder=\"Enter your story\"\u003e\u003c/textarea\u003e\n\t\t\u003c/div\u003e\n\t\t\u003cinput type=\"submit\" value=\"Save post\" /\u003e\n\t\u003c/form\u003e\n\u003c/div\u003e\n{{ end }}\n```\n\nFinally, create a file named `templates/edit.html`. This file will hold the markup that renders the post editing UI. Paste the following code into this file.\n\n```html\n{{ define \"content\" }}\n\u003ch2\u003eEdit Plog Post\u003c/h2\u003e\n\u003cform method=\"POST\" action=\"update\"\u003e\n\t\u003cinput type=\"hidden\" name=\"uid\" value=\"{{ .Id }}\" /\u003e\n\t\u003clabel\u003e Name \u003c/label\u003e\u003cinput type=\"text\" name=\"name\" value=\"{{ .Name }}\"  /\u003e\u003cbr /\u003e\n\t\u003cinput type=\"submit\" value=\"Save Blog Post\" /\u003e\n\u003c/form\u003e\u003cbr /\u003e\n{{ end }}\n```\n\n## Add User Authentication to Your Go App\n\nNow that you've got a minimalistic blog with a back-end and front-end, you need to add in the concept of users and user authentication to your app. You've got to add the ability to let users log into your blog and create a post!\n\nThere are lots of different ways to do this yourself, but the reality of the situation is that there are a lot of risks that come with implementing user authentication yourself: standards change, it's easy to miss something important that can cause a security risk later, etc. It's a lot safer to just… Not roll your own authentication.\n\nThat's why instead of rolling user authentication manually, I'll show you how to accomplish the same thing in a simpler, safer way using Okta's [free API service](https://developer.okta.com) for user management.\n\nTo get started, create a new file named `auth_handler.go`. This file will hold your auth-related handler functions.\n\n```go\npackage main\n\nimport (\n\t\"bytes\"\n\t\"crypto/rand\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"github.com/gorilla/context\"\n\t\"github.com/gorilla/sessions\"\n\t\"github.com/okta/okta-jwt-verifier-golang\"\n\t\"html/template\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"os\"\n)\n\nvar (\n\tstate = \"ApplicationState\"\n\tnonce = \"NonceNotSetYet\"\n\tsessionStore = sessions.NewCookieStore([]byte(\"okta-hosted-login-session-store\"))\n)\n\n\ntype Exchange struct {\n\tError            string `json:\"error,omitempty\"`\n\tErrorDescription string `json:\"error_description,omitempty\"`\n\tAccessToken      string `json:\"access_token,omitempty\"`\n\tTokenType        string `json:\"token_type,omitempty\"`\n\tExpiresIn        int    `json:\"expires_in,omitempty\"`\n\tScope            string `json:\"scope,omitempty\"`\n\tIdToken          string `json:\"id_token,omitempty\"`\n}\n\ntype CustomData struct {\n\tAuthor          map[string]string\n\tIsAuthenticated bool\n\tEmail           string\n}\n\nfunc GenerateNonce() (string, error) {\n\tnonceBytes := make([]byte, 32)\n\t_, err := rand.Read(nonceBytes)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"could not generate nonce\")\n\t}\n\n\treturn base64.URLEncoding.EncodeToString(nonceBytes), nil\n}\n\nfunc AuthHandler(next http.HandlerFunc) http.HandlerFunc {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\tauthor := getAuthorData(r)\n\t\tdata := CustomData{\n\t\t\tAuthor:         author,\n\t\t\tIsAuthenticated: isAuthenticated(r),\n\t\t}\n\t\tcontext.Set(r, \"data\", data)\n\t\tcontext.Set(r, \"email\", author[\"email\"])\n\t\tnext.ServeHTTP(w, r)\n\t}\n}\n\nfunc LoginHandler(w http.ResponseWriter, r *http.Request) {\n\tnonce, _ = GenerateNonce()\n\tvar redirectPath string\n\n\tq := r.URL.Query()\n\tq.Add(\"client_id\", os.Getenv(\"CLIENT_ID\"))\n\tq.Add(\"response_type\", \"code\")\n\tq.Add(\"response_mode\", \"query\")\n\tq.Add(\"scope\", \"openid profile email\")\n\tq.Add(\"redirect_uri\", \"http://localhost:8081/authorization-code/callback\")\n\tq.Add(\"state\", state)\n\tq.Add(\"nonce\", nonce)\n\tredirectPath = os.Getenv(\"ISSUER\") + \"/v1/authorize?\" + q.Encode()\n\n\thttp.Redirect(w, r, redirectPath, http.StatusMovedPermanently)\n}\n\nfunc LogoutHandler(w http.ResponseWriter, r *http.Request) {\n\tsession, err := sessionStore.Get(r, \"okta-hosted-login-session-store\")\n\tif err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\n\t}\n\n\tdelete(session.Values, \"id_token\")\n\tdelete(session.Values, \"access_token\")\n\tcontext.Clear(r)\n\tsession.Save(r, w)\n\n\thttp.Redirect(w, r, \"/\", http.StatusMovedPermanently)\n}\n\nfunc ProfileHandler(w http.ResponseWriter, r *http.Request) {\n\tdata := context.Get(r, \"data\")\n\ttpl, _ := template.ParseFiles(\"templates/layout.html\", \"templates/profile.html\")\n\ttpl.Execute(w, data)\n}\n\nfunc AuthCodeCallbackHandler(w http.ResponseWriter, r *http.Request) {\n\t// Check the state that was returned in the query string is the same as the above state\n\tif r.URL.Query().Get(\"state\") != state {\n\t\tfmt.Fprintln(w, \"The state was not as expected\")\n\t\treturn\n\t}\n\t// Make sure the code was provided\n\tif r.URL.Query().Get(\"code\") == \"\" {\n\t\tfmt.Fprintln(w, \"The code was not returned or is not accessible\")\n\t\treturn\n\t}\n\n\texchange := exchangeCode(r.URL.Query().Get(\"code\"), r)\n\n\tsession, err := sessionStore.Get(r, \"okta-hosted-login-session-store\")\n\tif err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\n\t}\n\n\t_, verificationError := verifyToken(exchange.IdToken)\n\n\tif verificationError != nil {\n\t\tfmt.Println(verificationError)\n\t}\n\n\tif verificationError == nil {\n\t\tsession.Values[\"id_token\"] = exchange.IdToken\n\t\tsession.Values[\"access_token\"] = exchange.AccessToken\n\n\t\tsession.Save(r, w)\n\t}\n\n\tdb := DBConn()\n\tcreateAuthorProfile(db, getAuthorData(r))\n\thttp.Redirect(w, r, \"/\", http.StatusMovedPermanently)\n}\n\nfunc exchangeCode(code string, r *http.Request) Exchange {\n\tauthHeader := base64.StdEncoding.EncodeToString(\n\t\t[]byte(os.Getenv(\"CLIENT_ID\") + \":\" + os.Getenv(\"CLIENT_SECRET\")))\n\n\tq := r.URL.Query()\n\tq.Add(\"grant_type\", \"authorization_code\")\n\tq.Add(\"code\", code)\n\tq.Add(\"redirect_uri\", \"http://localhost:8081/authorization-code/callback\")\n\n\turl := os.Getenv(\"ISSUER\") + \"/v1/token?\" + q.Encode()\n\n\treq, _ := http.NewRequest(\"POST\", url, bytes.NewReader([]byte(\"\")))\n\th := req.Header\n\th.Add(\"Authorization\", \"Basic \"+authHeader)\n\th.Add(\"Accept\", \"application/json\")\n\th.Add(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\th.Add(\"Connection\", \"close\")\n\th.Add(\"Content-Length\", \"0\")\n\n\tclient := \u0026http.Client{}\n\tresp, _ := client.Do(req)\n\tbody, _ := ioutil.ReadAll(resp.Body)\n\tdefer resp.Body.Close()\n\tvar exchange Exchange\n\tjson.Unmarshal(body, \u0026exchange)\n\n\treturn exchange\n\n}\n\nfunc isAuthenticated(r *http.Request) bool {\n\tsession, err := sessionStore.Get(r, \"okta-hosted-login-session-store\")\n\n\tif err != nil || session.Values[\"id_token\"] == nil || session.Values[\"id_token\"] == \"\" {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\nfunc getAuthorData(r *http.Request) map[string]string {\n\tm := make(map[string]string)\n\n\tsession, err := sessionStore.Get(r, \"okta-hosted-login-session-store\")\n\n\tif err != nil || session.Values[\"access_token\"] == nil || session.Values[\"access_token\"] == \"\" {\n\t\treturn m\n\t}\n\n\treqUrl := os.Getenv(\"ISSUER\") + \"/v1/userinfo\"\n\n\treq, _ := http.NewRequest(\"GET\", reqUrl, bytes.NewReader([]byte(\"\")))\n\th := req.Header\n\th.Add(\"Authorization\", \"Bearer \"+session.Values[\"access_token\"].(string))\n\th.Add(\"Accept\", \"application/json\")\n\n\tclient := \u0026http.Client{}\n\tresp, _ := client.Do(req)\n\tbody, _ := ioutil.ReadAll(resp.Body)\n\tdefer resp.Body.Close()\n\tjson.Unmarshal(body, \u0026m)\n\n\treturn m\n}\n\nfunc verifyToken(t string) (*jwtverifier.Jwt, error) {\n\tfmt.Println(nonce)\n\ttv := map[string]string{}\n\ttv[\"nonce\"] = nonce\n\ttv[\"aud\"] = os.Getenv(\"CLIENT_ID\")\n\tjv := jwtverifier.JwtVerifier{\n\t\tIssuer:           os.Getenv(\"ISSUER\"),\n\t\tClaimsToValidate: tv,\n\t}\n\n\tresult, err := jv.New().VerifyIdToken(t)\n\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"%s\", err)\n\t}\n\n\tif result != nil {\n\t\treturn result, nil\n\t}\n\n\treturn nil, fmt.Errorf(\"token could not be verified: %s\", \"\")\n}\n```\n\n\nThe file you just created now holds all of your authentication-related operations, including:\n\n- Login (via OpenID Connect),\n- Logout (via OpenID Connect),\n- Profile management (modifying your user profile), and\n- Helper functions to determine whether or not a user is authenticated or not\n\nThe way all this user authentication works is via OpenID Connect's [Authorization Code flow](https://developer.okta.com/authentication-guide/implementing-authentication/auth-code). This flow allows a user to sign into a server-side web application in a secure and standardized way.\n\nIf you'd like to see how these implementation details work, look through the code above and try to understand how the handlers are working. Reading code is one of the best ways to familiarize yourself with new concepts.\n\nBack to business though.\n\nBefore you can run your new code and test everything out, you'll need to actually set up and configure Okta.\n\nTo do this, you need to first go create a free [Okta Developer account](https://developer.okta.com/signup/).\n\nOnce you've created your account and are in the Okta dashboard, copy down your **Org URL** value from the top-right hand side of your dashboard page. This will be needed in a moment.\n\nThen visit the **Applications** tab, and click the **Add Application** button. Select the **Web** icon and click **Next**. On the following page, you'll need to define your app-specific settings (these tell Okta what type of app you're building so it knows how to secure it properly).\n\n- **Name**: Set the name to whatever you like. I prefer to name my Okta applications the same name as my project.\n- **Base URIs**: Set this to `http://localhost:8081`\n- **Login redirect URIs**: Set this to `http://localhost:8081/authorization-code/callback`\n\nNext, click **Done** and your new application will be created. Copy down the **Client ID** and **Client Secret** values on this page. You'll need these in a moment.\n\nNow what you need to do is create a file named `.env` in your project directory. This file will hold all of your super-secret application values: like your Okta application's ID and secret, etc. Create this file and insert the following values into it.\n\n```bash\nexport CLIENT_ID=\u003cyour client ID\u003e\nexport CLIENT_SECRET=\u003cyour client secet\u003e\nexport ISSUER=\u003cyour org URL\u003e/oauth2/default\n```\n\nTo \"activate\" these environment variables so your app can see them, run the command below:\n\n```bash\nsource .env\n```\n\n\nNow, you need to create a `templates/login.html` file to hold the login page markup. Paste the following code inside this new file.\n\n```html\n{{ define \"content\" }}\n\n\u003cdiv id=\"sign-in-widget\"\u003e\u003c/div\u003e\n\u003cscript type=\"text/javascript\"\u003e\n\tvar config = {};\n\tconfig.baseUrl = \"{{ .BaseUrl }}\";\n\tconfig.clientId = \"{{ .ClientId }}\";\n\tconfig.redirectUri = \"http://localhost:8081/authorization-code/callback\";\n\tconfig.authParams = {\n\t\t{{/*issuer: \"{{ .Issuer }}\",*/}}\n\t\tresponseType: 'code',\n\t\t{{/*state: \"{{ .State }}\" || false,*/}}\n\t\tdisplay: 'page',\n\t\tscope: ['openid', 'profile', 'email'],\n\t\t{{/*nonce: '{{ .Nonce }}',*/}}\n\t};\n\tnew OktaSignIn(config).renderEl(\n\t\t{ el: '#sign-in-widget' },\n\t\tfunction (res) {\n\t\t}\n\t);\n\u003c/script\u003e\n\n{{ end }}\n```\n\n## Create Your Go Router\n\nSo far you've built out the blog functionality in templates, handlers, and models. But you still need to plug it all together with a router. A router simply maps URL requests from a user to handler code.\n\nOpen up `main.go` and insert the following code (replacing what was there before).\n\n```go\npackage main\n\nimport (\n\t\"net/http\"\n\t\"os\"\n\n\t\"github.com/go-http-utils/logger\"\n)\n\n\nfunc main() {\n\tmux := http.NewServeMux()\n\tfiles := http.FileServer(http.Dir(\"./static\"))\n\n\tmux.HandleFunc(\"/\", AuthHandler(AllPosts))\n\tmux.HandleFunc(\"/new/\", AuthHandler(NewPost))\n\tmux.HandleFunc(\"/new/insert/\", AuthHandler(InsertPost))\n\tmux.HandleFunc(\"/edit/\", EditPost)\n\tmux.HandleFunc(\"/login/\", LoginHandler)\n\tmux.HandleFunc(\"/logout/\", LogoutHandler)\n\tmux.HandleFunc(\"/authorization-code/callback/\", AuthCodeCallbackHandler)\n\tmux.HandleFunc(\"/profile/\", AuthHandler(ProfileHandler))\n\tmux.Handle(\"/static/\", http.StripPrefix(\"/static/\", files))\n\n\thttp.ListenAndServe(\"0.0.0.0:8081\", logger.Handler(mux, os.Stdout, logger.DevLoggerType))\n}\n```\n\n## Finalizing Your Go Application\n\nAt this point, you've successfully built your first Go web application! Congratulations!\n\nTo test it out, run `go run *.go`, then visit `http://localhost:8081` in your browser. If you everything is working properly you should now be able to use your new blog!\n\n**NOTE**: `go run *.go` means to run all `*.go` files in current folder, we need this because we have one `main` package inside different go files.\n\nIf you liked this small Go tutorial, you might want to check out some of our other articles below (or [follow us on Twitter](https://twitter.com/oktadev), [Facebook](https://www.facebook.com/oktadevelopers), and [YouTube](https://www.youtube.com/channel/UC5AMiWqFVFxF1q9Ya1FuZ_Q/featured)). We publish all sorts of technical tutorials and guides you may find interesting.\n\n- [Build a Single-Page App with Go and Vue](https://developer.okta.com/blog/2018/10/23/build-a-single-page-app-with-go-and-vue)\n- [OpenID Connect - A Primer](https://developer.okta.com/blog/2017/07/25/oidc-primer-part-1)\n- [Learn JavaScript in 2019](https://developer.okta.com/blog/2018/12/19/learn-javascript-in-2019)\n- [Build and Test a React Native App with TypeScript and OAuth 2.0](https://developer.okta.com/blog/2018/11/29/build-test-react-native-typescript-oauth2)\n- [Build a Desktop App with Electron and Authentication](https://developer.okta.com/blog/2018/09/17/desktop-app-electron-authentication)\n\nIf you have any questions, please don’t hesitate to leave a comment below, or ask us on our [Okta Developer Forums](https://devforum.okta.com/).","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandriisoldatenko%2Fgo-blog","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fandriisoldatenko%2Fgo-blog","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandriisoldatenko%2Fgo-blog/lists"}