Go 및 PostgreSQL을 사용하여 간단한 연락처 목록 만들기

46060 단어 postgresgosqlwebdev
본고에서, 우리는 연락처 목록을 포함하는 간단한 웹 페이지를 구축할 것이다. 그 중에서 연락처는PostgreSQL 데이터베이스에서 온 것이다.Go의 데이터베이스에 연결하고 PostgreSQL을 사용하여 JSON 열을 지원합니다.결과는 다음과 같습니다.

본문을 읽으면 sqlxpgx 패키지를 Go의 PostgreSQL 데이터베이스에 연결하여 템플릿을 사용하여 데이터를 동적으로 보여주고 HTTP 서버에서 생성된 페이지를 제공하는 방법을 알 수 있습니다.

요구 사항


우리가 시작하기 전에:
  • 이 설치되어 있는지 확인합니다.자세한 내용은 this post을 참조하십시오.
  • $GOPATH이 어디에 있는지 확인하십시오.설정이 다르지 않으면 일반적으로 ~/go입니다.
  • HTTP 서버 시작

    $GOPATH의 새 빈 디렉터리에 main.go이라는 파일을 만듭니다.너는 디렉터리에 네가 좋아하는 어떤 이름을 지어줄 수 있다. 나는 go-contacts을 사용한다.Go의 내장 net/http 패키지를 사용하여 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)
    }
    
    서버는 하나의 호스트와 하나의 포트로 탐지해야 하기 때문에 addr이라는 CLI 플래그에서 이렇게 요구합니다.또한 환경 변수에 설정을 전달하는 옵션을 제공하기를 원하기 때문에 로고의 기본값은 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)
    }
    
    이것은 templates 디렉터리의 모든 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을 다시 실행하면 저희가 설정한 템플릿을 보실 수 있습니다.

    데이터베이스의 연락처


    페이지에 실제 연락처가 없습니다!그것들을 넣읍시다.
    Digital Ocean 데이터베이스를 사용하여 PostgreSQL 클러스터를 신속하게 구축합니다.만약 당신이 아직 없다면, 새로운 것을 만드는 데 몇 분만 걸립니다. 텍스트 게시물을 좋아한다면, the product documentation for Databases을 참조하십시오.동영상 좋아하면

    그룹을 만든 후 제어판에서 연결 문자열을 복사합니다.개요 페이지의 연결 세부 정보 섹션에서 목록에서 연결 문자열을 선택하고 복사합니다.

    연결 문자열은 데이터베이스에 연결하는 데 필요한 모든 세부 정보 (비밀번호 포함) 를 포함하므로 보안을 유지하십시오.

    데이터베이스 초기화


    Google Go 응용 프로그램은 연락처만 표시할 수 있기 때문에 데이터베이스에 가져올 수 있는 무작위로 생성된 연락처 10개를 포함하는 SQL 내보내기를 준비했습니다.너는 그것을 찾을 수 있다. here.
    macOS에서는 TablePlus를 사용하여 데이터베이스를 처리하는 것을 좋아하지만 원하는 클라이언트를 사용하거나 psql CLI 명령을 사용하여 가져올 수 있습니다.
    psql 'your connection string here' < contacts.sql
    

    연락처 가져오기


    네, 지금 데이터베이스가 하나 생겼어요. 연락처가 좀 있어요.🎉 프로그램을 연결하고 연락처를 받도록 합니다.우리는 점차적으로 이 기능을 구축할 것이다.
    Go에는 PostgreSQL 데이터베이스를 연결하는 여러 가지 방법이 있습니다.이런 상황에서 우리는 JSONB 필드에 접근하는 편리한 방법이 필요하다. 왜냐하면 우리의 연락처 데이터베이스는 그것들을 사용하기 때문이다.나는 개인적으로 github.com/jmoiron/sqlx 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("")
    )
    
    CLI 플래그(getenvWithDefault) 외에 환경 변수(DATABASE_URL)를 사용하여 연결 문자열을 전달할 수 있도록 함수 -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
    }
    
    지금 부족한 것은 수집 모음이다.연락처 유형을 보시면, 이 필드를 정의합니다: FavoritesJSON types.JSONText db:"favorites".이것은 데이터베이스의 favorites 열을 Contact 구조의 FavoritesJSON 필드에 비추어 텍스트로 서열화된 JSON 대상이 되도록 합니다.
    이것은 JSON 객체를 수동으로 해석하여 실제 Go 구조로 그룹화해야 한다는 것을 의미합니다.Go의 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
    }
    
    생성된 구조는 Contact 구조의 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"))
    
    ...
    
    그런 다음 HTML 템플릿의 <h1> 행에 다음을 추가합니다.
    {{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을 사용하여 동적 데이터를 포함하는 웹 페이지를 보여주고PostgreSQL 데이터베이스에 연결하며 데이터베이스에 저장된 JSONB 대상과 상호작용을 하는 데 익숙해질 것입니다.
    GitHub repo digitalocean/databases에서 전체 소스 코드를 찾을 수 있습니다.

    다음 단계


    다음은 본문을 읽고 연습할 수 있는 몇 가지 사항입니다.
  • 가장 좋아하는 색을 글머리 기호 목록으로 인쇄합니다. 모든 색은 하나의 단독 항목입니다.html/template의 내장된 range 함수를 사용하여 가장 좋아하는 색 부분을 순환합니다.
  • 은 자주 사용하는 모양(정사각형, 원형 등)을 하나 이상의 연락처에 추가한 다음 템플릿을 편집하여 표시합니다.Contact 구조는 변하지 않아야 한다.
  • 은 마지막 업데이트, 최근 업데이트 순서에 따라 연락처를 표시합니다.
  • 좋은 웹페이지 즐겨찾기