Are you interested in The Twilio Community Hackathon but not sure where to start? In this post, I talk about a game called TwilioQuest and how I am using it with Go to explore the Twilio API and drum up inspiration on how to solve today's COVID-related challenges (or simply make something fun!)
What is TwilioQuest?
TwilioQuest is a neat role-playing game made to teach a variety of technical skills through missions themed in a retro-looking sci-fi world. There are many missions that are focused on interacting with the Twilio API, and the game includes modules that are applicable to any developer as well, such as improving your command line skills and contributing to open source software on GitHub. As I flush out some ideas for the hackathon, I thought it wood be a good idea to start learning the API using TwilioQuest.
This hackathon is awesome because of the support being provided to participants (including free API credits! score!) and a category specifically for helping solve communication challenges as a result of the COVID-19 pandemic. I have never been a strong Javascript developer, and saw this as a unique opportunity to learn a little bit of Javascript and work on my Golang, which will be the focus of this post.
On to the Coding!
Twilio has official SDKs for lots of languages like Python and Javascript, but not for Go yet. There are some great libraries out there already on GitHub, but for the purpose of really learning the API I thought it was important to roll my own simple library to make it through the TwilioQuest missions. In this post, we are going to go over some of the code I wrote to get through the Basic Training mission in TwilioQuest.
Setting up a Basic Webserver in Go with Gin
Go has a great standard library with the web in mind since day one. We could use the raw http
library here but I have opted to use Gin. There are many reasons to use or not use web frameworks, but ultimately when I sit down to write side projects I take into consideration what my goals are. In this case, my goals are to practice my writing, learn some of the Twilio API, and see if we can make some tools that can make a difference. Using Gin here to speed-up some of my web server setup is a tool towards that end! If you are following along but want to use just the standard library, the offical documentation has a pretty good web server tutorial to get you started.
One of the first tasks in Basic Training is to write a web server that returns hello world
when you visit it. To accomplish this with Gin, all we need to do is create a new gin engine, tell the engine what to return when someone visits us, and run it!
package main
import "github.com/gin-gonic/gin"
func main() {
router := gin.Default()
router.GET("/", func(c *gin.Context) {
c.String(200, "hello world")
})
router.Run(":5000")
}
The default engine, which we are calling router
here, sets up some basic logging middleware and recovery middleware to help handle server (500 style) errors and give you some sensible logging defaults to help with debugging in the future. We then define what happens when someone makes a GET call to our web server. We are choosing to call our engine router
because at the basic level, most of the work we want to do with this variable is defining what to do when a user visits the different routes available on our web server. The first argument is the path and the second argument is a function of type gin.HandlerFunc
. In the future we will write our functions elsewhere to organize the code better. This function writes the string hello world
to send back as the response, along with a 200 HTTP Status Code (OK!). Lastly, we tell the engine to run and pass the argument :5000
which means to run as localhost
on port 5000.
Accessing Environment Variables
The basic training mission includes some tasks that involve providing your Twilio API credentials. The game comes with a build-in IDE that supports Javascript, and these tasks are to allow you to easily access those credentials througout the game. Since we are writing in Go outside of the game, we want to make sure we are still able to access those variables. One safe way to do so is to never include your credentials in any of your files, and instead access them through environment variables. One way to do this in Go is through the os
standard library:
// initialize the twilio credentials. We will need this later!
TwilioAccountSID := os.Getenv("TWILIO_ACCOUNT_SID")
TwilioAuthToken := os.Getenv("TWILIO_AUTH_TOKEN")
You don't need to use these credentials to pass basic training, but we will want to use something like this so that we can interact with the live API later!
Writing TwiML using Go Structs and Tags
TwiML is the markdown language that is used to communicate actions to the Twilio API for features like programmable voice and programmable sms. If you are writing applications with a supported SDK, the TwiML is mostly written for you. It is very xml-like and we can use that to our advantage in Go. Go structs allow you to add tags to explicitly define how a field should be represented in other formats, such as json or XML.
To pass basic training, we have to create an endpoint that returns TwiML with say tags. To that end, we will make a struct that will represent a TwiML Response, setup a handler to send the Response, and then hook up the handler to response to the /hello
route defined in the mission.
// Say is a representation of the TWiML say instruction
type Say string
// Response is a representation of TwiML instructions
type Response struct {
Says []Say `xml:"say"`
}
Our struct is called Response to match the TwiML terminology. We use the tags feature to let Go know that if we need to represent this struct in an XML format (which will make the struct TwiML compatible as well) how to name each field.
Go lets us define custom data types. In this case, we are making a type called Say
and telling Go that it can treat it as a string. This lets us add additional functionality and help us enforce rules about that particular data type later on.
Sending TwiML!
func hello(c *gin.Context) {
// return back a say tag
response := Response{
Says: []Say{"Hello, World!", "Welcome to my Twilio App!"},
}
c.XML(200, response)
}
To define a handler, your function needs to take in a *gin.Context
as its sole parameter. This context is "the most important part of Gin" according to the source code and is used to pass variables between different layers of middleware and houses many of the tools you need to render content back to the user. In this handler hello
, we are creating a response object and telling our context to serialize the object as XML (using the tags we made earlier as hints to the compiler on how we want it!).
Now we just need to setup a route!
router.GET("/", func(c *gin.Context) {
c.String(200, "hello world")
})
router.POST("/hello", hello)
The first line is how we setup a route earlier with an inline function. The second line defines the function to link to the route /hello
to be the hello function we wrote. The line begins with router.POST
to tell our webserver to only route responses to this handler if the user uses a POST request, which is one of the requirements for one of our basic training mission tasks.
Once that's all hooked up, you can run your server and test the path!
go build
./twilio-quest
And then in another terminal or using postman:
curl --location --request POST 'http://localhost:5000/hello'
<Response>
<say>Hello, World!</say>
<say>Welcome to my Twilio App!</say>
</Response>
We now have a running web server, a route to receive requests, and learned a way to start working with TwiML within Go! If you want to see the whole main.go
file we just wrote, this is what I ended up with:
package main
import (
"github.com/gin-gonic/gin"
)
// Say is a representation of the TWiML say instruction
type Say string
// Response is a representation of TwiML instructions
type Response struct {
Says []Say `xml:"say"`
}
func hello(c *gin.Context) {
// return back a say tag
response := Response{
Says: []Say{"Hello, World!", "Welcome to my Twilio App!"},
}
c.XML(200, response)
}
func main() {
// initialize the twilio credentials. We will need this later!
// TwilioAccountSID := os.Getenv("TWILIO_ACCOUNT_SID")
// TwilioAuthToken := os.Getenv("TWILIO_AUTH_TOKEN")
router := gin.Default()
router.GET("/", func(c *gin.Context) {
c.String(200, "hello world")
})
router.POST("/hello", hello)
router.Run(":5000")
}
What Next?
The basic training mission includes some other good tasks to go through. One tasks is to install ngrok for your environment so you can assign a temporary public URL to route traffic to your test web server running on localhost
. This is helpful if you want to do some quick testing that requires an internet endpoint or want to use a website like postwoman.io to test your routes.
As we go through TwilioQuest (and start thinking about what we want to do for the April 2020 Hackathon!), we will likely find good opportunities to start refactoring our code to make it easier to work with. Some likely tasks include moving all of our Twilio-related code into a single package that we can re-use and share later, support for configuration files, and tests.
In the next article, we will take a look at parts of the SMS and Programmable SMS API and think through some ways we can creatively use it! Follow me here on dev.to or on twitter to get updated when the post drops! If there are other topics about Go, such as some tips on getting started with Go Modules (AKA Go's package.json) or APIs you want to explore, let me know below!
Top comments (1)
This is awesome, I love seeing the exploration of other languages that we don't officially support yet (though we've definitely had some requests). I hope you're enjoying TwilioQuest too!