diff --git a/go-postgres-react/README.md b/go-postgres-react/README.md new file mode 100644 index 00000000..8b307680 --- /dev/null +++ b/go-postgres-react/README.md @@ -0,0 +1,69 @@ +# go-postgres-react + +This is for creating a Contacts application with the following components: + +- [x] a Golang backend +- [x] a Postgresql database for saving data + -- the corresponding Volume for persistent storage +- [] a react frontend app for serving the UI + +## Create a volume and deploy postgresql + +```console +$ cd github.com/kraftcloud/examples/postgres + +github.com/kraftcloud/examples/postgres$ kraft cloud volume create --name postgres --size 200 + +github.com/kraftcloud/examples/postgres$ kraft cloud deploy --metro fra0 -M 1024 -e POSTGRES_PASSWORD=unikraft -e PGDATA=/volume/postgres -v postgres:/volume -p 5432:5432/tls . +(Search for `service group` in the output) + +# Port-forward postgresql to localhost +github.com/kraftcloud/examples/postgres$ kraft cloud tunnel 5432:5432 + + +$ psql -U postgres -h localhost +Password for user postgres: unikraft +psql (16.2) +Type "help" for help. + +postgres=# CREATE TABLE contacts (id SERIAL PRIMARY KEY, name TEXT, email TEXT); +CREATE TABLE +postgres=# + + +``` + +## Deploy the backend: + +```console +github.com/kraftcloud/examples/go-postgres-react/go$ kraft cloud deploy --metro fra0 -p 443:8080 . +(Search for `domain` in the output) + +# Ensure that the backend API works fine by talking to the db + +# Empty response first +$ curl -vv https://DOMAIN_FROM_ABOVE.fra0.kraft.host/contacts +null + +# Create contact next +$ curl -v https://DOMAIN_FROM_ABOVE.fra0.kraft.host/contacts -d '{ "name": "example1", "email": "one@example.com" }' + +# Ensure the new contact is saved and retrieved from db via the backend +$ curl https://DOMAIN_FROM_ABOVE.fra0.kraft.host/contacts +[{"name":"example1","email":"one@example.com"}] + +# Above backend API confirms data is saved, but let us check in DB also +$ psql -U postgres -h localhost +Password for user postgres: unikraft +psql (16.2) +Type "help" for help. + +postgres=# select * from contacts; + id | name | email +----+----------+----------------- + 1 | example1 | one@example.com +(1 row) + +postgres=# + +``` diff --git a/go-postgres-react/go/Dockerfile b/go-postgres-react/go/Dockerfile new file mode 100644 index 00000000..3aa4b6f1 --- /dev/null +++ b/go-postgres-react/go/Dockerfile @@ -0,0 +1,19 @@ +FROM --platform=linux/x86_64 golang:1.22.3-bookworm AS build + +WORKDIR /src + +COPY . /src/ + +RUN set -xe; \ + go build \ + -buildmode=pie \ + -ldflags "-linkmode external -extldflags -static-pie" \ + -tags netgo \ + -o /server server.go \ + ; + +FROM scratch + +COPY --from=build /server /server +COPY --from=build /lib/x86_64-linux-gnu/libc.so.6 /lib/x86_64-linux-gnu/ +COPY --from=build /lib64/ld-linux-x86-64.so.2 /lib64/ diff --git a/go-postgres-react/go/Kraftfile b/go-postgres-react/go/Kraftfile new file mode 100644 index 00000000..abe5d412 --- /dev/null +++ b/go-postgres-react/go/Kraftfile @@ -0,0 +1,7 @@ +spec: v0.6 + +runtime: base:latest + +rootfs: ./Dockerfile + +cmd: ["/server"] diff --git a/go-postgres-react/go/README.md b/go-postgres-react/go/README.md new file mode 100644 index 00000000..321519b9 --- /dev/null +++ b/go-postgres-react/go/README.md @@ -0,0 +1,20 @@ +# Simple Go 1.21 HTTP Server + +This is a simple HTTP server written in the [Go](https://go.dev/) programming language. + +To run this example on KraftCloud, first [install the `kraft` CLI tool](https://unikraft.org/docs/cli). +Then clone this examples repository and `cd` into this directory, and invoke: + +```console +kraft cloud deploy --metro fra0 -p 443:8080 . +``` + +The command will build and deploy the `server.go` source code file. + +After deploying, you can query the service using the provided URL. + +## Learn more + +- [Go's Documentation](https://go.dev/doc/) +- [KraftCloud's Documentation](https://docs.kraft.cloud) +- [Building `Dockerfile` Images with `Buildkit`](https://unikraft.org/guides/building-dockerfile-images-with-buildkit) diff --git a/go-postgres-react/go/go.mod b/go-postgres-react/go/go.mod new file mode 100644 index 00000000..09c0293f --- /dev/null +++ b/go-postgres-react/go/go.mod @@ -0,0 +1,5 @@ +module github.com/kraftcloud/examples/go-postgres-react/go + +go 1.22.3 + +require github.com/lib/pq v1.10.9 diff --git a/go-postgres-react/go/go.sum b/go-postgres-react/go/go.sum new file mode 100644 index 00000000..aeddeae3 --- /dev/null +++ b/go-postgres-react/go/go.sum @@ -0,0 +1,2 @@ +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= diff --git a/go-postgres-react/go/server.go b/go-postgres-react/go/server.go new file mode 100644 index 00000000..cbfab888 --- /dev/null +++ b/go-postgres-react/go/server.go @@ -0,0 +1,74 @@ +package main + +import ( + "database/sql" + "encoding/json" + "fmt" + "net/http" + + _ "github.com/lib/pq" +) + +type Contact struct { + Name string `json:"name"` + Email string `json:"email"` +} + +var db *sql.DB + +func main() { + var err error + db, err = sql.Open( + "postgres", + "user=postgres dbname=postgres password=unikraft host=postgres-wahr0.internal sslmode=disable", + ) + if err != nil { + panic(err) + } + + http.HandleFunc("/contacts", contactsHandler) + + fmt.Println("Listening on :8080...") + http.ListenAndServe(":8080", nil) +} + +func contactsHandler(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodGet: + rows, err := db.Query("SELECT name, email FROM contacts") + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer rows.Close() + + var contacts []Contact + for rows.Next() { + var c Contact + if err := rows.Scan(&c.Name, &c.Email); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + contacts = append(contacts, c) + } + + json.NewEncoder(w).Encode(contacts) + case http.MethodPost: + var c Contact + json.NewDecoder(r.Body).Decode(&c) + + _, err := db.Exec( + "INSERT INTO contacts(name, email) VALUES($1, $2)", + c.Name, + c.Email, + ) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + json.NewEncoder(w).Encode(c) + default: + http.Error(w, "Invalid request method", http.StatusMethodNotAllowed) + } +}