Beginner: Starting a Go server
In this tutorial, we’ll walk through multiple ways to create
and run a basic Go web server using the
net/http
package. These approaches will form the foundation for
serving HTML templates, APIs, and eventually integrating
with Tailwind CSS and htmx in the GOTTH stack.
1. Minimal Server
Here’s the simplest way to serve a single “Hello, world!” response on port 8080:
// minimal.go package main import ( "fmt" "net/http" ) func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Hello, world!") }) http.ListenAndServe(":8080", nil) }
Simply run
go run minimal.go
, then navigate to
http://localhost:8080
.
2. Using a Named Handler Function
If you’d like more clarity, you can define a separate function for handling requests and attach that to a route:
// namedhandler.go
package main
import (
"fmt"
"net/http"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello from a named handler!")
}
func main() {
http.HandleFunc("/", helloHandler)
http.ListenAndServe(":8080", nil)
}
This approach is more flexible since you can define multiple handler functions for different endpoints or for more sophisticated request handling.
3. Multiple Routes with the Default Mux
Go’s default multiplexer (often just called the “default
mux”) can handle multiple routes by calling
http.HandleFunc
more than once:
// multipleroutes.go
package main
import (
"fmt"
"net/http"
)
func homeHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Welcome to the Home Page!")
}
func aboutHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Learn about us here.")
}
func main() {
http.HandleFunc("/", homeHandler)
http.HandleFunc("/about", aboutHandler)
http.ListenAndServe(":8080", nil)
}
Now you can visit
http://localhost:8080/
and
http://localhost:8080/about
to see different responses.
4. Using a Custom Mux
If you prefer not to rely on Go’s default mux, you can
create your own using
http.NewServeMux
. This is helpful for organizing routes in larger
applications:
// custommux.go
package main
import (
"fmt"
"net/http"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "This is the root path, served by a custom mux.")
})
mux.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello from a custom mux route!")
})
http.ListenAndServe(":8080", mux)
}
Either approach (default mux vs. custom mux) works fine. It’s largely a matter of personal preference or project organization.