├── .gitignore ├── README.md ├── tutorial_1 ├── README.md ├── main.go └── web │ └── test.html ├── tutorial_2 ├── README.md ├── main.go └── web │ ├── chat.html │ └── test.html └── tutorial_3 ├── README.md ├── main.go └── web ├── books.html ├── books_design.html └── test.html /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | .idea 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | webapp tutorial 2 | ====================== 3 | 4 | This tutorial series will help you get a web application up and running using the following technologies: 5 | 6 | * Go for developing the backend 7 | * Angularjs for developing the frontend 8 | * Bootstrap and Font-Awesome for page layout and icons 9 | 10 | Click on each of the folders above to view the code and READMEs for each tutorial. 11 | -------------------------------------------------------------------------------- /tutorial_1/README.md: -------------------------------------------------------------------------------- 1 | Tutorial 1 2 | ========== 3 | 4 | In this first tutorial we get up and running with a basic web server that 5 | serves any files in the local directory. 6 | 7 | How to run 8 | ------------- 9 | 10 | * Install Go 11 | * http://golang.org/doc/install 12 | * Download this project from github and unzip 13 | * https://github.com/jakecoffman/golang-webapp-tutorial/archive/master.zip 14 | * Open a command window and `cd` to the tutorial_1 directory 15 | * Type `go run main.go` to start the server 16 | * Open a web browser and go to http://localhost/ 17 | 18 | If you get a message that says "It works!" then it's working. 19 | -------------------------------------------------------------------------------- /tutorial_1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "net/http" 8 | ) 9 | 10 | func main() { 11 | // command line flags 12 | port := flag.Int("port", 80, "port to serve on") 13 | dir := flag.String("directory", "web/", "directory of web files") 14 | flag.Parse() 15 | 16 | // handle all requests by serving a file of the same name 17 | fs := http.Dir(*dir) 18 | fileHandler := http.FileServer(fs) 19 | http.Handle("/", fileHandler) 20 | 21 | log.Printf("Running on port %d\n", *port) 22 | 23 | addr := fmt.Sprintf("127.0.0.1:%d", *port) 24 | // this call blocks -- the progam runs here forever 25 | err := http.ListenAndServe(addr, nil) 26 | fmt.Println(err.Error()) 27 | } 28 | -------------------------------------------------------------------------------- /tutorial_1/web/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Tutorial 4 | 5 | 6 | 7 | 8 |
9 |

Bootstrap's working

10 |

11 | So is font-awesome. 12 |

13 |
14 |
15 | 16 |

Is Angular working?

17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /tutorial_2/README.md: -------------------------------------------------------------------------------- 1 | Tutorial 1 2 | ========== 3 | 4 | In this first tutorial we get up and running with a basic web server that 5 | serves any files in the local directory. 6 | 7 | How to run 8 | ------------- 9 | 10 | * Install Go 11 | * http://golang.org/doc/install 12 | * Download this project from github and unzip 13 | * https://github.com/jakecoffman/golang-webapp-tutorial/archive/master.zip 14 | * Open a command window and `cd` to the tutorial_1 directory 15 | * Type `go run main.go` to start the server 16 | * Open a web browser and go to http://localhost/ 17 | 18 | If you get a message that says "It works!" then it's working. 19 | -------------------------------------------------------------------------------- /tutorial_2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "github.com/gorilla/websocket" 7 | "log" 8 | "net/http" 9 | ) 10 | 11 | var connections map[*websocket.Conn]bool 12 | 13 | func sendAll(msg []byte) { 14 | for conn := range connections { 15 | if err := conn.WriteMessage(websocket.TextMessage, msg); err != nil { 16 | delete(connections, conn) 17 | conn.Close() 18 | } 19 | } 20 | } 21 | 22 | func wsHandler(w http.ResponseWriter, r *http.Request) { 23 | // Taken from gorilla's website 24 | conn, err := websocket.Upgrade(w, r, nil, 1024, 1024) 25 | if _, ok := err.(websocket.HandshakeError); ok { 26 | http.Error(w, "Not a websocket handshake", 400) 27 | return 28 | } else if err != nil { 29 | log.Println(err) 30 | return 31 | } 32 | log.Println("Succesfully upgraded connection") 33 | connections[conn] = true 34 | 35 | for { 36 | // Blocks until a message is read 37 | _, msg, err := conn.ReadMessage() 38 | if err != nil { 39 | delete(connections, conn) 40 | conn.Close() 41 | return 42 | } 43 | log.Println(string(msg)) 44 | sendAll(msg) 45 | } 46 | } 47 | 48 | func main() { 49 | // command line flags 50 | port := flag.Int("port", 80, "port to serve on") 51 | dir := flag.String("directory", "web/", "directory of web files") 52 | flag.Parse() 53 | 54 | connections = make(map[*websocket.Conn]bool) 55 | 56 | // handle all requests by serving a file of the same name 57 | fs := http.Dir(*dir) 58 | fileHandler := http.FileServer(fs) 59 | http.Handle("/", fileHandler) 60 | http.HandleFunc("/ws", wsHandler) 61 | 62 | log.Printf("Running on port %d\n", *port) 63 | 64 | addr := fmt.Sprintf("127.0.0.1:%d", *port) 65 | // this call blocks -- the progam runs here forever 66 | err := http.ListenAndServe(addr, nil) 67 | fmt.Println(err.Error()) 68 | } 69 | -------------------------------------------------------------------------------- /tutorial_2/web/chat.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Tutorial 4 | 5 | 6 | 12 | 13 | 14 |
15 |

