Full stack monolith experiment with Golang

Full stack monolith experiment with Golang

I recently built a project called "Scriptables" to help me deploy servers fast (an open-source Laravel forge alternative) and prevent me from doing the same mundane stuff over and over again.

It's not the most comprehensive server builder around (well yet anyway), but a good enough starting point, an MVP that I can use to quickly spin up Ubuntu servers. I mostly use the tool to spin up LAMP-type servers but have also spun up some Django servers with it as well.

I have never used Golang for a web app, my general use cases for Golang are small to medium background jobs, terminal tools, and scraping, thus this was an interesting experiment.

Why a Laravel forge alternative anyway?

Laravel Forge is a handy platform no doubt, and does a whole lot more than Scriptables, for sure.

Still, it's some third-party platform having access to my SSH keys and servers. I am sure they take every measure possible to protect their client's data, but still, I'm more comfortable having my instance locked behind a VPN or server that's not publicly available.

Furthermore, I am okay with writing BASH. Scriptables is essentially made up of a bunch of modular BASH scripts, it's fairly easy to customize and build any kind of server you like, including Django app servers, Next.js app servers, and various other types.

Finally, the GUI gives me a lot more flexibility to build out functionality that I need vs using an off-the-shelf product like Laravel Forge.

Why Golang for a webapp?

Minimalism, web frameworks in general are very bloated and require a ton of dependencies. I like the Golang ecosystem, you don't need too many third-party packages, and the language itself is super compact and easy to write.

It feels poetic to me, especially using structs as types. They are lean, plus you get nice code formatting and the IntelliSense in vscode is really amazing. I feel so productive, compared to Laravel where I have to jump around the filesystem, use composer packages, and have to deal with all the weird quirks of PHP.

Apart from the awesome developer experience, the whole Golang ecosystem is really efficient and fast, you literally run "go build" and get a single binary that can be run anywhere without the need for external dependencies, you don't even need Golang installed in production.

Pulling in just the packages I need

While the standard library took me quite far, there came a time when I needed a little bit more.

The MySQL adapter was okay but a nice ORM would be great, this is when I reached for GORM - it's such a beautiful and easy package to work with.

You can combine structs with GORM, similar to Laravel's Eloquent. Further, you can also run raw SQL - so kinda like the best of both worlds.

Here's an example:

var book Book
db.First(&book)

# Where
db.Where("isbn = ?", "xyz").First(&book)

# Raw Query
var totalSales int64
db.Raw(`
SELECT SUM(price) FROM orders WHERE category_id = ?`,
5).Scan(&totalSales)

Next, I added more and more routes, it was becoming a bit cumbersome - so I reached for GIN. GIN is a very lightweight web framework for Golang. In GIN you can declare routes similar to the below:

router.POST("/server/firewall/delete/rule", controller.DeleteFirewallRule)
router.POST("/server/firewall/add/rule", controller.AddFirewallRule)
router.GET("/sshkeys", controller.SshKeys)

Apart from these 2, and db drivers - there are not too many other packages that I have used.

What features does my app have?

It took me roughly 3 months, 1 to 3 days a week to work on this project whenever I had a bit of free time, which when I look back really shows how productive Golang can be. Here are the features I built:

  • Full authentication: login, logout, password reset, detect and ban too many failed attempts.

  • Teams functionality: invite and manage other teammates.

  • Email functionality: sends out registration emails, forgot password, and so on.

  • SSH jobs: jobs that will provision servers including setting up SSH keys, MySQL, Laravel, PHP, and so forth.

  • CRON job management: Set up and sync CRONs from the GUI.

  • Firewall management: add and delete firewall rules from the GUI.

  • Extended GIN templates to use a jinja2 package.

  • 2Factor auth - I used a 3rd party library but still needed to implement the flow.

... And the list goes on.

Conclusion

Golang is quite capable of building a full monolith backend, with just a handful of hand-picked packages - you can reach the same level of productivity you would achieve in any of the major frameworks.

Go's not perfect though, there are some areas it falls short in and you have to implement your own solution:

  1. Laravel and Django templates are awesome to work with. There's nothing as feature-complete as these two in Golang. Nowadays, most are using a frontend framework like React or Vue - so this is probably not a major issue.

  2. There are no established guidelines. In Django and Laravel, every project has the same sort of structure. Golang on the other hand can vary depending on the team, developer, or company.

  3. Some plumbing stuff would be great. Implementing CSRF in GIN was a bit annoying, this should just come standard. Auth scaffolding, forgot password - that kind of thing would be nice too.

Overall I had fun experimenting with Golang, not sure if I will use it again for bigger projects but for small to medium web apps - why not?