Openstatus www.openstatus.dev
at 40ee67dc9bbbb4d39796e1b7e8b2ae17c61dd77e 231 lines 6.2 kB view raw
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. Its 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! 🐐