Cara membuat aplikasi web pertama Anda menggunakan Go

Halo, Habr! Saya mempersembahkan kepada Anda terjemahan artikel "Bagaimana membangun aplikasi web pertama Anda dengan Go" oleh Ayooluwa Isaiah.


Ini adalah panduan untuk aplikasi web Go pertama Anda. Kami akan membuat aplikasi berita yang menggunakan API Berita untuk menerima artikel berita tentang topik tertentu, dan menyebarkannya ke server produksi pada akhirnya.


Anda dapat menemukan kode lengkap yang digunakan untuk tutorial ini di repositori GitHub ini.


Persyaratan


Satu-satunya persyaratan untuk tugas ini adalah Go diinstal pada komputer Anda dan Anda sedikit terbiasa dengan sintaks dan konstruksinya. Versi Go yang saya gunakan untuk membuat aplikasi ini juga yang terbaru pada saat penulisan: 1.12.9 . Untuk melihat versi Go yang diinstal, gunakan perintah go version .


Jika Anda merasa tugas ini terlalu sulit untuk Anda, buka pelajaran bahasa pengantar saya sebelumnya, yang akan membantu Anda memulai.


Jadi mari kita mulai!


Kami mengkloning repositori file mulai pada GitHub dan cd ke direktori yang dibuat. Kami memiliki tiga file utama: Di file main.go kami akan menulis semua kode Go untuk tugas ini. File index.html adalah template yang akan dikirim ke browser, dan untuk aplikasi berada di assets/styles.css .


Buat server web dasar


Mari kita mulai dengan membuat server inti yang mengirimkan teks "Hello World!" Ke browser saat menjalankan permintaan GET ke root server. Ubah file main.go Anda menjadi seperti ini:


 package main import ( "net/http" "os" ) func indexHandler(w http.ResponseWriter, r *http.Request) { w.Write([]byte("<h1>Hello World!</h1>")) } func main() { port := os.Getenv("PORT") if port == "" { port = "3000" } mux := http.NewServeMux() mux.HandleFunc("/", indexHandler) http.ListenAndServe(":"+port, mux) } 

Baris pertama package main - menyatakan bahwa kode dalam file main.go dalam paket utama. Setelah itu, kami mengimpor paket net/http , yang menyediakan implementasi klien HTTP dan server untuk digunakan dalam aplikasi kami. Paket ini adalah bagian dari perpustakaan standar dan disertakan dengan setiap instalasi Go.


Dalam fungsi main , http.NewServeMux() membuat multiplexer permintaan HTTP baru dan menetapkannya ke variabel mux . Pada dasarnya, multiplexer permintaan cocok dengan URL permintaan yang masuk ke daftar jalur terdaftar dan memanggil penangan yang sesuai untuk jalur tersebut setiap kali ditemukan kecocokan.


Selanjutnya, kami mendaftarkan fungsi pengendali pertama kami untuk jalur root / . Fungsi handler ini adalah argumen kedua untuk HandleFunc dan selalu memiliki func (w http.ResponseWriter, r * http.Request) tanda tangan func (w http.ResponseWriter, r * http.Request) .


Jika Anda melihat fungsi indexHandler , Anda akan melihat bahwa itu hanya memiliki tanda tangan seperti itu, yang membuatnya menjadi argumen kedua yang valid untuk HandleFunc . Parameter w adalah struktur yang kami gunakan untuk mengirim respons ke permintaan HTTP. Ini mengimplementasikan metode Write() , yang mengambil irisan byte dan menulis data gabungan sebagai bagian dari respons HTTP.


Di sisi lain, parameter r mewakili permintaan HTTP yang diterima dari klien. Ini adalah cara kami mengakses data yang dikirim oleh browser web di server. Kami belum menggunakannya di sini, tetapi kami pasti akan menggunakannya nanti.


Akhirnya, kita memiliki metode http.ListenAndServe() yang memulai server pada port 3000 jika port tidak diatur oleh lingkungan. Jangan ragu untuk menggunakan port yang berbeda jika 3000 digunakan di komputer Anda.


