Skeleton Fullstack CLI

May 7, 2025 · Tyler Yeager · 1 min reading time

Featured Image of blog post

In my previous post, I mentioned I would have a template for a minimal server side and client side app. Feel free to extend or reduce this as needed. It’s meant to help you jump start on creating a small program.

We’re going to use the following tech stack

Backend:

  • TypeScript
  • Hono Server

Frontend:

  • Golang

Backend

This basic, practical typescript template will allow the following:

  • Require an app secret a . Only your application will be able to interact with it. Most services provide some way of storing secure variables. You should use them.
  • Grab a query parameter i called item_id. If it doesn’t have it, it will return a JSON response with a failure code.
  • Load a structured JSON body request d defined under jsonBody.

Note that n the import is a little strange. We are using val.town to import the package. Depending on how you are hosting this code (self, or with a service), this part might change somewhat. Make sure to check the documentation.

app.ts
import { Hono } from "npm:hono";
const app = new Hono();
const APP_SECRET = "5fdd613b60ff";
type jsonBody = {
data: object;
};
app.get("/", c => {
return c.text("Hello World");
});
app.post("/server", async c => {
const authHeader = c.req.header("Authorization");
console.log({ authHeader });
if (!authHeader || authHeader.split(" ")[1] !== APP_SECRET) {
c.status(401);
return c.json({
status: "failure",
message: "Not Authorized",
});
}
const itemId = c.req.query("item_id");
if (!itemId) {
c.status(400);
return c.json({
status: "failure",
message: "Missing item_id query parameter",
});
}
const body: jsonBody = await c.req.json();
return c.json({
status: "success",
message: `processed item_id ${itemId}`,
data: body.data,
});
});
export default app.fetch;

To do a quick test of this:

Terminal window
SERVER="https://basic-express-backend.val.run"
TOKEN="5fdd613b60ff"
curl --request POST \
--url "$SERVER/server?item_id=5" \
--header "Authorization: Bearer $TOKEN" \
--header 'Content-Type: application/json' \
--data '{
"data": {
"hello": "world"
}
}'

Frontend

This golang template will do the following:

  • Define structure s for the request and response
  • Provide multiple flags f you can use to specify the item id, server and auth code, with defaults.
  • Set query parameters q , such as item_id. You can modify, remove or create more.
  • Set http headers h . This allows authorization and specifying content types.
  • The request j will use the RequestBody as a POST request.

Feel free to strip out sensitive data d and require providing those flags if you’d like. If you don’t need a part of this, such as query params, just delete it.

main.go
package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"io"
"net/http"
"strconv"
)
const defaultUrl = "https://basic-express-backend.val.run/server"
const defaultAuthToken = "5fdd613b60ff"
type RequestBodyData struct {
Hello string `json:"hello"`
}
type RequestBody struct {
Data RequestBodyData `json:"data"`
}
type ResponseBodyData struct {
Hello string `json:"hello"`
}
type ResponseBody struct {
Status string `json:"status"`
Message string `json:"message"`
Data ResponseBodyData `json:"data"`
}
var item int
var server string
var message string
var authToken string
func main() {
flag.IntVar(&item, "item", 0, "item number")
flag.StringVar(&server, "server", defaultUrl, "server url")
flag.StringVar(&authToken, "auth_token", defaultAuthToken, "auth token")
flag.StringVar(&message, "message", "hello world", "message string")
flag.Parse()
requestBody := RequestBody{
Data: RequestBodyData{
Hello: message,
},
}
jsonBody, _ := json.Marshal(requestBody)
bufferedJsonBody := bytes.NewBuffer(jsonBody)
req, err := http.NewRequest("POST", server, bufferedJsonBody)
if err != nil {
3 collapsed lines
fmt.Println("Error creating request:", err)
return
}
q := req.URL.Query()
q.Add("item_id", strconv.Itoa(item))
req.URL.RawQuery = q.Encode()
req.Header = http.Header{
"Content-Type": {"application/json"},
"Authorization": {"Bearer " + authToken},
}
client := http.Client{}
resp, err := client.Do(req)
if err != nil {
3 collapsed lines
fmt.Println("Error sending request:", err)
return
}
defer func(Body io.ReadCloser) {
7 collapsed lines
if Body != nil {
err = Body.Close()
}
if err != nil {
fmt.Println("Error closing body", err)
}
}(req.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
3 collapsed lines
fmt.Println("Error reading response body:", err)
return
}
if resp.StatusCode == http.StatusOK {
var response ResponseBody
err = json.Unmarshal(body, &response)
fmt.Println(response.Message, response.Data.Hello)
return
} else {
fmt.Println(string(body))
return
}
}

Example

So how do you run it? Copy the main.go code into your own main.go file and run it (or build it)

Terminal window
go run main.go -item_id 5
go build -o skeleton_fullstack_cli main.go
./skeleton_fullstack_cli -item_id 5

If you use the default example for the frontend code, it will work. I have a demo backend running (for now).

Did you find this interesting?

Consider subscribing 😊

No AI-generated content or SEO garbage.

Unsubscribe anytime