Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Learning Go - Build your first Slack app using ...
Search
Ernesto Jiménez
October 08, 2016
Programming
170
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Learning Go - Build your first Slack app using Go and App Engine
Ernesto Jiménez
October 08, 2016
More Decks by Ernesto Jiménez
See All by Ernesto Jiménez
From Zero to 400 webhooks/second in 24 hours
ernesto_jimenez
0
120
Keep packages backwards compatible
ernesto_jimenez
0
150
Inception. Go programs that generate Go code
ernesto_jimenez
3
460
Other Decks in Programming
See All in Programming
Signal Forms: Details & Live Coding @enterJS 2026 in Mannheim
manfredsteyer
PRO
0
200
鹿野さんに聞く!『TypeScriptコードレシピ集』で磨く実践力
tonkotsuboy_com
4
900
1B+ /day規模のログを管理する技術
broadleaf
0
120
気圧・高度・GPSを記録&可視化するアプリ「Koudo」を作った話
hjmkth
1
330
IBM Bobを活用したレガシーアプリの最新化
oniak3ibm
PRO
1
220
Skillsは効率化、Agentsは"自分の拡張"——Builder時代のエージェント編成(CC Night 2026)
wemra
1
180
Oxlintのカスタムルールの現況
syumai
6
1.2k
Semantic Version 単位で戦略を柔軟に変えて、パッケージアップデートを自動化する
daitasu
1
320
エンジニア向け会社紹介/Findy Company Profile
findyinc
6
350k
生成AI時代にこそ効くGo | Why Go Works in the Age of Generative AI
mom0tomo
8
3.4k
Honoでのサプライチェーン侵害対策 〜 3つのライブラリに学ぶ
yusukebe
7
1.5k
Hunting Vulnerabilities in Symfony with LLMs
vinceamstoutz
0
560
Featured
See All Featured
Product Roadmaps are Hard
iamctodd
PRO
55
12k
The SEO identity crisis: Don't let AI make you average
varn
0
510
Tips & Tricks on How to Get Your First Job In Tech
honzajavorek
1
550
The #1 spot is gone: here's how to win anyway
tamaranovitovic
3
1.1k
The Art of Programming - Codeland 2020
erikaheidi
57
14k
Agile Leadership in an Agile Organization
kimpetersen
PRO
0
170
Utilizing Notion as your number one productivity tool
mfonobong
4
330
Bioeconomy Workshop: Dr. Julius Ecuru, Opportunities for a Bioeconomy in West Africa
akademiya2063
PRO
1
160
A Tale of Four Properties
chriscoyier
163
24k
Automating Front-end Workflow
addyosmani
1370
210k
Evolution of real-time – Irina Nazarova, EuRuKo, 2024
irinanazarova
9
1.4k
Beyond borders and beyond the search box: How to win the global "messy middle" with AI-driven SEO
davidcarrasco
3
170
Transcript
Learning Go Build your first Slack app using Go and
App Engine
1. Why Go? 2. Learning the basics of Go 3.
Deploying to App Engine 4. Building our Slack app
Building HTTP APIs in Go is extremely easy
Why go?
Go is what C++ should have been @ernesto_jimenez's joke
272 pages
1.368 pages just C++?
400 pages
None
it is very simple
and very powerful
it has great tooling
and the stdlib is amazing
You can be productive with go really quickly
Why am I here?
None
None
The very basics
Hello World
package main import "fmt" func main() { fmt.Println("Hello DevFest!") }
$ go run main.go Hello DevFest!
Hello World API
package main import ( "fmt" "net/http" ) func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { name := r.URL.Query().Get("name") if name == "" { name = "DevFest" } fmt.Fprintf(w, "Hello %s!", name) }) http.ListenAndServe(":8080", http.DefaultServeMux) }
$ curl http://localhost:8080/ Hello DevFest! $ curl "http://localhost:8080/?name=world" Hello world!
Hello World JSON API
package main import ( "encoding/json" "fmt" "net/http" )
type request struct { Name string `json:"name"` } type response
struct { Message string `json:"message,omitempty"` Error string `json:"error,omitempty"` }
func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { var
data request err := json.NewDecoder(r.Body).Decode(&data) if err != nil { w.WriteHeader(http.StatusBadRequest) json.NewEncoder(w).Encode(response{ Error: err.Error(), }) return } json.NewEncoder(w).Encode(response{ Message: fmt.Sprintf("Hello %s!", data.Name), }) }) http.ListenAndServe(":8080", http.DefaultServeMux) }
$ curl http://localhost:8080/ -d '{"name": "DevFest"}' {"message":"Hello DevFest!"} $ curl
http://localhost:8080/ -d 'Something' {"error":"invalid character 'S' looking for beginning of value"}
Deploying to App Engine
Create your app.yaml
runtime: go api_version: go1 handlers: - url: /.* script: _go_app
Tweak your code
package app // rename your package import ( "encoding/json" "fmt"
"net/http" ) func init() { // change main for init http.HandleFunc("/", handler) // remove http.ListenAndServe } func handler(w http.ResponseWriter, r *http.Request) { // ... }
Install the Go App Engine SDK https://cloud.google.com/appengine/downloads
Run your API locally
$ goapp serve Skipping SDK update check. Starting API server
at: http://localhost:63885 Starting module "default" running at: http://localhost:8080 Starting admin server at: http://localhost:8000 default: "POST / HTTP/1.1" 200 29 $ curl http://localhost:8080/ -d '{"name": "DevFest"}' {"message":"Hello DevFest!"}
None
Deploying your application
Create a Google Cloud Project https://console.cloud.google.com/ save your project id
$ goapp deploy \ --application [ YOUR PROJECT ID ]
\ --version [ OPTIONAL VERSION ID ] \ app.yaml
$ goapp deploy \ --application devfest-bot \ --version hello-world \
app.yaml
$ curl http://hello-world.devfest-bot.appspot.com/ \ -d '{"name": "DevFest"}' {"message":"Hello DevFest!"}
Building our Slack app
None
token=uitkEUWQ9mLLyNZPKsitLjGE team_id=T0001 team_domain=example channel_id=C2147483705 channel_name=test user_id=U2147483697 user_name=Steve command=/weather text=London response_url=https://hooks.slack.com/commands/1234/5678
/echo
package echo import ( "fmt" "net/http" ) func init() {
http.HandleFunc("/command", command) } func command(w http.ResponseWriter, r *http.Request) { user := r.PostFormValue("user_name") text := r.PostFormValue("text") fmt.Fprintf(w, "%s, %s", user, text) }
Testing it out locally
$ goapp serve $ ngrok http 8080 Web Interface http://127.0.0.1:4040
Forwarding http://274a0d24.ngrok.io -> localhost:8080 Forwarding https://274a0d24.ngrok.io -> localhost:8080
None
None
None
Deploying
$ goapp deploy \ --application devfest-bot \ --version echo \
app.yaml
None
None
None
None
Securing the command
go get google.golang.org/appengine
import ( "fmt" "net/http" "os" "google.golang.org/appengine" "google.golang.org/appengine/log" )
func init() { http.HandleFunc("/command", checkToken(os.Getenv("TOKEN"), command)) } func checkToken(token string,
h http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := appengine.NewContext(r) if r.PostFormValue("token") != token { log.Warningf(ctx, "Invalid token") w.WriteHeader(http.StatusUnauthorized) return } h(w, r) } } func command(w http.ResponseWriter, r *http.Request) { user := r.PostFormValue("user_name") text := r.PostFormValue("text") fmt.Fprintf(w, "%s, %s", user, text) }
$ appcfg.py update \ --application devfest-bot \ --version echo \
-E TOKEN:[ YOUR TOKEN ] \ app.yaml
$ curl -iX POST http://echo.devfest-bot.appspot.com/command \ -d token=invalid HTTP/1.1 401
Unauthorized Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: 249651e6b5324a76026990575350b6b4;o=1 Date: Sun, 02 Oct 2016 17:31:55 GMT Server: Google Frontend Content-Length: 0
/translate
go get google.golang.org/api/translate/v2
import ( "fmt" "net/http" "os" "google.golang.org/appengine" "google.golang.org/appengine/log" "google.golang.org/api/googleapi/transport" "google.golang.org/api/translate/v2" "google.golang.org/appengine/urlfetch"
)
var apiKey = os.Getenv("TRANSLATE_KEY") func command(w http.ResponseWriter, r *http.Request) {
ctx := appengine.NewContext(r) ts, err := translate.New(&http.Client{ Transport: &transport.APIKey{ Key: apiKey, Transport: &urlfetch.Transport{Context: ctx}, }, }) if err != nil { log.Errorf(ctx, "Error translate.New: %s", err.Error()) http.Error(w, "internal error", http.StatusInternalServerError) return } // ...
// ... t := ts.Translations.List([]string{ r.PostFormValue("text"), }, "en") res, err
:= t.Do() if err != nil { log.Errorf(ctx, "Error translating: %s", err.Error()) fmt.Fprintf(w, "Error: %s", err.Error()) return } for _, t := range res.Translations { text := t.TranslatedText lang := t.DetectedSourceLanguage fmt.Fprintf(w, "%s (%s)\n", text, lang) } }
None
None
A nicer output
type message struct { Text string `json:"text"` Attachments []attachment `json:"attachments,omitempty"`
ResponseType string `json:"response_type,omitempty"` } type attachment struct { Text string `json:"text"` Color string `json:"color"` }
func command(w http.ResponseWriter, r *http.Request) { // [ initialise ts
] w.Header().Set("Content-Type", "application/json") t := ts.Translations.List([]string{r.PostFormValue("text")}, "en") res, err := t.Do() if err != nil { log.Errorf(ctx, "Error translating: %s", err.Error()) json.NewEncoder(w).Encode(message{ Text: "Error", Attachments: []attachment{ {Color: "danger", Text: err.Error()}, }, }) return } // ...
// ... msg := message{ Text: fmt.Sprintf("Translating %q", r.PostFormValue("text")), ResponseType:
"in_channel", } for _, t := range res.Translations { msg.Attachments = append(msg.Attachments, attachment{ Color: "good", Text: fmt.Sprintf( "%s (%s)", t.TranslatedText, t.DetectedSourceLanguage, ), }) } json.NewEncoder(w).Encode(msg) }
None
under 100 lines of code
production ready
the source code git.io/devfest-london-2016
the Slack invite.slack.golangbridge.org #golang-newbies
Happy Hacking! @ernesto_jimenez
[email protected]