
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.
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:
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.
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 intvar server stringvar message stringvar 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)
go run main.go -item_id 5go 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).