Skip to content

Instantly share code, notes, and snippets.

@tsenart
Last active February 25, 2026 11:21
Show Gist options
  • Select an option

  • Save tsenart/5fc18c659814c078378d to your computer and use it in GitHub Desktop.

Select an option

Save tsenart/5fc18c659814c078378d to your computer and use it in GitHub Desktop.
HTTP Closures
package main
import (
"net/http"
"database/sql"
"fmt"
"log"
"os"
)
func helloHandler(db *sql.DB) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var name string
// Execute the query.
row := db.QueryRow("SELECT myname FROM mytable")
if err := row.Scan(&name); err != nil {
http.Error(w, err.Error(), 500)
return
}
// Write it back to the client.
fmt.Fprintf(w, "hi %s!\n", name)
})
}
func withMetrics(l *log.Logger, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
began := time.Now()
next.ServeHTTP(w, r)
l.Printf("%s %s took %s", r.Method, r.URL, time.Since(began))
})
}
func main() {
// Open our database connection.
db, err := sql.Open("postgres", "…")
if err != nil {
log.Fatal(err)
}
// Create our logger
logger := log.New(os.Stdout, "", 0)
// Register our handler.
http.Handle("/hello", helloHandler(db))
// Register our handler with metrics logging
http.Handle("/hello_again", withMetrics(logger, helloHandler(db)))
http.ListenAndServe(":8080", nil)
}
@alexedwards

Copy link
Copy Markdown

I really like this approach.

Just wondering - Is there any advantage/disadvantage of doing it the above way instead of creating a custom DB type and defining handlers as methods against it? Like so...

type MyDB struct {
  *sql.DB
}

func(db *MyDB) handlerOne(next http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    // Do something databasey
  })
}

func(db *MyDB) handlerTwo() http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    // Do something else databasey
  })
}

func main() {
  db, err := sql.Open("postgres", "…")
  // Handle
  mydb := &MyDB{db}
  http.Handle("/", myDB.handlerOne(myDB.handlerTwo()))
  // Etc.
}

It seems to me that the nice thing with this is that the middleware handler (handlerOne) retains the form func (http.Handler) http.Handler meaning it can be used nicely for chaining in the Alice style.

@crossle

crossle commented Aug 31, 2017

Copy link
Copy Markdown

Why not store db connect to context?

@Jeiwan

Jeiwan commented Dec 25, 2017

Copy link
Copy Markdown

@crossle DB connections are usually opened once, when the app starts, and reused later. Contexts are for things that are started/initialized/created per request or per operation.

@gerep

gerep commented Jan 4, 2018

Copy link
Copy Markdown

@crossle Use context Values only for request-scoped data that transits processes and APIs, not for passing optional parameters to functions. - https://godoc.org/golang.org/x/net/context

@mikeschinkel

Copy link
Copy Markdown

This was super helpful to me as I have been learning GoLang. Thanks for posting!

@ldelossa

Copy link
Copy Markdown

+1 this is good.

@smurariu

smurariu commented May 8, 2018

Copy link
Copy Markdown

@alexedwards I don't think there's an advantage or disadvantage but it does somewhat limit the explosion of types/interfaces. And I'm guessing that in some scenarios you'll want to use your approach in others this one so it's really useful to be aware of both.

@montera82

Copy link
Copy Markdown

and how do you write a test against the handler hellohandler using this approach?

@admercs

admercs commented May 23, 2018

Copy link
Copy Markdown

The main issue with the @tsenart method is if both your handlers and handles live in a separate package from your main database instance. This still requires creating a struct or global variable in order to pass the database to these functions from main. Hence, I don't consider it a very realistic solution for large projects. In terms of package layout and compartmentalization, the method used here looks very nice: https://hackernoon.com/make-yourself-a-go-web-server-with-mongodb-go-on-go-on-go-on-48f394f24e

@Carlislegroup

Copy link
Copy Markdown

Diving into best practices to separate DB from code for scalability/flexibility, and came here from ben johnson's post (https://medium.com/@benbjohnson/structuring-applications-in-go-3b04be4ff091). Thank you for putting this together. Is there any blog posts on how to write tests against this approach? Thanks!

@ace3

ace3 commented Jul 1, 2018

Copy link
Copy Markdown

+1 very helpful

@blinderjay

Copy link
Copy Markdown

smarter way to organize golang code

@0x1F346

0x1F346 commented Oct 14, 2020

Copy link
Copy Markdown

and how do you write a test against the handler hellohandler using this approach?

Execute helloHandler and put the returned HandlerFunc into a var

@zhouzilong2020

zhouzilong2020 commented Mar 10, 2022

Copy link
Copy Markdown

and how do you write a test against the handler hellohandler using this approach?

Execute helloHandler and put the returned HandlerFunc into a var

I guess writing unit test this way is again unrealistic, just imagine if there are thousands of handlers to be tested.

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