Bonjour, Habr! Je vous présente la traduction de l'article "Comment construire votre premiÚre application web avec Go" par Ayooluwa Isaiah.
Ceci est le guide de votre premiÚre application Web Go. Nous allons créer une application de news qui utilise l' API News pour recevoir des articles de news sur un sujet spécifique et la déployer sur le serveur de production à la fin.
Vous pouvez trouver le code complet utilisé pour ce tutoriel dans ce référentiel GitHub .
Prérequis
La seule condition requise pour cette tùche est que Go soit installé sur votre ordinateur et que vous connaissiez un peu sa syntaxe et ses constructions. La version Go que j'ai utilisée pour créer l'application est également la plus récente au moment de la rédaction: 1.12.9 . Pour afficher la version installée de Go, utilisez la commande go version
.
Si vous trouvez cette tùche trop difficile pour vous, allez à ma précédente leçon de langue d' introduction , qui devrait vous aider à démarrer.
Commençons donc!
Nous clonons le référentiel de fichiers de démarrage sur GitHub et cd
dans le répertoire créé. Nous avons trois fichiers principaux: Dans le fichier main.go
nous écrirons tout le code Go pour cette tùche. Le fichier index.html
est le modÚle qui sera envoyé au navigateur et les
de l'application se trouvent dans assets/styles.css
.
Créer un serveur Web de base
Commençons par crĂ©er un serveur principal qui envoie le texte «Hello World!» Au navigateur lors de l'exĂ©cution d'une requĂȘte GET Ă la racine du serveur. Modifiez votre fichier main.go
pour qu'il ressemble Ă ceci:
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) }
La premiĂšre ligne du package main
- déclare que le code du fichier main.go
au package principal. AprÚs cela, nous avons importé le package net/http
, qui fournit des implémentations client et serveur HTTP à utiliser dans notre application. Ce package fait partie de la bibliothÚque standard et est inclus avec chaque installation Go.
Dans la fonction main
, http.NewServeMux()
crĂ©e un nouveau multiplexeur de requĂȘtes HTTP et l'affecte Ă la variable mux
. Essentiellement, le multiplexeur de demandes fait correspondre l'URL des demandes entrantes à la liste des chemins enregistrés et appelle le gestionnaire approprié pour le chemin chaque fois qu'une correspondance est trouvée.
Ensuite, nous enregistrons notre premiĂšre fonction de gestionnaire pour le chemin racine /
. Cette fonction de gestionnaire est le deuxiĂšme argument de HandleFunc
et possĂšde toujours la func (w http.ResponseWriter, r * http.Request)
signature func (w http.ResponseWriter, r * http.Request)
.
Si vous regardez la fonction indexHandler
, vous verrez qu'elle a juste une telle signature, ce qui en fait un deuxiĂšme argument valide pour HandleFunc
. Le paramĂštre w
est la structure que nous utilisons pour envoyer des rĂ©ponses Ă la requĂȘte HTTP. Il implĂ©mente la mĂ©thode Write()
, qui prend une tranche d'octets et écrit les données combinées dans le cadre de la réponse HTTP.
En revanche, le paramĂštre r
reprĂ©sente la requĂȘte HTTP reçue du client. C'est ainsi que nous accĂ©dons aux donnĂ©es envoyĂ©es par le navigateur Web sur le serveur. Nous ne lâutilisons pas encore ici, mais nous lâutiliserons certainement plus tard.
Enfin, nous avons la méthode http.ListenAndServe()
, qui démarre le serveur sur le port 3000 si le port n'est pas défini par l'environnement. N'hésitez pas à utiliser un port différent si 3000 est utilisé sur votre ordinateur.
Ensuite, compilez et exécutez le code que vous venez d'écrire:
go run main.go
Si vous allez sur http: // localhost: 3000 dans votre navigateur, vous devriez voir le texte "Hello World!".

