Add new post about random names API
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
Kaan Barmore-Genç 2024-10-06 15:42:13 -05:00
parent 62212201a7
commit 51c0ca17f1
2 changed files with 178 additions and 1 deletions

View file

@ -2,7 +2,6 @@
title: 'Adding single click dev environment links to your open source project for easy contributions' title: 'Adding single click dev environment links to your open source project for easy contributions'
date: 2024-05-27T05:10:06Z date: 2024-05-27T05:10:06Z
description: 'See how you can streamline contributions and onboarding by leveraging DevContainers and cloud dev environments - a one-click setup that lets new folks dive right in!' description: 'See how you can streamline contributions and onboarding by leveraging DevContainers and cloud dev environments - a one-click setup that lets new folks dive right in!'
highlighted: true
--- ---
[I have been using DevContainers](/2023.02.10.why-use-devcontainer/) a lot. If [I have been using DevContainers](/2023.02.10.why-use-devcontainer/) a lot. If

View file

@ -0,0 +1,178 @@
---
title: 'I built an API for random names and numbers because why not?'
date: 2024-10-06T14:18:00Z
description: 'So I needed random names for a script, and instead of using a sensible solution, I decided to build an entire API. Join me as I fumble through Go, preact, and Kamal, then end up with a functional free public API.'
highlighted: true
---
I was recently writing up a script for... something. I honestly can't quite
remember anymore, but I needed to generate random names in that script. Think
something like how Github will assign random names to Codespaces like `upgraded giggle`.
You can always just call `openssl rand -hex 12` and get a random string,
but memorable phrases are always better.
It might not be everyday that you need to read out a container name or remember
a URL off the top of your head, but when you do it helps out so much to have a
phrase than a random sequence of letters and numbers.
Importantly for this case, I was writing a basic script that couldn't assume
much about what is installed or present on the system, and I didn't want to pull
in any extra files or bloat the script with a large dictionary of words. So I
thought it would be a smart idea to call a public API to get a random name
instead!
Simple public APIs like [ipify.org](https://www.ipify.org/) are always a life
saver when you're writing up a small script and you just need something real
quick. But I couldn't find one for generating random words or names.
So I decided to take this up and build one myself.
## Building the API
I've been wanting to learn Go for a while, partly because my new job uses Go in
some parts. Nothing I work on right now, but I thought it would be useful in
case something did come up. And besides, I've been looking for something
in-between NodeJS and Rust in the tradeoff of developer experience and
performance, and thought Go might strike a good balance.
I ended up building the service with [Fiber](https://gofiber.io/). I primarily
looked up [a benchmark for requests per second](https://web-frameworks-benchmark.netlify.app/result?asc=0&l=go&metric=totalRequestsPerS&order_by=level64),
and Fiber stood out as the top option that is actively maintained and well
documented.
Playing around with that benchmark, you can see there are other frameworks that
excel in other tasks, but I primarily care about requests per second since that
is going to ultimately decide how much load the API can handle, and stuff
like response latency is not particularly important.
### So how was Go?
Building the API took very little time. I am extremely impressed with Go in that
regard. Sure, the constant `if err != nil` checks do get tedious, but unlike
when I was using Rust I never had to grapple with incomprehensible compiler
errors. That's definitely an unfair comparison for Rust, and there are of course
cases where Go's garbage collector is going to be an unacceptable tradeoff. But
building APIs is my use case for these languages.
I was pleasantly surprised with the standard library
[embed](https://pkg.go.dev/embed) functionality, which allows you to bundle
resources into the program itself. I think this is the way to go for all small
projects where the backend/API is serving the UI. You can always put a caching
proxy/CDN in front of the backend, and it simplifies your deployments since you
now always have a matching version of the UI and backend combined bundled
together into a single file or container.
The main thing I didn't like was the error handling. I think returning errors
from functions is a good call, but requiring explicit checks for them makes the
code noisy. I wish there was an equivalent of Rust's `?` operator that allows
you to easily bubble up errors, which is what you often want. In an API, it's
very natural to handle errors at the API level, so you can always bubble the
error up and then turn it into a 400 or 500 response at a higher level. Or if
there was support for compiler macros, so someone could build this support into
Go.
I did find the amazing [Mo](https://github.com/samber/mo), which brings Monads
to Go. With support for `Option` and `Result`, it does everything I want and
more. I didn't end up really using it, but I could see that being a better
option than passing `nil`s around.
And that is one thing I was really disappointed with Go, `nil`. At least make it
strongly typed so it's obvious what is nullable and what is not!
## The docs
For documenting the API, I decided to build something myself. I knew I wanted an
interactive doc, and felt that I could have some fun with it. So I built a site
using [preact](https://preactjs.com/). preact itself is okay, not much to talk
about. It's basically react. But one thing I immediately fell in love with was
[signals](https://preactjs.com/guide/v10/signals/). It's a beautiful solution,
allowing you to handle both global and local state with ease.
Then I put that together with [TailwindCSS](https://tailwindcss.com/) and
[DaisyUI](https://daisyui.com/). These are my go-to picks nowadays, it makes
building a decent looking interface really quickly. The whole thing is then
bundled with [Vite](https://vite.dev/).
## Deploying
Deployment was an interesting question too. In the past I have usually done it
manually. I also dabbled with ansible a bit, but never used it to deploy stuff.
But I have gotten really used to automated deployments at work, so I wanted
something similar for this project. At the same time, I didn't want to just
throw the project up on a serverless hosting solution, because I'm building a
free public API here and I don't want to wake up to a million-dollar bill one
day.
Thanks to some lovely feedback I got on the Fediverse, I found out about Kamal though.
<iframe src="https://fosstodon.org/@r1w1s1/113213653122833711/embed" class="mastodon-embed" style="border: 0" width="100%" height="685px"></iframe><script src="https://fosstodon.org/embed.js" async="async"></script>
Setting up Kamal was in some ways easier than I expected. It did take me a few
attempts of fiddling with CI and Tailscale, but I eventually got it working.
I have Tailscale running on the small VPS hosting the API. I use the Tailscale
Github Action to temporarily authenticate the CI runner with Tailscale.
```yml
# In a Github Actions workflow:
# ...
- name: Tailscale
uses: tailscale/github-action@v2
with:
oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }}
oauth-secret: ${{ secrets.TS_OAUTH_SECRET }}
tags: tag:ci
- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.3'
- name: Deploy
run: |
gem install kamal
kamal deploy
```
I enabled Tailscale SSH on the VPS, then configured the access controls so the
CI runners can authenticate with the VPS without messing around with SSH keys.
```json
{
"tagOwners": {
// Temporary CI runners
"tag:ci": ["autogroup:admin"],
// Devices that can receive deploys from CI
"tag:target": ["autogroup:admin"],
},
"acls": [
// ...
// CI has access to CI targets
{
"action": "accept",
"src": ["tag:ci"],
"dst": ["tag:target:*"],
},
],
"ssh": [
{
"action": "accept",
"src": ["tag:ci"],
"dst": ["tag:target"],
"users": ["root"],
},
],
}
```
And that was about it. After following the instructions, Kamal deployed
everything and even handled getting an SSL certificate for me. This API is now
up and running at [rnd.bgenc.dev](https://rnd.bgenc.dev), which is a new domain
I recently bought for any small projects I build like this.
As a side note, I now own `bgenc.com`, `.net`, and `.dev`! I'm really excited
because someone was squatting `bgenc.com` for a while, but it finally became
available a couple months ago and I grabbed it instantly. I wasn't even
checking, but I got an email for some scam offering to help me get .com and .org
because they were becoming available soon. I checked the domain and tada, it in
fact was abandoned. After refreshing the domain registry's page for days, I
grabbed it as soon as I could.