How to Apply Design Patterns in Golang
Time to read: 7 minutes
Applying design patterns in Golang
In the world of software engineering, design patterns serve as battle-tested solutions to common architectural challenges. Among these, the Singleton and Factory patterns stand out for their practical utility and widespread adoption across various programming paradigms. When implemented in Go—a language known for its simplicity and efficiency —they can significantly enhance application structure and maintainability.
The Singleton pattern ensures that a class has only one instance while providing a global point of access to it. This pattern proves invaluable when exactly one object is needed to coordinate actions across a system. Meanwhile, the Factory pattern creates objects without exposing the instantiation logic to the client, referring to the newly created object through a common interface.
To demonstrate these patterns, you will build a simple application that can send messages via three channels - voice messages, SMS messages, or WhatsApp messages. The messaging functionality will be powered by the Twilio Messaging API.
Prerequisites
To follow this tutorial, you will need the following:
- A free Twilio account. If you don't have one yet, sign up for a free one today
- A Twilio phone number. You also need to ensure that your Twilio phone number has appropriate geo permissions to enable calls to the destinations you need to call
- A Twilio WhatsApp number. For development purposes, you can activate the Twilio Sandbox for WhatsApp
- A phone that can receive phone calls, SMS, and WhatsApp messages
- Go 1.24
- A basic understanding of Go
Set up the project
The first thing you need to do is to create a project folder and navigate into it. To do that, run the following commands.
Next, create a new Go module by running the following command
Then, add the project's dependencies:
- GoDotEnv: This will help with managing environment variables
- Twilio Go Helper Library: This simplifies interacting with Twilio's APIs
To install them, run the command below.
Next, create a new file named .env to store the environment variables which the application requires. Then, in the new file paste the following code.
The next thing you need to do is to retrieve your Twilio Account SID, Auth Token, phone number, and WhatsApp number. To do that:
- Login to the Twilio Console
- Copy the details of the first three from the Account Info panel
- In .env, replace <your-account-sid> , <your-auth-token>, <your-twilio-phone-number> respectively, with the copied details


If you are using the WhatsApp sandbox, make sure you have added your phone number to the sandbox by following the setup instructions. The phone number to be used in your .env file will also be shown here. Ensure you only copy the phone number (in the format + XXXXXXXXXXX).