Go Templates
Voyons les bases de la crĂ©ation de modĂšles dans Go. Si vous connaissez les modĂšles dans d'autres langues, cela devrait ĂȘtre assez facile Ă comprendre.
Les modĂšles permettent de personnaliser facilement la sortie de votre application Web en fonction de l'itinĂ©raire sans avoir Ă Ă©crire le mĂȘme code Ă diffĂ©rents endroits. Par exemple, nous pouvons crĂ©er un modĂšle pour la barre de navigation et l'utiliser sur toutes les pages du site sans dupliquer le code. De plus, nous avons Ă©galement la possibilitĂ© d'ajouter une logique de base Ă nos pages Web.
Go propose deux bibliothĂšques de modĂšles dans sa bibliothĂšque standard: text/template
et html/template
. Les deux fournissent la mĂȘme interface, mais le package html/template
est utilisé pour générer une sortie HTML protégée contre l'injection de code, nous allons donc l'utiliser ici.
Importez ce package dans votre fichier main.go
et utilisez-le comme suit:
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
est une variable de niveau package qui indique la définition d'un modÚle à partir des fichiers fournis. L'appel template.ParseFiles
analyse le fichier index.html
à la racine de notre répertoire de projet et vérifie sa validité.
Nous encapsulons l'appel template.ParseFiles
dans template.Must
afin que le code provoque une panique lorsqu'une erreur se produit. La raison pour laquelle nous paniquons ici au lieu d'essayer de gĂ©rer l'erreur est parce que cela n'a aucun sens de continuer Ă exĂ©cuter le code si nous avons un modĂšle non valide. Il s'agit d'un problĂšme qui doit ĂȘtre rĂ©solu avant d'essayer de redĂ©marrer le serveur.
Dans la fonction indexHandler
nous indexHandler
le modĂšle créé prĂ©cĂ©demment en fournissant deux arguments: oĂč nous voulons Ă©crire la sortie et les donnĂ©es que nous voulons transmettre au modĂšle.
Dans le cas ci-dessus, nous écrivons la sortie dans l'interface ResponseWriter
et, puisque nous n'avons actuellement aucune donnée à transmettre à notre modÚle, nil
est transmis comme deuxiĂšme argument.
ArrĂȘtez le processus en cours dans votre terminal Ă l'aide de Ctrl-C et redĂ©marrez-le avec go run main.go
, puis actualisez votre navigateur. Vous devriez voir le texte «Démo de l'application News» sur la page, comme illustré ci-dessous:

Ajouter une barre de navigation Ă la page
Remplacez le contenu de la <body>
dans votre fichier index.html comme indiqué ci-dessous:
<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>
Redémarrez ensuite le serveur et actualisez votre navigateur. Vous devriez voir quelque chose de similaire à ceci:

Travailler avec des fichiers statiques
Veuillez noter que la barre de navigation que nous avons ajoutée ci-dessus n'a pas de styles, malgré le fait que nous les ayons déjà spécifiés dans la <head>
notre document.
En effet, le chemin /
correspond à tous les chemins qui ne sont pas traités ailleurs. Par conséquent, si vous accédez à http: // localhost: 3000 / assets / style.css , vous obtiendrez toujours la page d'accueil de News Demo au lieu du fichier CSS car la route /assets/style.css
pas été spécifiquement déclarée.
Mais la nécessité de déclarer des gestionnaires explicites pour tous nos fichiers statiques est irréaliste et ne peut pas évoluer. Heureusement, nous pouvons créer un gestionnaire pour servir toutes les ressources statiques.
La premiÚre chose à faire est de créer une instance de l'objet serveur de fichiers, en passant dans le répertoire dans lequel se trouvent tous nos fichiers statiques:
fs := http.FileServer(http.Dir("assets"))
Ensuite, nous devons dire à notre routeur d'utiliser cet objet serveur de fichiers pour tous les chemins commençant par le préfixe /assets/
:
mux.Handle("/assets/", http.StripPrefix("/assets/", fs))
Maintenant tous ensemble:
Redémarrez le serveur et actualisez le navigateur. Les styles doivent s'activer comme indiqué ci-dessous:

Nous créons un itinéraire / recherche
CrĂ©ons un itinĂ©raire qui gĂšre les requĂȘtes de recherche d'articles de presse. Nous utiliserons l' API News pour traiter les demandes, vous devez donc vous inscrire pour recevoir une clĂ© API gratuite ici .
Cette route attend deux paramĂštres de requĂȘte: q
reprĂ©sente la requĂȘte de l'utilisateur et la page
utilisée pour faire défiler les résultats. Ce paramÚtre de page
est facultatif. S'il n'est pas inclus dans l'URL, nous supposons simplement que le numéro de page des résultats est défini sur «1».
Ajoutez le gestionnaire suivant sous indexHandler
Ă votre fichier main.go
:
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) }
Le code ci-dessus extrait les paramĂštres q
et page
de l'URL de demande et les affiche tous les deux dans le terminal.
Enregistrez ensuite la fonction searchHandler
tant que gestionnaire de chemin de /search
, comme indiqué ci-dessous:
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))
N'oubliez pas d'importer les packages fmt
et net/url
ci-dessus:
import ( "fmt" "html/template" "net/http" "net/url" "os" )
RedĂ©marrez maintenant le serveur, entrez la requĂȘte dans le champ de recherche et vĂ©rifiez le terminal. Vous devriez voir votre demande dans le terminal, comme indiquĂ© ci-dessous:
Créer un modÚle de données
Lorsque nous faisons une demande au point de terminaison News API/everything
, nous attendons une réponse json au format suivant:
{ "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..." } ] }
Pour travailler avec ces données dans Go, nous devons générer une structure qui reflÚte les données lors du décodage du corps de réponse. Bien sûr, vous pouvez le faire manuellement, mais je préfÚre utiliser le site Web JSON-to-Go , ce qui rend ce processus vraiment facile. Il génÚre une structure Go (avec des balises) qui fonctionnera pour ce JSON.
Tout ce que vous avez à faire est de copier l'objet JSON et de le coller dans le champ marqué JSON , puis de copier la sortie et de la coller dans votre code. Voici ce que nous obtenons pour l'objet JSON ci-dessus:
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"` }

