GoとPostgreSQLで簡単な連絡先リストを作成する


このポストでは、PostgreSQLデータベースから取得したコンタクトを使用して、連絡先リストを含む簡単なWebページを構築します.GSONでデータベースに接続し、PostgreSQLのJSONカラムのサポートを使用します.結果は次のようになります.

このポストに従うことによって、あなたはsqlx and pgx パッケージ、テンプレートを使用して動的にデータをレンダリングし、結果として得られるページをHTTPサーバーに提供します.

要件


始める前に:
  • インストールを行ってください.参照this post 指示のために.
  • あなたの場所を知っていることを確認します$GOPATH そうです.普通は~/go 設定しない限り.
  • HTTPサーバーの取得


    あなたの中の新しい空のディレクトリで$GOPATH , ファイル名main.go . ディレクトリに何か好きな名前を付けることができますgo-contacts . goの組み込みを使用してHTTPサーバの設定を開始しますnet/http パッケージ.
    package main
    
    import (
        "flag"
        "log"
        "net/http"
    "os"
    )
    
    var (
        listenAddr = flag.String("addr", getenvWithDefault("LISTENADDR", ":8080"), "HTTP address to listen on")
    )
    
    func getenvWithDefault(name, defaultValue string) string {
            val := os.Getenv(name)
            if val == "" {
                    val = defaultValue
            }
    
            return val
    }
    
    func main() {
        flag.Parse()
    
        log.Printf("listening on %s\n", *listenAddr)
        http.ListenAndServe(*listenAddr, nil)
    }
    
    サーバはホストとポートを聞きたいので、CLIフラグで指定しますaddr . また、環境変数の設定で渡すオプションを提供したいので、フラグのデフォルト値はLISTENADDR 環境変数.CLIフラグが渡されない場合、環境変数の値が使用されます.どちらも設定されていない場合は、私たちに戻る:8080 .
    ファイルを保存して実行すると、http://localhost:8080 .
    go run main.go
    
    とホールドオンを参照してください、“404ページが見つかりません”エラーですか?

    いいよそれは、我々がまだどんなルートまたはページも構成していないので、サーバーが要求に応じる方法を知らないので.なぜ、我々は前進して、現在それをしませんか.

    連絡先リストページ


    連絡先リストページを作りましょう./ . 私たちはtemplate/html パッケージは、後で動的にデータ(連絡先)を渡すことができます.
    ディレクトリを作成するtemplates 並んでmain.go そしてその中にindex.html 次のコンテンツを使用します.
    <!doctype html>
    <html>
        <head>
            <meta charset="utf-8">
            <meta name="viewport" content="width=device-width, initial-scale=1">
    
            <title>Contacts</title>
            <link rel="stylesheet" href="https://unpkg.com/[email protected]/css/tachyons.min.css"/>
        </head>
        <body>
            <div class="mw6 center pa3 sans-serif">
                <h1 class="mb4">Contacts</h1>
            </div>
        </body>
    </html>
    
    これは、私たちの連絡先リストのベースとなる基本的なスタイリングを持つページです.
    今、インデックスを読む必要があります.HTMLテンプレート.輸入html/template そして、グローバル変数を追加し、listenAddr トップで:
    import (
        "flag"
        "log"
        "html/template"
        "net/http"
    )
    
    var (
            listenAddr       = flag.String("addr", getenvWithDefault("LISTENADDR", ":8080"), "HTTP address to listen on")
            tmpl             = template.New("")
    )
    
    内部main() , アフターflag.Parse() 行を追加します.すべてのオペレーティングシステムとの互換性のためにpath/filepath パッケージファイルへのパスを構築するためのパッケージです.
    var err error
    
    _, err = tmpl.ParseGlob(filepath.Join(".", "templates", "*.html"))
    if err != nil {
        log.Fatalf("Unable to parse templates: %v\n", err)
    }
    
    これは、テンプレートディレクトリ内のすべてのHTMLファイルを読み込み、レンダリングの準備をします.これで、テンプレートを設定します/ . ページのサーブするために、ファイルの一番下に新しい関数を追加します.
    func handler(w http.ResponseWriter, r *http.Request) {
        tmpl.ExecuteTemplate(w, "index.html", nil)
    }
    
    最後に、このハンドラー関数を使用するサーバーを構成します.上記のlog.Printf() ラインインmain() , 追加
    http.HandleFunc("/", handler)
    
    さあ準備ができましたファイル全体は次のようになります.
    package main
    
    import (
        "flag"
        "log"
        "html/template"
        "net/http"
        "os"
        "path/filepath"
    )
    
    var (
        listenAddr = flag.String("addr", getenvWithDefault("LISTENADDR", ":8080"), "HTTP address to listen on")
        tmpl       = template.New("")
    )
    
    func getenvWithDefault(name, defaultValue string) string {
            val := os.Getenv(name)
            if val == "" {
                    val = defaultValue
            }
    
            return val
    }
    
    func main() {
        flag.Parse()
    
        var err error
    
        _, err = tmpl.ParseGlob(filepath.Join(".", "templates", "*.html"))
        if err != nil {
            log.Fatalf("Unable to parse templates: %v\n", err)
        }
    
        http.HandleFunc("/", handler)
        log.Printf("listening on %s\n", *listenAddr)
        http.ListenAndServe(*listenAddr, nil)
    }
    
    func handler(w http.ResponseWriter, r *http.Request) {
        tmpl.ExecuteTemplate(w, "index.html", nil)
    }
    
    ランgo run main.go もう一度、あなたは我々が設定したテンプレートを見るべきです.

    データベース内の連絡先


    何かが実際の連絡先ページに欠けている!中に入れましょう.
    私たちはすぐにPostgreSQLクラスタを得るためにDigitaloceanデータベースを使用します.あなたがまだしていないならば、それはほんの数分かかるthe product documentation for Databases . 閉じるこの動画はお気に入りから削除されています.

    クラスタを作成したら、コントロールパネルから接続文字列をコピーします.概要ページの「接続詳細」セクションで、リストから「接続文字列」を選択してコピーします.

    接続文字列には、データベースに接続するために必要なすべての詳細(パスワードを含む)が含まれます.

    データベースの初期化


    私たちの移動アプリは、連絡先を表示する処理されますので、私はあなたのデータベースにインポートすることができます10ランダムに生成された連絡先を含むSQLエクスポートを準備している.あなたはそれを見つけることができますhere .
    MacOSでは、TablePlusを使用してデータベースを操作するのが好きですが、psql CLIコマンドのように
    psql 'your connection string here' < contacts.sql
    

    連絡先の取得


    ので、今私たちはそれにいくつかの連絡先とデータベースがあります🎉 我々のプログラムを接続して、連絡先を取得しましょう.我々は一歩一歩この機能を構築します.
    goのPostgreSQLデータベースに接続する方法がたくさんあります.この場合、JSOBフィールドにアクセスする便利な方法も必要です.私は個人的に github.com/jmoiron/sqlx and github.com/jackc/pgx 最善を尽くす.
    パッケージのインポートによって起動します.
    go get -u -v github.com/jackc/pgx github.com/jmoiron/sqlx
    
    そして、main.go :
    import (
        ...
    
        _ "github.com/jackc/pgx/stdlib"
        "github.com/jmoiron/sqlx"
        "github.com/jmoiron/sqlx/types"
    )
    
    今、我々がする必要があるいくつかのことがあります.データベースのテーブル構造に基づいてコンタクトタイプを定義し、PostgreSQLデータベースに接続する必要があります.連絡先ページを提供するときは、連絡先のデータベースを検索し、レンダリングのためのテンプレートに渡す.

    連絡先


    これらの型をmain.go . それらの構造にマッチするthe contacts database export とJSONBフィールドのサポートを準備するfavorites :
    // ContactFavorites is a field that contains a contact's favorites
    type ContactFavorites struct {
        Colors []string `json:"colors"`
    }
    
    // Contact represents a Contact model in the database 
    type Contact struct {
        ID                   int
        Name, Address, Phone string
    
        FavoritesJSON types.JSONText    `db:"favorites"`
        Favorites     *ContactFavorites `db:"-"`
    
        CreatedAt string `db:"created_at"`
        UpdatedAt string `db:"updated_at"`
    }
    

    データベース接続


    我々はまだデータベースに接続していないことに注意してください👀 それをしましょう.PostgreSQL接続文字列をCLIフラグとして渡し、グローバルデータベース変数を追加します.だから再びmain.go :
    var (
        connectionString = flag.String("conn", getenvWithDefault("DATABASE_URL", ""), "PostgreSQL connection string")
        listenAddr       = flag.String("addr", ":8080", "HTTP address to listen on")
        db               *sqlx.DB
        tmpl             = template.New("")
    )
    
    関数を使用することに注意してくださいgetenvWithDefault listen変数と同様に、環境変数を使って接続文字列を渡すことができます(DATABASE_URL ) CLIフラグに加えて-conn ).
    テンプレートロジックの後にmain() (右上の)http.HandleFunc() ), 次を追加します.
    if *connectionString == "" {
        log.Fatalln("Please pass the connection string using the -conn option")
    }
    
    db, err = sqlx.Connect("pgx", *connectionString)
    if err != nil {
        log.Fatalf("Unable to establish connection: %v\n", err)
    }
    
    現在、PostgreSQLデータベースに接続しています.

    連絡先データベースのクエリ


    データベースからすべての連絡先を取得するには、ファイルの下部に新しい関数を追加します.より明確なエラーについては、別のパッケージを使用します.github.com/pkg/errors . ダウンロードして、それの上部にインポートmain.go いつものように.
    go get -u -v github.com/pkg/errors
    
    import (
        ...
        "github.com/pkg/errors"
        ...
    )
    
    
    
    func fetchContacts() ([]*Contact, error) {
        contacts := []*Contact{}
        err := db.Select(&contacts, "select * from contacts")
        if err != nil {
            return nil, errors.Wrap(err, "Unable to fetch contacts")
        }
    
        return contacts, nil
    }
    
    今不足している1つの事は、お気に入りの列です.コンタクトタイプを見ると、このフィールドを定義しました.FavoritesJSON types.JSONText db:"favorites" . このマップはfavorites データベースのカラムFavoritesJSON Context構造体のフィールドで、テキストとしてシリアル化されたJSONオブジェクトとして利用できます.
    これは、JSONオブジェクトを実際の囲碁構造に手動で解析して、unmarshalする必要があることを意味します.私たちはgo ' sを使いますencoding/json パッケージの先頭にインポートしてくださいmain.go . 追加fetchContacts() :
    import (
        ...
        "encoding/json"
        ...
    )
    ...
    func fetchContacts() ([]*Contact, error) {
        ...
    
        for _, contact := range contacts {
            err := json.Unmarshal(contact.FavoritesJSON, &contact.Favorites)
    
            if err != nil {
                return nil, errors.Wrap(err, "Unable to parse JSON favorites")
            }
        }
    
        return contacts, nil
    }
    
    結果の構造体はFavorites コンタクト構造体のフィールド.

    連絡先のレンダリング


    クール、データがあります.それを使いましょう!インサイドhandler() 機能、使用しますfetchContacts() コンタクトを取得し、テンプレートに渡す
    func handler(w http.ResponseWriter, r *http.Request) {
        contacts, err := fetchContacts()
        if err != nil {
            w.WriteHeader(http.StatusInternalServerError)
            w.Write([]byte(err.Error()))
            return
        }
    
        tmpl.ExecuteTemplate(w, "index.html", struct{ Contacts []*Contact }{contacts})
    }
    
    これは、連絡先を取得し、失敗時にエラーを表示し、テンプレートに渡すことを試みます.エラーが発生した場合、応答として完全なエラーが送信されます.生産環境では、エラーを記録し、代わりに一般的なエラーメッセージを送信します.
    今、私たちは、私たちがそれを通過している接触で何かをするためにテンプレートを修正する必要があります.コンマ区切りのリストとして好きな色を表示するにはstrings.Join 関数.テンプレート内で使用する前に、テンプレート関数として定義する必要がありますmain() 上記のtmpl.ParseGlob ライン.インポートすることを忘れないでくださいstrings 一番上のパッケージ
    import (
        ...
        "strings"
        ...
    )
    
    ...
    
    tmpl.Funcs(template.FuncMap{"StringsJoin": strings.Join})
    _, err = tmpl.ParseGlob(filepath.Join(".", "templates", "*.html"))
    
    ...
    
    その後、<h1> HTMLテンプレートで行を追加します.
    {{range .Contacts}}
    <div class="pa2 mb3 striped--near-white">
        <header class="b mb2">{{.Name}}</header>
        <div class="pl2">
            <p class="mb2">{{.Phone }}</p>
            <p class="pre mb3">{{.Address}}</p>
            <p class="mb2"><span class="fw5">Favorite colors:</span> {{StringsJoin .Favorites.Colors ", "}}</p>
        </div>
    </div>
    {{end}}
    
    以上です.ファイナルmain.go ファイルは以下のようになります.
    package main
    
    import (
        "encoding/json"
        "flag"
        "log"
        "html/template"
        "net/http"
            "path/filepath"
        "strings"
    
        _ "github.com/jackc/pgx/stdlib"
        "github.com/jmoiron/sqlx"
        "github.com/jmoiron/sqlx/types"
        "github.com/pkg/errors"
    )
    
    // ContactFavorites is a field that contains a contact's favorites
    type ContactFavorites struct {
        Colors []string `json:"colors"`
    }
    
    // Contact represents a Contact model in the database    
    type Contact struct {
        ID                   int
        Name, Address, Phone string
    
        FavoritesJSON types.JSONText    `db:"favorites"`
        Favorites     *ContactFavorites `db:"-"`
    
        CreatedAt string `db:"created_at"`
        UpdatedAt string `db:"updated_at"`
    }
    
    var (
        connectionString = flag.String("conn", getenvWithDefault("DATABASE_URL", ""), "PostgreSQL connection string")
        listenAddr       = flag.String("addr", getenvWithDefault("LISTENADDR", ":8080"), "HTTP address to listen on")
        db               *sqlx.DB
        tmpl             = template.New("")
    )
    
    func getenvWithDefault(name, defaultValue string) string {
            val := os.Getenv(name)
            if val == "" {
                    val = defaultValue
            }
    
            return val
    }
    
    func main() {
        flag.Parse()
        var err error
    
        // templating
    
        tmpl.Funcs(template.FuncMap{"StringsJoin": strings.Join})
        _, err = tmpl.ParseGlob(filepath.Join(".", "templates", "*.html"))
        if err != nil {
            log.Fatalf("Unable to parse templates: %v\n", err)
        }
    
        // postgres connection
    
        if *connectionString == "" {
            log.Fatalln("Please pass the connection string using the -conn option")
        }
    
        db, err = sqlx.Connect("pgx", *connectionString)
        if err != nil {
            log.Fatalf("Unable to establish connection: %v\n", err)
        }
    
        // http server
    
        http.HandleFunc("/", handler)
    
        log.Printf("listening on %s\n", *listenAddr)
        http.ListenAndServe(*listenAddr, nil)
    }
    
    func fetchContacts() ([]*Contact, error) {
        contacts := []*Contact{}
        err := db.Select(&contacts, "select * from contacts")
        if err != nil {
            return nil, errors.Wrap(err, "Unable to fetch contacts")
        }
    
        for _, contact := range contacts {
            err := json.Unmarshal(contact.FavoritesJSON, &contact.Favorites)
    
            if err != nil {
                return nil, errors.Wrap(err, "Unable to parse JSON favorites")
            }
        }
    
        return contacts, nil
    }
    
    func handler(w http.ResponseWriter, r *http.Request) {
        contacts, err := fetchContacts()
        if err != nil {
            w.WriteHeader(http.StatusInternalServerError)
            w.Write([]byte(err.Error()))
            return
        }
    
        tmpl.ExecuteTemplate(w, "index.html", struct{ Contacts []*Contact }{contacts})
    }
    
    再度プログラムを実行し、データベースの接続文字列を渡すと、連絡先リストが表示されます.
    go run main.go -conn "connection string here"
    # alternatively:
    DATABASE_URL="connection string here" go run main.go
    

    結論


    このポストをたどった後に、あなたはどのようにステップバイステップで簡単な連絡先リストを構築するかを学びました.そして、空のページがHTTPウェブサーバによってサーブして、PostgreSQLデータベースから取り出された連絡先のリストを貸し出すもので終わります.道に沿って、あなたは慣れ親しんだようになりますhtml/template Webページを動的なデータでレンダリングするには、PostgreSQLデータベースに接続し、データベースに格納されているJSONBオブジェクトと相互作用します.
    あなたは、Githubレポで完全なソースコードを見つけることができますdigitalocean/databases .

    次の手順


    ここでは、このポストのあとに実行することができます.
  • 別の項目である各色で箇条書きのリストとしてお気に入りの色を印刷します.用途html/template '内蔵range ループの好きな色のスライスの機能.
  • つまたは複数の連絡先に好きな形状(四角形、円など)を追加し、それを表示するテンプレートを編集します.The Contact structは変更されないままであるべきです.
  • リストの順序で連絡先は、最後に更新された最新の最初の.