I recently created my very own cryptocurrency exchange aggregator called cyphergoat it finds you the best rate to swap your crypto from different partnered exchanges.
It has two parts:
- An API that interacts with exchanges. Written in go and uses gin
- The Web UI that is written in go and uses a combination of HTML, HTMX, tailwindcss, CSS and Javascript in templ templates. Aka the GoTTH stack. It interacts with the api in order to find rates etc.
What is extremely cool with this stack and setup is that we are able to produce a single binary with everything included for each part and ship it to the server. On the webui side this is possible since the html is compiled into go code using templ and then shipped with the binary.
In this article I will be going through my setup to make it easier for you to make something like this.
Setup
I am using a debian 12 server which will expose my application via cloudflare tunnels. All of the static files are being served via nginx and the api and website binaries are ran as systemd services.
In this guide I will show you how I set this up.
The setup
I have a single folder on my dev machine called cyphergoat: It contains
api/
web/
builds/
The api folder houses the api source code. The web the website source code.
And the builds houses all of the builds that are deployed to the server.
Tailwind
The first real challenge comes with setting up tailwindcss correctly.
In my web project I have a static folder specifically for static files. Inside of it I have two files
/web
styles.css
tailwind.css
The styles.css
simply contains:
@import "tailwindcss";
The tailwind.css file is where tailwind-cli will save it’s stuff.
To build the tailwind stuff I simply run
npx @tailwindcss/cli -i ./static/styles.css -o ./static/tailwind.css --watch
(assuming you have tailwind-cli installed)
In my header.templ file (the header of all the pages) at the top I have
<link href="/static/tailwind.css" rel="stylesheet">
<link href="/static/styles.css" rel="stylesheet">
And the files are being served using echo’s e.Static (in my main.go file )
func main(){
e := echo.New()
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.Use(middleware.Secure())
e.Static("/static", "static") // Serves content from static folder.
// Rest of the handlers
}
Server
On my server side I have a debian 12 vm running on proxmox.
In my users home directory I have a folder with the following contents:
cyphergoat/
├── api
├── static/
└── web
The static folder contains all of the static files (including tailwind.css and styles.css) and the web and api are the binaries.
I then have two systemd services for these exectuables:
The cg-api.service
/etc/systemd/system/cg-api.service
[Unit]
Description=CypherGoat API
After=network.target
[Service]
User=arkal
Group=www-data
WorkingDirectory=/home/arkal/cyphergoat
ExecStart=/home/arkal/cyphergoat/api
Restart=always
RestartSec=1
[Install]
WantedBy=multi-user.target
And cg-web.service
/etc/systemd/system/cg-web.service
[Unit]
Description=CypherGoat Web
After=network.target
[Service]
User=arkal
Group=www-data
WorkingDirectory=/home/arkal/cyphergoat
ExecStart=/home/arkal/cyphergoat/web
[Install]
WantedBy=multi-user.target
Both are owned by the group www-data
(this is probably not necessary for the api), in order to make it easier to serve them via nginx.
Nginx
The website is communicating with the api but I still need to make the web-ui accessible.
I have setup an nginx site with the following configuration:
/etc/nginx/sites-available/cg
server {
server_name cyphergoat.com;
location / {
proxy_pass http://127.0.0.1:4200;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /static/ {
alias /var/www/static/;
expires 30d;
}
# Optional robots.txt
location = /robots.txt {
root /var/www/static;
access_log off;
log_not_found off;
}
listen 80;
}
I have also setup certbot to have an ssl cert.
You can setup certbot by running:
sudo apt install certbot python3-certbot-nginx -y
Generate the ssl cert:
sudo certbot --nginx -d cyphergoat.com
Read Self host your own website for a more in depth nginx setup.
Cloudflare Tunnels
I am currently making my website accessible using cloudflare pages. It is an extremely easy to use port forwarding solution
To do this you will need a cloudflare account and a domain pointed to cloudflare.
First head to the Zero Trust Dashboard
Under Networks
click on Tunnels
and then Create a tunnel
Once created you should Install and run a connector
, follow the instructions on the page for your specific setup.
After the connector is running you should click on the Public Hostname
tab and Add a public hostname
.
Now you should see something like this:
Fill in the info as I have.
The service type should be HTTP
and the url should be 127.0.0.1:80
or localhost:80
Obviously there is no reason to make your api publicly accessible when deploying your website.
Deployment
In order to deploy my binaries I went ahead and created a quick bash script:
cd api
go build -o ../builds/ .
cd ../web
templ generate && go build -o ../builds/web cmd/main.go
cd ..
rsync -urvP ./builds/ user@SERVER:/home/user/cyphergoat
rsync -urvP ./web/static user@SERVER:/home/user/cyphergoat/
rsync -urvP ./api/coins.json user@SERVER:/user/user/cyphergoat/
The script will build the api, generate the templ files and build the webui and then sends everything over to my server (including the static folder)
I then ssh into my server
ssh user@ip
and then restart the services
sudo systemctl restart cg-api cg-web
And that’s it.
Join my free newsletter!
Related Articles
Simple Rate Limiting in Go (Gin)