Add new post about random names API
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
parent
62212201a7
commit
51c0ca17f1
|
@ -2,7 +2,6 @@
|
|||
title: 'Adding single click dev environment links to your open source project for easy contributions'
|
||||
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!'
|
||||
highlighted: true
|
||||
---
|
||||
|
||||
[I have been using DevContainers](/2023.02.10.why-use-devcontainer/) a lot. If
|
||||
|
|
178
src/routes/posts/2024.10.06.i-built-an-api-for-random-names.md
Normal file
178
src/routes/posts/2024.10.06.i-built-an-api-for-random-names.md
Normal 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.
|
Loading…
Reference in a new issue