Kemudian kompilasi dan jalankan kode yang baru saja Anda tulis:


 go run main.go 

Jika Anda membuka http: // localhost: 3000 di browser Anda, Anda akan melihat teks "Hello World!".


Peramban yang berani menampilkan teks Hello World


Pergi Templat


Mari kita lihat dasar-dasar templating di Go. Jika Anda terbiasa dengan template dalam bahasa lain, ini seharusnya cukup mudah untuk dipahami.


Template menyediakan cara mudah untuk menyesuaikan output aplikasi web Anda tergantung pada rute tanpa harus menulis kode yang sama di tempat yang berbeda. Misalnya, kita dapat membuat templat untuk bilah navigasi dan menggunakannya di semua halaman situs tanpa menggandakan kode. Selain itu, kami juga mendapat kesempatan untuk menambahkan beberapa logika dasar ke halaman web kami.


Go menyediakan dua pustaka templat di pustaka standar: text/template dan html/template . Keduanya menyediakan antarmuka yang sama, namun paket html/template digunakan untuk menghasilkan output HTML yang dilindungi terhadap injeksi kode, jadi kami akan menggunakannya di sini.


Impor paket ini ke file main.go Anda dan gunakan sebagai berikut:


 package main import ( "html/template" "net/http" "os" ) var tpl = template.Must(template.ParseFiles("index.html")) func indexHandler(w http.ResponseWriter, r *http.Request) { tpl.Execute(w, nil) } func main() { port := os.Getenv("PORT") if port == "" { port = "3000" } mux := http.NewServeMux() mux.HandleFunc("/", indexHandler) http.ListenAndServe(":"+port, mux) } 

tpl adalah variabel tingkat paket yang menunjukkan definisi templat dari file yang disediakan. Panggilan template.ParseFiles mem-parsing file index.html di root direktori proyek kami dan memeriksa validitasnya.


Kami membungkus panggilan template.ParseFiles di template.Must sehingga kode menyebabkan kepanikan ketika kesalahan terjadi. Alasan kami panik di sini daripada mencoba menangani kesalahan adalah karena tidak masuk akal untuk melanjutkan mengeksekusi kode jika kami memiliki templat yang tidak valid. Ini adalah masalah yang perlu diperbaiki sebelum mencoba me-restart server.


Dalam fungsi indexHandler kami menjalankan template yang dibuat sebelumnya dengan memberikan dua argumen: di mana kami ingin menulis output dan data yang ingin kami sampaikan ke template.


Dalam kasus di atas, kami menulis output ke antarmuka ResponseWriter dan, karena kami tidak memiliki data untuk dikirimkan ke templat kami saat ini, nil dilewatkan sebagai argumen kedua.


Hentikan proses yang berjalan di terminal Anda menggunakan Ctrl-C dan mulai lagi dengan go run main.go , lalu segarkan browser Anda. Anda akan melihat teks "Demo Aplikasi Berita" pada halaman seperti yang ditunjukkan di bawah ini:


Peramban yang berani menampilkan Teks Demo Aplikasi Berita


Tambahkan bilah navigasi ke halaman


Ganti konten <body> dalam file index.html Anda seperti yang ditunjukkan di bawah ini:


 <main> <header> <a class="logo" href="/">News Demo</a> <form action="/search" method="GET"> <input autofocus class="search-input" value="" placeholder="Enter a news topic" type="search" name="q"> </form> <a href="https://github.com/freshman-tech/news" class="button github-button">View on Github</a> </header> </main> 

Kemudian reboot server dan segarkan browser Anda. Anda harus melihat sesuatu yang mirip dengan ini:


Browser menampilkan bilah navigasi tanpa gaya


Bekerja dengan file statis


Harap perhatikan bahwa bilah navigasi yang kami tambahkan di atas tidak memiliki gaya, walaupun faktanya kami telah menentukannya di <head> dokumen kami.


