In the case of a REST API, you'll typically deal with at least three different implementation layers:

  • HTTP handler
  • some sort of business logic/use case
  • persistent storage/database interface
main

The article Applying The Clean Architecture to Go applications illustrates very well how the various parts can be separated. How strictly you should follow this approach depends a little on the complexity of your project.

Below is a very basic breakdown, separating the handler from logic and database layer.

HTTP handler

The handler does nothing else than mapping the request values into local variables or possibly custom data structures if needed. In addition to that it just runs the use case logic and maps the result before writing it to the response. This is also a good place to map different errors to different response objects.

type Interactor interface {
    Bar(foo string) ([]usecases.Bar, error)
}

type MyHandler struct {
    Interactor Interactor
}

func (handler MyHandler) Bar(w http.ResponseWriter, r *http.Request) {
    foo := r.FormValue("foo")
    res, _ := handler.Interactor.Bar(foo)

    // you may want to map/cast res to a different type that is encoded
    // according to your spec
    json.NewEncoder(w).Encode(res)
}

Unit tests are a great way to test that the HTTP response contains the correct data for different results and errors.

Use case / business logic

DataRepository
type DataRepository interface {
    Find(f string) (Bar, error)
}

type Bar struct {
    Identifier string
    FooBar     int
}

type Interactor struct {
    DataRepository DataRepository
}

func (interactor *Interactor) Bar(f string) (Bar, error) {
    b := interactor.DataRepository.Find(f)

    // ... custom logic

    return b
}   

Database interface

DataRepository
type Repo {
    db sql.DB
}

func NewDatabaseRepo(db sql.DB) *Repo {
    // config if necessary...

    return &Repo{db: db}
}

func (r Repo)Find(f string) (usecases.Bar, error) {
    rows, err := db.Query("SELECT id, foo_bar FROM bar WHERE foo=?", f)
    if err != nil {
        log.Fatal(err)
    }
    defer rows.Close()

    for rows.Next() {
        var id string, fooBar int
        if err := rows.Scan(&id, &fooBar); err != nil {
            log.Fatal(err)
        }
        // map row value to desired structure
        return usecases.Bar{Identifier: id, FooBar: fooBar}
    }

    return errors.New("not found")
}

Again, this allows testing the database operations separately without the need of any mock SQL statements.

Note: The code above is very much pseudo code and incomplete.