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'
|
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
|
||||||
|
|
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