Ini karena jalur / sebenarnya cocok dengan semua jalur yang tidak diproses di tempat lain. Oleh karena itu, jika Anda membuka http: // localhost: 3000 / aset / style.css , Anda masih akan mendapatkan beranda Demo Berita alih-alih file CSS karena rute /assets/style.css belum dideklarasikan secara spesifik.


Tetapi kebutuhan untuk mendeklarasikan penangan eksplisit untuk semua file statis kami tidak realistis dan tidak dapat diukur. Untungnya, kita dapat membuat satu penangan untuk melayani semua sumber daya statis.


Hal pertama yang harus dilakukan adalah membuat instance dari objek server file, melewati direktori di mana semua file statis kita berada:


 fs := http.FileServer(http.Dir("assets")) 

Selanjutnya, kita perlu memberi tahu router kita untuk menggunakan objek server file ini untuk semua jalur yang dimulai dengan /assets/ prefix:


 mux.Handle("/assets/", http.StripPrefix("/assets/", fs)) 

Sekarang semuanya:


 // main.go //   func main() { port := os.Getenv("PORT") if port == "" { port = "3000" } mux := http.NewServeMux() //     fs := http.FileServer(http.Dir("assets")) mux.Handle("/assets/", http.StripPrefix("/assets/", fs)) mux.HandleFunc("/", indexHandler) http.ListenAndServe(":"+port, mux) } 

Nyalakan ulang server dan segarkan peramban. Gaya harus dihidupkan seperti yang ditunjukkan di bawah ini:


Peramban yang berani menampilkan bilah navigasi bergaya



Mari kita buat rute yang menangani permintaan pencarian untuk artikel berita. Kami akan menggunakan API Berita untuk memproses permintaan, jadi Anda harus mendaftar untuk menerima kunci API gratis di sini .


Rute ini mengharapkan dua parameter kueri: q mewakili kueri pengguna, dan page digunakan untuk menggulir hasil. Parameter page ini adalah opsional. Jika tidak termasuk dalam URL, kami hanya berasumsi bahwa nomor halaman hasil diatur ke "1".


Tambahkan handler berikut di bawah indexHandler ke file main.go Anda:


 func searchHandler(w http.ResponseWriter, r *http.Request) { u, err := url.Parse(r.URL.String()) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("Internal server error")) return } params := u.Query() searchKey := params.Get("q") page := params.Get("page") if page == "" { page = "1" } fmt.Println("Search Query is: ", searchKey) fmt.Println("Results page is: ", page) } 

Kode di atas mengekstrak parameter q dan page dari URL permintaan dan menampilkan keduanya di terminal.


