Openstatus
www.openstatus.dev
1---
2title: Introducing the GoaT stack
3description:
4 A full-stack app template featuring a Golang server and a Vite + React SPA front end.
5author:
6 name: Thibault Le Ouay Ducasse
7 url: https://bsky.app/profile/thibaultleouay.dev
8 avatar: /assets/authors/thibault.jpeg
9publishedAt: 2025-03-24
10image: /assets/posts/introducing-goatstack/goat.png
11tag: engineering
12---
13
14## What is the GoaT stack?
15
16The GoaT stack is a [full-stack app template](https://github.com/openstatusHQ/goat-stack) featuring a Golang server and a Vite + React SPA front end.
17
18We want the GoaT stack to have a great DX.
19
20### What is included in the GoaT stack?
21
22**[SQLite](https://www.sqlite.org/)**: We have chosen SQLite as the database because we love it. It’s just a file.
23**[LiteStream](https://litestream.io/)**: To backup our database
24
25
26**[Protocol Buffer with ConnecTRPC](https://connectrpc.com/)**: For their http based protocol that works with Protocol Buffer.
27
28**[Chi](https://go-chi.io)**: For our Golang router\
29**[connectrpc.com/connect](https://connectrpc.com/connect)**: To handle http based request
30
31
32**[React](https://react.dev/)**: Our frontend framework of choice.\
33**[TanStack Query + Connect-Query](https://github.com/connectrpc/connect-query-es)**: to generate our typesafe query.\
34**[TanStack Router](https://tanstack.com/router/latest)**: The typed router is a joy to work with.
35
36
37
38### API: Schema First Approach
39
40Using Protocol Buffer we can design our API in `proto` file
41
42```proto
43syntax = "proto3";
44
45package goat.v1;
46
47enum Vote {
48 YES = 0;
49 NO = 1;
50}
51
52message VoteRequest {
53 Vote Vote = 1;
54}
55message VoteResponse {
56 bool Success = 1;
57}
58```
59
60### Why not going full stack TypeScript
61
62
63<a href="https://bsky.app/profile/did:plc:eu6cezqsf5yocjsyc7mgkued/post/3lkeag64a2s2x"><Image
64 alt="New users per week"
65 src="/assets/posts/introducing-goatstack/bsky-post.png"
66 width={650}
67 height={575}
68/></a>
69
70
71We have worked in a large monorepo, we have often been frustrated by the number of times we had to restart our TypeScript LSP.
72
73
74
75
76We love Golang. We heavily rely on it for OpenStatus.
77
78## How to get started with the Goat Stack
79
80
81### Requirements
82
83To get started you need to have these installed on your computer
84
85- [Just](https://just.systems)
86- [Golang](https://go.dev/)
87- [pnpm](https://pnpm.io)
88- [Node](https://nodejs.org/en)
89
90
91
92### Get Started
931. First clone our repository : [https://github.com/openstatusHQ/goat-stack](https://github.com/openstatusHQ/goat-stack)
94
952. Run
96```bash
97just init
98```
99
100It will download all the dependancies.
101
1023. Open your IDE and update `packages/proto/goat/v1/goat.proto`
103Add new procedure in the GoatService
104
105```proto
106service GoatService {
107 rpc GetVotes(GetVotesRequest) returns (GetVotesResponse) {}
108 rpc Vote(VoteRequest) returns (VoteResponse) {}
109}
110
111message GetVotesRequest {}
112
113message GetVotesResponse {
114 int64 Yes = 1;
115 int64 No = 2;
116}
117
118```
119
1204. Run `just buf`
121
1225. Implement the handler in the server `apps/server/internal/goat/handler.go`
123
124```go
125func (h *goatHandler) Vote(ctx context.Context, req *connect.Request[goatv1.VoteRequest]) (*connect.Response[goatv1.VoteResponse], error) {
126 tx := h.db.MustBegin()
127 var value string
128 switch req.Msg.Vote {
129 case goatv1.Vote_YES:
130 value = "yes"
131 break
132 case goatv1.Vote_NO:
133 value = "no"
134 break
135 default:
136 break
137 }
138 r := tx.MustExec("INSERT INTO vote (timestamp, vote) VALUES ($1, $2)", time.Now().Unix(), value)
139 tx.Commit()
140 res := connect.NewResponse(&goatv1.VoteResponse{
141 Success: true,
142 })
143 return res, nil
144}
145```
146
1476. Start calling it in your React App with the newly generated query
148
149```tsx
150// Our wrapper around tanstack query
151import { useMutation } from "@connectrpc/connect-query";
152import { createFileRoute, Link, useRouter } from "@tanstack/react-router";
153// Our generated query
154import { vote } from "../gen/proto/goat/v1/goat-GoatService_connectquery";
155// Our generated types
156import { Vote } from "../gen/proto/goat/v1/goat_pb";
157import { Button } from "@goat/ui/components/button";
158
159export const Route = createFileRoute("/")({
160 component: App,
161});
162
163function App() {
164 // Use the mutation hook with our generated query
165 const v = useMutation(vote);
166 const { navigate } = useRouter();
167 return (
168 <div>
169 <div >
170 <p>Is this the 🐐 stack?</p>
171 <div>
172 <Button
173 variant={"outline"}
174 disabled={v.isPending}
175 onClick={async () => {
176 await v.mutateAsync({
177 Vote: Vote.YES,
178 });
179 navigate({ to: "/results" });
180 }}
181 >
182 Yes
183 </Button>
184 <Button
185 variant={"outline"}
186 disabled={v.isPending}
187 onClick={async () => {
188 await v.mutateAsync({
189 Vote: Vote.NO,
190 });
191 navigate({ to: "/results" });
192 }}
193 >
194 No
195 </Button>
196 </div>
197 </div>
198 </div>
199 );
200}
201
202````
203
204
205### How to deploy it.
206
207We provide 2 docker files to deploy the dashboard and the server where you want. For example our server for [GoatStack.dev](https://GoatStack.dev) is hosted on [Koyeb](https://www.koyeb.com/) and our dashboard on [Cloudflare](https://cloudflare.com/)
208
209Before deploying the server you need to update `apps/server/etc/litestream.yml` with your S3 compatible bucket key to backup your database.
210
211```yaml
212dbs:
213 - path: /data/db
214 replicas:
215 - type: s3
216 endpoint: https://${CLOUDFLARE_R2_ACCOUNT_ID}.r2.cloudflarestorage.com/
217 bucket: goat-stack
218 access-key-id: ${CLOUDFLARE_R2_ACCESS_KEY_ID}
219 secret-access-key: ${CLOUDFLARE_R2_SECRET_ACCESS_KEY}
220```
221
222
223### Conclusion
224
225We hope you will enjoy using the GoaT stack as much as we do. We are looking forward to seeing what you will build with it. Feel free to reach out to us on [ping@openstatus](mailto:ping@openstatus.dev?subject=GoatStack) if you have any questions or feedback.
226
227And if you want to contribute to the GoaT stack, we would be more than happy to welcome you to our community.
228
229And create a free [OpenStatus account](/app/login) to monitor your server and get notified if something goes wrong.
230
231Happy coding! 🐐