J'ai apporté plusieurs modifications à la structure AutoGenerated
en séparant le fragment Articles
dans sa propre structure et en mettant à jour le nom de la structure. Collez la déclaration de variable tpl
suivante dans main.go
et ajoutez le package de time
Ă votre importation:
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"` }
Comme vous le savez peut-ĂȘtre, Go requiert que tous les champs exportĂ©s de la structure commencent par une majuscule. Cependant, il est habituel de reprĂ©senter les champs JSON en utilisant camelCase ou snake_case , qui ne commencent pas par une majuscule.
Par conséquent, nous utilisons des balises de champ de structure telles que json:"id"
pour afficher explicitement le champ de structure dans le champ JSON, comme indiqué ci-dessus. Il vous permet également d'utiliser des noms complÚtement différents pour le champ de structure et le champ json correspondant, si nécessaire.
Enfin, crĂ©ons un type de structure diffĂ©rent pour chaque requĂȘte de recherche. Ajoutez ceci sous la structure des Results
dans main.go
:
type Search struct { SearchKey string NextPage int TotalPages int Results Results }
Cette structure reprĂ©sente chaque requĂȘte de recherche effectuĂ©e par l'utilisateur. SearchKey
est la requĂȘte elle-mĂȘme, le champ NextPage
permet de faire défiler les résultats, TotalPages
- le nombre total de pages de rĂ©sultats de requĂȘte et Results
- la page actuelle des rĂ©sultats de requĂȘte.
Envoyer une demande à l'aide de l'API News et afficher les résultats
Maintenant que nous avons le modÚle de données pour notre application, continuons et faisons des demandes à l'API News, puis restituons les résultats sur la page.
Ătant donnĂ© que l'API News nĂ©cessite une clĂ© API, nous devons trouver un moyen de la transmettre dans notre application sans codage en dur dans le code. Les variables d'environnement sont une approche courante, mais j'ai dĂ©cidĂ© d'utiliser Ă la place des drapeaux de ligne de commande. Go fournit un package d' flag
qui prend en charge l'analyse de base des indicateurs de ligne de commande, et c'est ce que nous allons utiliser ici.
apiKey
abord une nouvelle variable apiKey
sous la variable tpl
:
var apiKey *string
Ensuite, utilisez-le dans la fonction main
comme suit:
func main() { apiKey = flag.String("apikey", "", "Newsapi.org access key") flag.Parse() if *apiKey == "" { log.Fatal("apiKey must be set") }
Ici, nous appelons la méthode flag.String()
, qui nous permet de définir un indicateur de chaßne. Le premier argument de cette méthode est le nom de l'indicateur, le second est la valeur par défaut et le troisiÚme est la description de l'utilisation.
AprÚs avoir défini tous les indicateurs, vous devez appeler flag.Parse()
pour les flag.Parse()
réellement. Enfin, comme l' apikey
est un composant requis pour cette application, nous nous assurons que le programme se bloque si ce drapeau n'est pas défini pendant l'exécution du programme.
Assurez-vous d'ajouter le package d' flag
à votre importation, puis redémarrez le serveur et transmettez l'indicateur d' apikey
requis, comme indiqué ci-dessous:
go run main.go -apikey=<your newsapi access key>
Ensuite, continuons et mettons Ă jour searchHandler
afin que la requĂȘte de recherche de l'utilisateur soit envoyĂ©e Ă newsapi.org et que les rĂ©sultats soient affichĂ©s dans notre modĂšle.
Remplacez les deux appels à la méthode fmt.Println()
Ă la fin de la fonction searchHandler
code suivant:
func searchHandler(w http.ResponseWriter, r *http.Request) {
Tout d'abord, nous créons une nouvelle instance de la structure de Search
et définissons la valeur du champ SearchKey
sur la valeur du paramĂštre d'URL q
dans la demande HTTP.
AprĂšs cela, nous convertissons la variable de page
en un entier et NextPage
le résultat au champ NextPage
variable de search
. Ensuite, nous créons la variable pageSize
et définissons sa valeur sur 20. Cette variable pageSize
représente le nombre de résultats que l'API news renverra dans sa réponse. Cette valeur peut aller de 0 à 100.
Ensuite, nous créons le point de terminaison à l'aide de fmt.Sprintf()
et lui faisons une demande GET. Si la réponse de l'API News n'est pas 200 OK , nous renverrons une erreur générale du serveur au client. Sinon, le corps de la réponse est analysé dans search.Results
.
Ensuite, nous calculons le nombre total de pages en divisant le champ pageSize
par pageSize
. Par exemple, si une requĂȘte renvoie 100 rĂ©sultats et que nous n'en affichons que 20 Ă la fois, nous devrons faire dĂ©filer cinq pages pour voir les 100 rĂ©sultats de cette requĂȘte.
AprĂšs cela, nous rendons notre modĂšle et passons la variable de search
comme interface de données. Cela nous permet d'accéder aux données d'un objet JSON dans notre modÚle, comme vous le verrez.
Avant de passer Ă index.html
, assurez-vous de mettre à jour vos importations comme indiqué ci-dessous:
import ( "encoding/json" "flag" "fmt" "html/template" "log" "math" "net/http" "net/url" "os" "strconv" "time" )
Continuons et affichons les résultats sur la page en modifiant le fichier index.html
comme suit. Ajoutez ceci sous la <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>
Pour accéder au champ de structure dans le modÚle, nous utilisons l'opérateur point. Cet opérateur fait référence à un objet de structure (dans ce cas, la search
), puis à l'intérieur du modÚle, nous {{.Results}}
simplement le nom du champ (comme {{.Results}}
).
Le bloc de range
nous permet d'itérer sur une tranche dans Go et de produire du HTML pour chaque élément de la tranche. Ici, nous parcourons la tranche des structures Article
contenues dans le champ Articles
et affichons le HTML à chaque itération.
Redémarrez le serveur, actualisez le navigateur et recherchez des actualités sur un sujet populaire. Vous devriez obtenir une liste de 20 résultats par page, comme indiqué dans la capture d'écran ci-dessous.

Notez que la requĂȘte de recherche disparaĂźt de l'entrĂ©e lorsque la page est actualisĂ©e avec les rĂ©sultats. IdĂ©alement, la requĂȘte doit ĂȘtre conservĂ©e jusqu'Ă ce que l'utilisateur effectue une nouvelle recherche. Voici comment fonctionne la recherche Google, par exemple.
Nous pouvons facilement résoudre ce problÚme en mettant à jour l'attribut value
de la balise d' input
dans notre fichier index.html
comme suit:
<input autofocus class="search-input" value="{{ .SearchKey }}" placeholder="Enter a news topic" type="search" name="q">
RedĂ©marrez votre navigateur et effectuez une nouvelle recherche. La requĂȘte de recherche sera enregistrĂ©e comme indiquĂ© ci-dessous:

Si vous regardez la date dans chaque article, vous verrez qu'elle est mal lisible. La sortie actuelle montre comment l'API News renvoie la date de publication de l'article. Mais nous pouvons facilement changer cela en ajoutant une méthode à la structure Article
et en l'utilisant pour formater la date au lieu d'utiliser la valeur par défaut.
Ajoutons le code suivant juste en dessous de la structure Article
dans main.go
:
func (a *Article) FormatPublishedDate() string { year, month, day := a.PublishedAt.Date() return fmt.Sprintf("%v %d, %d", month, day, year) }
Ici, une nouvelle méthode FormatPublishedDate
créée dans la structure Article
, et cette méthode formate le champ PublishedAt
dans Article
et renvoie une chaĂźne au format suivant: 10 2009
.
Pour utiliser cette nouvelle méthode dans votre modÚle, remplacez .PublishedAt
par .FormatPublishedDate
dans votre fichier index.html
. RedĂ©marrez ensuite le serveur et rĂ©pĂ©tez la requĂȘte de recherche prĂ©cĂ©dente. Cela produira les rĂ©sultats avec une heure correctement formatĂ©e, comme indiquĂ© ci-dessous:

Affiche le nombre total de résultats.
AmĂ©liorons l'interface utilisateur de notre application d'actualitĂ©s en indiquant le nombre total de rĂ©sultats en haut de la page, puis affichons un message au cas oĂč aucun rĂ©sultat n'aurait Ă©tĂ© trouvĂ© pour une requĂȘte particuliĂšre.
Tout ce que vous avez Ă faire est d'ajouter le code suivant en tant qu'enfant du .container
, juste au-dessus de l'élément .search-results
dans votre fichier index.html
:
<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».

. , :

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-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>
, , .

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 :
git add . git commit -m "Initial commit" git push heroku master
https://__.herokuapp.com , .
Conclusion
News Go -. , Heroku.
, . - , , .
Merci d'avoir lu!