Create notification-related functionality
Create a new folder named notifier at the root of the project folder. This will hold the code for notification-related functionality.
Next, you will create the application notifiers; these are services responsible for sending messages. Your application will have three: SMSNotifier, VoiceNotifier, and WhatsAppNotifier, for notifications via SMS, voice call, and WhatsApp respectively.
In the notifier folder, create a new file named sms.go and add the following code to it.
Here, you created a new struct named SMSNotifier
with one receiver method named Notify
. This method takes the recipient’s phone number and the message to be delivered, and makes a request to the Twilio API using the twilio-go library. If an error is encountered, it is returned with an empty string; otherwise the message's SID is returned.
Next, in the notifier folder, create a new file named voice.go and add the following code to it.
Your implementation here is similar to the one for sending SMS notifications. You have a VoiceNotifier
struct with a receiver method named Notify
which takes the same parameters, makes an API call, and returns a message SID if the request was handled successfully.
There’s a slight difference, however, in the content of the request. For your voice messages, you used TwiML to create an instruction for Twilio. This instruction is to read the typed message when the recipient answers the phone call. You created this instruction using the sayTwiml()
function.
The last notifier to create is the one for sending WhatsApp notifications. In the notifier folder, create a new file named whatsApp.go and add the following code to it.
Similar to what has been done in the previous two notifiers, a struct named WhatsAppNotifier is created with a receiver method named Notify. This method takes the recipient phone number and message, then sends a request to Twilio's Programmable Messaging API. Since this is a WhatsApp message request, the from and to phone numbers are prepended with whatsapp:.
With the notifiers in place, the next thing to do is create an interface which will be exposed to other parts of the application, and a factory function to return the appropriate interface implementation based on a specified criteria in this case, the notification medium. In the notifier folder, create a new file named notifier.go and add the following code to it.
Using the autoload package made available by GoDotEnv, the variables in .env are loaded on import. Next, the code initiated the Twilio REST client using the NewRestClient() function. This function retrieves the Twilio SID and Auth Token from your environment variables and creates a new client for you. Next, it retrieved your Twilio phone number and WhatsApp number from your environment variables.
Then, it declared an interface named Notifier which has a single function named Notify(). This function takes a phone number and a message, and returns a string and an error. All the notifiers declared earlier implement the Notifier interface because they have a single method with the same signature as the Notify() function.
After that, it declares a function named GetNotifier() which takes a string for the notification medium and returns a notifier for the specified medium. Notice that the method signature returns an interface and not a concrete notifier. Finally, the GetSupportedMedia() function returns all the currently supported means of sending notifications.
With this in place, it’s time to create the templates for rendering the application frontend.
Create the view templates
For the frontend, you will use Go templates to render HTML pages. Create a new folder named template at the root of the project folder. In the template folder, create a new file named base.html and add the following code to it.
This template imports Bootstrap for styling, creates a slot for the body of a page (which is defined in a separate template), and renders an error or success alert if available in the template context (which we will create later).
Next, in the template folder, create a new file named index.html which renders the index page. Add the following code to the newly created file.
This renders a form which allows the user to provide a phone number (in E.164 format) and a notification medium. On submit, a request will be sent to the backend and the message will be sent to the user via the specified medium.
Create the route's handler
Having created the templates, you need to create handler functions to do the following:
Render the index page
Handle the request to send a notification
To keep your handler functions concise, you can create some helper functions for validation and template rendering.
Create a new folder named handler at the root of the project folder. In the handler folder, create a new file named helper.go and add the following code to it.
Here, you declared a struct named TemplateContext which is used by the template to render errors, success messages; or display data returned from the backend. Next, you created a pointer instance of the TemplateContext which will be used throughout the application — known as a singleton.
The clearContext() function resets the error and success message values to empty strings. The render() function is used to render a template while the renderError() populates the error message in the TemplateContext before rendering the template. The check() function logs any errors encountered and kills the application.
Finally, the validatePhoneNumber() function ensures that the provided phone number matches against the regular expression for a valid E.164 phone number. In the event that the provided phone number is invalid, an error is returned with an appropriate error message.
Next, in the handler folder, create a new file named handler.go and add the following code to it
The index() function is responsible for rendering the index page. This function clears the TemplateContext, sets the context data to the supported notification media, and renders the index.html template you created earlier.
The notify() function is responsible for handling the submitted form and sending the notification. This function retrieves the recipient phone number and notification medium from the parsed form. Next, it validates the phone number using the validatePhoneNumber() function you declared earlier.
Then, it retrieves the appropriate notifier based on the submitted form and tries to send a notification. Observe that the notify() function is decoupled from the concrete notifiers. This means that adding or removing a notifier will not require any modification of this function. This is the major benefit that the Factory design pattern affords you.
At any point, if an error is returned, this error is rendered and the user can refill and resubmit the form. If everything works as expected, the context message is populated and the index page is re-rendered to show the success message, allowing the user to send another notification if so desired.
Finally, the GetRouter() function creates a new Mux which links the specified paths to the appropriate handler function.
Put it all together
Finally, create a new file named main.go at the root of the folder and add the following code to it.
Here, your application is served on port 8080. Now, run the application with the following command.
Opening http://localhost:8080 in your browser, it should look similar to the screen recording below.


That's how to apply design patterns in Go
There you have it! I bet it was easier than you expected. You can review the final codebase for this article on GitHub, should you get stuck at any point. I’m excited to see what else you come up with. Until next time, make peace not war✌🏾
Joseph Udonsak is a software engineer with a passion for solving challenges — be it building applications, or conquering new frontiers on Candy Crush. When he’s not staring at his screens, he enjoys a cold beer and laughs with his family and friends. Find him at LinkedIn, Medium, and Dev.to.
Related Posts
Related Resources
Twilio Docs
From APIs to SDKs to sample apps
API reference documentation, SDKs, helper libraries, quickstarts, and tutorials for your language and platform.
Resource Center
The latest ebooks, industry reports, and webinars
Learn from customer engagement experts to improve your own communication.
Ahoy
Twilio's developer community hub
Best practices, code samples, and inspiration to build communications and digital engagement experiences.