Kemudian daftarkan fungsi searchHandler sebagai penangan path /search , seperti yang ditunjukkan di bawah ini:


 func main() { port := os.Getenv("PORT") if port == "" { port = "3000" } mux := http.NewServeMux() fs := http.FileServer(http.Dir("assets")) mux.Handle("/assets/", http.StripPrefix("/assets/", fs)) // Add the next line mux.HandleFunc("/search", searchHandler) mux.HandleFunc("/", indexHandler) http.ListenAndServe(":"+port, mux) } 

Ingatlah untuk mengimpor paket fmt dan net/url dari atas:


 import ( "fmt" "html/template" "net/http" "net/url" "os" ) 

Sekarang restart server, masukkan permintaan di bidang pencarian dan periksa terminal. Anda harus melihat permintaan Anda di terminal, seperti yang ditunjukkan di bawah ini:




Buat model data


Saat kami mengajukan permintaan ke News API/everything titik akhir News API/everything , kami mengharapkan respons json dalam format berikut:


 { "status": "ok", "totalResults": 4661, "articles": [ { "source": { "id": null, "name": "Gizmodo.com" }, "author": "Jennings Brown", "title": "World's Dumbest Bitcoin Scammer Tries to Scam Bitcoin Educator, Gets Scammed in The Process", "description": "Ben Perrin is a Canadian cryptocurrency enthusiast and educator who hosts a bitcoin show on YouTube. This is immediately apparent after a quick a look at all his social media. Ten seconds of viewing on of his videos will show that he is knowledgeable about di…", "url": "https://gizmodo.com/worlds-dumbest-bitcoin-scammer-tries-to-scam-bitcoin-ed-1837032058", "urlToImage": "https://i.kinja-img.com/gawker-media/image/upload/s--uLIW_Oxp--/c_fill,fl_progressive,g_center,h_900,q_80,w_1600/s4us4gembzxlsjrkmnbi.png", "publishedAt": "2019-08-07T16:30:00Z", "content": "Ben Perrin is a Canadian cryptocurrency enthusiast and educator who hosts a bitcoin show on YouTube. This is immediately apparent after a quick a look at all his social media. Ten seconds of viewing on of his videos will show that he is knowledgeable about..." } ] } 

Untuk bekerja dengan data ini di Go, kita perlu membuat struktur yang mencerminkan data saat mendekode badan respons. Tentu saja, Anda dapat melakukannya secara manual, tetapi saya lebih suka menggunakan situs web JSON-to-Go , yang membuat proses ini sangat mudah. Ini menghasilkan struktur Go (dengan tag) yang akan berfungsi untuk JSON ini.


Yang harus Anda lakukan adalah menyalin objek JSON dan menempelkannya ke bidang yang ditandai dengan JSON , lalu menyalin hasilnya dan menempelkannya ke kode Anda. Inilah yang kita dapatkan untuk objek JSON di atas:


 type AutoGenerated struct { Status string `json:"status"` TotalResults int `json:"totalResults"` Articles []struct { Source struct { ID interface{} `json:"id"` Name string `json:"name"` } `json:"source"` Author string `json:"author"` Title string `json:"title"` Description string `json:"description"` URL string `json:"url"` URLToImage string `json:"urlToImage"` PublishedAt time.Time `json:"publishedAt"` Content string `json:"content"` } `json:"articles"` } 

Peramban yang berani menampilkan alat JSON to Go


Saya membuat beberapa perubahan pada struktur AutoGenerated dengan memisahkan fragmen Articles ke dalam strukturnya sendiri dan memperbarui nama struktur. Rekatkan deklarasi variabel tpl berikut ke main.go dan tambahkan paket time ke impor Anda:


 type Source struct { ID interface{} `json:"id"` Name string `json:"name"` } type Article struct { Source Source `json:"source"` Author string `json:"author"` Title string `json:"title"` Description string `json:"description"` URL string `json:"url"` URLToImage string `json:"urlToImage"` PublishedAt time.Time `json:"publishedAt"` Content string `json:"content"` } type Results struct { Status string `json:"status"` TotalResults int `json:"totalResults"` Articles []Article `json:"articles"` } 

Seperti yang Anda ketahui, Go mengharuskan semua bidang yang diekspor dalam struktur dimulai dengan huruf kapital. Namun, merupakan kebiasaan untuk mewakili bidang JSON menggunakan camelCase atau snake_case , yang tidak dimulai dengan huruf kapital.


Oleh karena itu, kami menggunakan tag bidang struktur seperti json:"id" untuk secara eksplisit menampilkan bidang struktur di bidang JSON, seperti yang ditunjukkan di atas. Ini juga memungkinkan Anda untuk menggunakan nama yang sama sekali berbeda untuk bidang struktur dan bidang json yang sesuai, jika perlu.


Terakhir, mari kita buat jenis struktur yang berbeda untuk setiap permintaan pencarian. Tambahkan ini di bawah struktur Results di main.go :


 type Search struct { SearchKey string NextPage int TotalPages int Results Results } 

Struktur ini mewakili setiap permintaan pencarian yang dibuat oleh pengguna. SearchKey adalah kueri itu sendiri, bidang NextPage memungkinkan NextPage untuk menggulir hasil, TotalPages - jumlah total halaman hasil permintaan, dan Results - halaman saat ini dari hasil permintaan.


Kirim permintaan menggunakan API Berita dan berikan hasilnya


Sekarang kami memiliki model data untuk aplikasi kami, mari lanjutkan dan buat permintaan ke News API, dan kemudian berikan hasilnya di halaman.


Karena API Berita memerlukan kunci API, kita perlu menemukan cara untuk meneruskannya dalam aplikasi kita tanpa kode yang sulit dalam kode. Variabel lingkungan adalah pendekatan yang umum, tetapi saya memutuskan untuk menggunakan flag baris perintah sebagai gantinya. Go menyediakan paket flag yang mendukung analisis dasar bendera baris perintah, dan inilah yang akan kami gunakan di sini.


Pertama mendeklarasikan variabel apiKey baru di bawah variabel tpl :


 var apiKey *string 

Kemudian gunakan dalam fungsi main sebagai berikut:


 func main() { apiKey = flag.String("apikey", "", "Newsapi.org access key") flag.Parse() if *apiKey == "" { log.Fatal("apiKey must be set") } //    } 

Di sini kita memanggil metode flag.String() , yang memungkinkan kita untuk mendefinisikan flag string. Argumen pertama untuk metode ini adalah nama bendera, yang kedua adalah nilai default, dan yang ketiga adalah deskripsi penggunaan.


Setelah mendefinisikan semua flag, Anda perlu memanggil flag.Parse() untuk benar-benar flag.Parse() . Akhirnya, karena apikey adalah komponen yang diperlukan untuk aplikasi ini, kami memastikan bahwa program mogok jika flag ini tidak diatur selama eksekusi program.


Pastikan Anda menambahkan paket flag ke impor Anda, kemudian restart server dan berikan flag apikey diperlukan, seperti yang ditunjukkan di bawah ini:


 go run main.go -apikey=<your newsapi access key> 

Selanjutnya, mari kita lanjutkan dan perbarui searchHandler sehingga permintaan pencarian pengguna dikirim ke newsapi.org dan hasilnya ditampilkan di templat kami.


Ganti dua panggilan ke metode fmt.Println() di akhir fungsi searchHandler kode berikut:


 func searchHandler(w http.ResponseWriter, r *http.Request) { // beginning of the function search := &Search{} search.SearchKey = searchKey next, err := strconv.Atoi(page) if err != nil { http.Error(w, "Unexpected server error", http.StatusInternalServerError) return } search.NextPage = next pageSize := 20 endpoint := fmt.Sprintf("https://newsapi.org/v2/everything?q=%s&pageSize=%d&page=%d&apiKey=%s&sortBy=publishedAt&language=en", url.QueryEscape(search.SearchKey), pageSize, search.NextPage, *apiKey) resp, err := http.Get(endpoint) if err != nil { w.WriteHeader(http.StatusInternalServerError) return } defer resp.Body.Close() if resp.StatusCode != 200 { w.WriteHeader(http.StatusInternalServerError) return } err = json.NewDecoder(resp.Body).Decode(&search.Results) if err != nil { w.WriteHeader(http.StatusInternalServerError) return } search.TotalPages = int(math.Ceil(float64(search.Results.TotalResults / pageSize))) err = tpl.Execute(w, search) if err != nil { w.WriteHeader(http.StatusInternalServerError) } } 

Pertama, kami membuat instance baru dari struktur Search dan mengatur nilai bidang SearchKey ke nilai parameter URL q dalam permintaan HTTP.


Setelah itu, kami mengonversi variabel page menjadi integer dan menetapkan hasilnya ke bidang NextPage variabel search . Kemudian kita membuat variabel pageSize dan menetapkan nilainya menjadi 20. Variabel pageSize ini menunjukkan jumlah hasil yang akan dikembalikan oleh API berita dalam responsnya. Nilai ini dapat berkisar dari 0 hingga 100.


Lalu kami membuat titik akhir menggunakan fmt.Sprintf() dan membuat permintaan GET untuk itu. Jika respons dari News API tidak 200 OK , kami akan mengembalikan kesalahan server umum kepada klien. Kalau tidak, badan respons diuraikan dalam search.Results . search.Results .


Lalu kami menghitung jumlah total halaman dengan membagi bidang TotalResults dengan TotalResults . Misalnya, jika kueri mengembalikan 100 hasil, dan kami hanya melihat 20 pada satu waktu, kami perlu menelusuri lima halaman untuk melihat semua 100 hasil untuk kueri itu.


Setelah itu, kami merender template kami dan meneruskan variabel search sebagai antarmuka data. Ini memungkinkan kami untuk mengakses data dari objek JSON di templat kami, seperti yang akan Anda lihat.


Sebelum beralih ke index.html , pastikan untuk memperbarui impor Anda seperti yang ditunjukkan di bawah ini:


 import ( "encoding/json" "flag" "fmt" "html/template" "log" "math" "net/http" "net/url" "os" "strconv" "time" ) 

Mari kita lanjutkan dan tampilkan hasilnya pada halaman dengan mengubah file index.html sebagai berikut. Tambahkan ini di bawah <header> :


 <section class="container"> <ul class="search-results"> {{ range .Results.Articles }} <li class="news-article"> <div> <a target="_blank" rel="noreferrer noopener" href="{{.URL}}"> <h3 class="title">{{.Title }}</h3> </a> <p class="description">{{ .Description }}</p> <div class="metadata"> <p class="source">{{ .Source.Name }}</p> <time class="published-date">{{ .PublishedAt }}</time> </div> </div> <img class="article-image" src="{{ .URLToImage }}"> </li> {{ end }} </ul> </section> 

Untuk mengakses bidang struktur dalam templat, kami menggunakan operator titik. Operator ini merujuk ke objek struktur (dalam hal ini, search ), dan kemudian di dalam templat kami cukup menentukan nama bidang (sebagai {{.Results}} ).


Blok range memungkinkan kita untuk beralih di atas slice di Go dan output beberapa HTML untuk setiap elemen di slice. Di sini, kami mengulangi potongan struktur Article yang terkandung dalam bidang Articles dan menampilkan HTML di setiap iterasi.


Mulai ulang server, segarkan peramban, dan cari berita tentang topik populer. Anda harus mendapatkan daftar 20 hasil per halaman, seperti yang ditunjukkan pada gambar di bawah.


Browser menampilkan daftar berita


Simpan kueri penelusuran dalam bahasa asing


Perhatikan bahwa permintaan pencarian menghilang dari input ketika halaman disegarkan dengan hasilnya. Idealnya, kueri harus disimpan sampai pengguna melakukan pencarian baru. Inilah cara kerja Google Penelusuran, misalnya.


Kita dapat dengan mudah memperbaikinya dengan memperbarui atribut value dari tag input dalam file index.html kami sebagai berikut:


 <input autofocus class="search-input" value="{{ .SearchKey }}" placeholder="Enter a news topic" type="search" name="q"> 

Mulai ulang browser Anda dan lakukan pencarian baru. Permintaan pencarian akan disimpan seperti yang ditunjukkan di bawah ini:



Format tanggal publikasi


Jika Anda melihat tanggal di setiap artikel, Anda akan melihat bahwa itu tidak dapat dibaca. Output saat ini menunjukkan bagaimana API Berita mengembalikan tanggal publikasi artikel. Tetapi kita dapat dengan mudah mengubah ini dengan menambahkan metode ke struktur Article dan menggunakannya untuk memformat tanggal alih-alih menggunakan nilai default.


Mari kita tambahkan kode berikut tepat di bawah struktur Article di main.go :


 func (a *Article) FormatPublishedDate() string { year, month, day := a.PublishedAt.Date() return fmt.Sprintf("%v %d, %d", month, day, year) } 

Di sini, metode FormatPublishedDate baru dibuat dalam struktur Article , dan metode ini memformat bidang PublishedAt di Article dan mengembalikan string dalam format berikut: 10 2009 .


Untuk menggunakan metode baru ini di templat Anda, ganti .PublishedAt dengan .FormatPublishedDate di file index.html Anda. Kemudian restart server dan ulangi permintaan pencarian sebelumnya. Ini akan menampilkan hasil dengan waktu yang diformat dengan benar, seperti yang ditunjukkan di bawah ini:


Peramban yang berani menampilkan tanggal yang diformat dengan benar


Tampilkan jumlah total hasil.


Mari kita tingkatkan antarmuka pengguna aplikasi berita kami dengan menunjukkan jumlah total hasil di bagian atas halaman, dan kemudian menampilkan pesan jika tidak ada hasil yang ditemukan untuk permintaan tertentu.


Yang harus Anda lakukan adalah menambahkan kode berikut sebagai anak dari .container , tepat di atas elemen .search-results dalam file index.html Anda:


 <div class="result-count"> {{ if (gt .Results.TotalResults 0)}} <p>About <strong>{{ .Results.TotalResults }}</strong> results were found.</p> {{ else if (ne .SearchKey "") and (eq .Results.TotalResults 0) }} <p>No results found for your query: <strong>{{ .SearchKey }}</strong>.</p> {{ end }} </div> 

Go , . gt , , TotalResults Results . , .


, SearchKey ( (ne .SearchKey "") ) TotalResults ( (eq .Results.TotalResults 0) ), «No results found».


, . «No results found».


Browser showing no results found message


. , :


Browser showing results count at the top of the page



20 , , .


Next , . , , Search main.go :


 func (s *Search) IsLastPage() bool { return s.NextPage >= s.TotalPages } 

, NextPage , TotalPages Search . , NextPage , . :


 func searchHandler(w http.ResponseWriter, r *http.Request) { //   search.TotalPages = int(math.Ceil(float64(search.Results.TotalResults / pageSize))) //   if  if ok := !search.IsLastPage(); ok { search.NextPage++ } //    } 

, , . .search-results index.html .


 <div class="pagination"> {{ if (ne .IsLastPage true) }} <a href="/search?q={{ .SearchKey }}&page={{ .NextPage }}" class="button next-page">Next</a> {{ end }} </div> 

, Next .


, href /search q , NextPage page .


Previous . , 1. , CurrentPage() Search , . IsLastPage :


 func (s *Search) CurrentPage() int { if s.NextPage == 1 { return s.NextPage } return s.NextPage - 1 } 

NextPage - 1 , , NextPage 1. , 1 . :


 func (s *Search) PreviousPage() int { return s.CurrentPage() - 1 } 

, Previous , 1. .pagination index.html :


 <div class="pagination"> {{ if (gt .NextPage 2) }} <a href="/search?q={{ .SearchKey }}&page={{ .PreviousPage }}" class="button previous-page">Previous</a> {{ end }} {{ if (ne .IsLastPage true) }} <a href="/search?q={{ .SearchKey }}&page={{ .NextPage }}" class="button next-page">Next</a> {{ end }} </div> 

. , :





, , , , .


index.html :


 <div class="result-count"> {{ if (gt .Results.TotalResults 0)}} <p>About <strong>{{ .Results.TotalResults }}</strong> results were found. You are on page <strong>{{ .CurrentPage }}</strong> of <strong> {{ .TotalPages }}</strong>.</p> {{ else if (ne .SearchKey "") and (eq .Results.TotalResults 0) }} <p>No results found for your query: <strong>{{ .SearchKey }}</strong>.</p> {{ end }} </div> 

, , .


Browser showing current page


Heroku


, , Heroku. , , . . freshman-news .


, Heroku . heroku login , Heroku.


, git- . , git init , , heroku git-. freshman-news .


 heroku git:remote -a freshman-news 

Procfile ( touch Procfile ) :


 web: bin/news-demo -apikey $NEWS_API_KEY 

GitHub Go, , go.mod , . , , .


 module github.com/freshman-tech/news-demo go 1.12.9 

Settings Heroku Reveal Config Vars . NEWS_API_KEY , .


Heroku config variables


, Heroku :


 git add . git commit -m "Initial commit" git push heroku master 

https://__.herokuapp.com , .


Kesimpulan


News Go -. , Heroku.


, . - , , .


Terima kasih sudah membaca!

Source: https://habr.com/ru/post/id475390/


All Articles