My Chat

16 |
17 |
18 | 21 |
22 | 23 | 24 | 25 | 26 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /tutorial_2/web/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Tutorial 4 | 5 | 6 | 7 | 8 |
9 |

Bootstrap's working

10 |

11 | So is font-awesome. 12 |

13 |
14 |
15 | 16 |

Is Angular working?

17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /tutorial_3/README.md: -------------------------------------------------------------------------------- 1 | Tutorial 3 2 | ========== 3 | 4 | -------------------------------------------------------------------------------- /tutorial_3/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "net/http" 10 | "strconv" 11 | 12 | "github.com/gorilla/mux" 13 | ) 14 | 15 | // error response contains everything we need to use http.Error 16 | type handlerError struct { 17 | Error error 18 | Message string 19 | Code int 20 | } 21 | 22 | // book model 23 | type book struct { 24 | Title string `json:"title"` 25 | Author string `json:"author"` 26 | Id int `json:"id"` 27 | } 28 | 29 | // list of all of the books 30 | var books = make([]book, 0) 31 | 32 | // a custom type that we can use for handling errors and formatting responses 33 | type handler func(w http.ResponseWriter, r *http.Request) (interface{}, *handlerError) 34 | 35 | // attach the standard ServeHTTP method to our handler so the http library can call it 36 | func (fn handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 37 | // here we could do some prep work before calling the handler if we wanted to 38 | 39 | // call the actual handler 40 | response, err := fn(w, r) 41 | 42 | // check for errors 43 | if err != nil { 44 | log.Printf("ERROR: %v\n", err.Error) 45 | http.Error(w, fmt.Sprintf(`{"error":"%s"}`, err.Message), err.Code) 46 | return 47 | } 48 | if response == nil { 49 | log.Printf("ERROR: response from method is nil\n") 50 | http.Error(w, "Internal server error. Check the logs.", http.StatusInternalServerError) 51 | return 52 | } 53 | 54 | // turn the response into JSON 55 | bytes, e := json.Marshal(response) 56 | if e != nil { 57 | http.Error(w, "Error marshalling JSON", http.StatusInternalServerError) 58 | return 59 | } 60 | 61 | // send the response and log 62 | w.Header().Set("Content-Type", "application/json") 63 | w.Write(bytes) 64 | log.Printf("%s %s %s %d", r.RemoteAddr, r.Method, r.URL, 200) 65 | } 66 | 67 | func listBooks(w http.ResponseWriter, r *http.Request) (interface{}, *handlerError) { 68 | return books, nil 69 | } 70 | 71 | func getBook(w http.ResponseWriter, r *http.Request) (interface{}, *handlerError) { 72 | // mux.Vars grabs variables from the path 73 | param := mux.Vars(r)["id"] 74 | id, e := strconv.Atoi(param) 75 | if e != nil { 76 | return nil, &handlerError{e, "Id should be an integer", http.StatusBadRequest} 77 | } 78 | b, index := getBookById(id) 79 | 80 | if index < 0 { 81 | return nil, &handlerError{nil, "Could not find book " + param, http.StatusNotFound} 82 | } 83 | 84 | return b, nil 85 | } 86 | 87 | func parseBookRequest(r *http.Request) (book, *handlerError) { 88 | // the book payload is in the request body 89 | data, e := ioutil.ReadAll(r.Body) 90 | if e != nil { 91 | return book{}, &handlerError{e, "Could not read request", http.StatusBadRequest} 92 | } 93 | 94 | // turn the request body (JSON) into a book object 95 | var payload book 96 | e = json.Unmarshal(data, &payload) 97 | if e != nil { 98 | return book{}, &handlerError{e, "Could not parse JSON", http.StatusBadRequest} 99 | } 100 | 101 | return payload, nil 102 | } 103 | 104 | func addBook(w http.ResponseWriter, r *http.Request) (interface{}, *handlerError) { 105 | payload, e := parseBookRequest(r) 106 | if e != nil { 107 | return nil, e 108 | } 109 | 110 | // it's our job to assign IDs, ignore what (if anything) the client sent 111 | payload.Id = getNextId() 112 | books = append(books, payload) 113 | 114 | // we return the book we just made so the client can see the ID if they want 115 | return payload, nil 116 | } 117 | 118 | func updateBook(w http.ResponseWriter, r *http.Request) (interface{}, *handlerError) { 119 | payload, e := parseBookRequest(r) 120 | if e != nil { 121 | return nil, e 122 | } 123 | 124 | _, index := getBookById(payload.Id) 125 | books[index] = payload 126 | return make(map[string]string), nil 127 | } 128 | 129 | func removeBook(w http.ResponseWriter, r *http.Request) (interface{}, *handlerError) { 130 | param := mux.Vars(r)["id"] 131 | id, e := strconv.Atoi(param) 132 | if e != nil { 133 | return nil, &handlerError{e, "Id should be an integer", http.StatusBadRequest} 134 | } 135 | // this is jsut to check to see if the book exists 136 | _, index := getBookById(id) 137 | 138 | if index < 0 { 139 | return nil, &handlerError{nil, "Could not find entry " + param, http.StatusNotFound} 140 | } 141 | 142 | // remove a book from the list 143 | books = append(books[:index], books[index+1:]...) 144 | return make(map[string]string), nil 145 | } 146 | 147 | // searches the books for the book with `id` and returns the book and it's index, or -1 for 404 148 | func getBookById(id int) (book, int) { 149 | for i, b := range books { 150 | if b.Id == id { 151 | return b, i 152 | } 153 | } 154 | return book{}, -1 155 | } 156 | 157 | var id = 0 158 | 159 | // increments id and returns the value 160 | func getNextId() int { 161 | id += 1 162 | return id 163 | } 164 | 165 | func main() { 166 | // command line flags 167 | port := flag.Int("port", 80, "port to serve on") 168 | dir := flag.String("directory", "web/", "directory of web files") 169 | flag.Parse() 170 | 171 | // handle all requests by serving a file of the same name 172 | fs := http.Dir(*dir) 173 | fileHandler := http.FileServer(fs) 174 | 175 | // setup routes 176 | router := mux.NewRouter() 177 | router.Handle("/", http.RedirectHandler("/static/", 302)) 178 | router.Handle("/books", handler(listBooks)).Methods("GET") 179 | router.Handle("/books", handler(addBook)).Methods("POST") 180 | router.Handle("/books/{id}", handler(getBook)).Methods("GET") 181 | router.Handle("/books/{id}", handler(updateBook)).Methods("POST") 182 | router.Handle("/books/{id}", handler(removeBook)).Methods("DELETE") 183 | router.PathPrefix("/static/").Handler(http.StripPrefix("/static", fileHandler)) 184 | http.Handle("/", router) 185 | 186 | // bootstrap some data 187 | books = append(books, book{"Ender's Game", "Orson Scott Card", getNextId()}) 188 | books = append(books, book{"Code Complete", "Steve McConnell", getNextId()}) 189 | books = append(books, book{"World War Z", "Max Brooks", getNextId()}) 190 | books = append(books, book{"Pragmatic Programmer", "David Thomas", getNextId()}) 191 | 192 | log.Printf("Running on port %d\n", *port) 193 | 194 | addr := fmt.Sprintf("127.0.0.1:%d", *port) 195 | // this call blocks -- the progam runs here forever 196 | err := http.ListenAndServe(addr, nil) 197 | fmt.Println(err.Error()) 198 | } 199 | -------------------------------------------------------------------------------- /tutorial_3/web/books.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | My Bookshelf 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 17 |

My Bookshelf

18 |
19 |
20 |
21 |
22 |

Books

23 | 28 |
29 |
30 |

{{selected.title}}

31 |
32 |
Author:
33 |
{{selected.author}}
34 |
35 |
36 |
37 | 40 | 43 |
44 |
45 |
46 | 47 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /tutorial_3/web/books_design.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | My Bookshelf 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 17 |

My Bookshelf

18 |
19 |
20 |
21 |
22 |

Books

23 | 27 |
28 |
29 |

Title

30 |
31 |
Author:
32 |
Me, perhaps
33 |
34 |
35 |
36 | 39 | 42 |
43 |
44 |
45 | 46 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /tutorial_3/web/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Tutorial 4 | 5 | 6 | 7 | 8 |
9 |

Bootstrap's working

10 |

11 | So is font-awesome. 12 |

13 |
14 |
15 | 16 |

Is Angular working?

17 |
18 | 19 | 20 | 21 | --------------------------------------------------------------------------------