# Fullstack application in Go with connection to PostgreSQL **To deploy your main application in Amverum, you need to follow these simple steps** 1. Open the page https://cloud.amverum.com/projects 2. Click the *Create* button and select *service type* **application** 3. We upload all the files (you can use git, or you can use the interface). Make sure you have downloaded all the necessary files. For the example project this is: - go.mod (necessarily) - go.sum (necessarily) - main.go (necessarily) - Dockerfile (necessarily) - static/index.html (if you use) - static/script.js (if you use) - static/styles.css (if you use) - amverum.yml (optional) 4. After this, the [build](https://docs.amverum.ru/applications/build.html) and [deployment](https://docs.amverum.ru/applications/run.html) application will begin. Wait for the “Successfully Deployed” status to appear. Let's take a closer look at the process. ## Let's create a simple web application in the Go programming language, where you can leave and read quotes. To store them we will use the PostgreSQL DBMS. The application directory has the following structure: ``` └─ code/ ├── static │ ├── styles.css │ ├── script.js │ └── index.html ├── amverum.yml ├── main.go ├── go.sum ├── go.mod └── Dockerfile ``` > The code for static files is available at the end of the page. ## Configuration ## Amverum.yaml You can write a yaml file yourself, or fill it out in the “Configuration” section of your personal account. **Example amverum.yml file when using only amverum.yml:** meta: environment: golang toolchain: name: go version: 1.22 build: image: golang:1.22 run: image: golang:1.22 persistenceMount: /data containerPort: 80 **Example amverum.yml file when used with Dockerfile:** meta: environment: docker toolchain: docker build: dockerfile: Dockerfile skip: false run: persistenceMount: /data containerPort: "80" > Important: save modified files (database, etc.) to the permanent storage /data. This will avoid their loss during reassembly. The Data folder in the code and the permanent storage /data are different directories. Let's consider an alternative way to set the configuration - through a Dockerfile. ## Dockerfile > Note: if you are using a Dockerfile, then in most cases you do not need to add the amverum.yml configuration file. > ### Steps: 1. Create a Dockerfile in the project directory. 2. In the Dockerfile we specify the base image called *builder*: `FROM golang:1.21.1 AS builder` > instead of 1.21.1 you can specify any other version that you need. 3. Set the working directory: `WORKDIR /app` 4. Copy the files *main.go*, *go.mod* and *go.sum* to the current directory of the working directory: `COPY main.go go.mod go.sum ./` 5. We build the project and create the executable file *server*: `RUN CGO_ENABLED=0 go build -a -installsuffix cgo -o server` 6. We use the Alpine Linux image (this will reduce the size of the final image and improve its performance): `FROM alpine:latest` 7. Copy the *server* executable file, compiled in the previous image, to the current directory of the working directory: `COPY --from=builder /app/server ./` 8. Copy static files inside the container: `COPY static/ ./static/` 9. Open port 80 for external connections: `EXPOSE 80` 10. Add a command to launch the application: `CMD ["./server", "--port", "80"]` **The resulting Dockerfile:** FROM golang:1.21.1 AS builder WORKDIR /app COPY main.go go.mod go.sum ./ RUN CGO_ENABLED=0 go build -a -installsuffix cgo -o server FROM alpine:latest COPY --from=builder /app/server ./ COPY static/ ./static/ EXPOSE 80 CMD ["./server", "--port", "80"] ## Dependencies (go.mod and sum.go) We initialize a new module for managing dependencies. To do this, run the following command, which will create the *go.mod* file: $ go mod init main > Note: instead of main you can write an arbitrary line All that remains is to run one more command, which will add two entries for each dependency and create a *go.sum* file: go mod tidy ## DBMS deployment(PostgreSQL) The database needs to be deployed as a separate application, and then you can connect to it from the main application. Detailed instructions are available at [link](https://docs.amverum.ru/databases/postgreSQL.html#postgresql). ## Creating a project in Amverum The last step is to deploy the application itself. The *main.go* file contains the main code and connects to the database. Don't forget to change the parameters for connecting to the database to those that you used in the previous step when creating the database on Amverum: - **user** = Username - **password** = User password - **dbname** = Name of the database being created - the **host** parameter can be found on the *Info* page of your PostgreSQL project (for example, amverum-username-cnpg-appname-rw) **main.go:** package main import ( "database/sql" "encoding/json" "log" "net/http" "strconv" _ "github.com/lib/pq" ) // Specify the values that you specified when creating the database on Amverum Cloud const ( host = "amverum-nskripko-cnpg-godb-rw" port = 5432 user = "nick" password = "href239" dbname = "godb" ) func main() { portStr := strconv.Itoa(port) dbinfo := "host=" + host + " port=" + portStr + " user=" + user + " password=" + password + " dbname=" + dbname + " sslmode=disable" db, err := sql.Open("postgres", dbinfo) if err != nil { log.Fatal(err) } defer db.Close() _, err = db.Exec(`CREATE TABLE IF NOT EXISTS quotes ( id SERIAL PRIMARY KEY, quote TEXT NOT NULL )`) if err != nil { log.Fatal(err) } http.HandleFunc("/quotes", func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } rows, err := db.Query("SELECT quote FROM quotes") if err != nil { log.Println("Error querying database:", err) http.Error(w, "Internal server error", http.StatusInternalServerError) return } defer rows.Close() var quotes []string for rows.Next() { var quote string if err := rows.Scan("e); err != nil { log.Println("Error scanning rows:", err) continue } quotes = append(quotes, quote) } json.NewEncoder(w).Encode(quotes) }) http.HandleFunc("/addquote", func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } var data struct { Quote string `json:"quote"` } if err := json.NewDecoder(r.Body).Decode(&data); err != nil { http.Error(w, "Bad request", http.StatusBadRequest) return } _, err := db.Exec("INSERT INTO quotes (quote) VALUES ($1)", data.Quote) if err != nil { log.Println("Error inserting quote into database:", err) http.Error(w, "Internal server error", http.StatusInternalServerError) return } w.WriteHeader(http.StatusCreated) }) fs := http.FileServer(http.Dir("static")) http.Handle("/", fs) log.Fatal(http.ListenAndServe(":80", nil)) } > :warning: The code is a demo example and we strongly do not recommend specifying the login and password for connecting to the database in the code. Use environment variables (secrets)! ## Functionality check 1. Go to the project settings and activate the domain name. 2. Now you can go to this URL and our application will open. If something does not work, we recommend that you read the Build and Application logs. Congratulations, you have successfully created your first application in Amverum! ## Code of static files from the example **static/index.html:** Quote Board

Quote Board

**static/styles.css:** body { font-family: Arial, sans-serif; } .container { max-width: 600px; margin: 50px auto; padding: 0 20px; } input[type="text"] { width: calc(100% - 80px); padding: 10px; margin-right: 10px; } button { padding: 10px 20px; background-color: #007bff; color: #fff; border: none; cursor: pointer; } button:hover { background-color: #0056b3; } #quoteList { margin-top: 20px; } **static/script.js:** document.addEventListener('DOMContentLoaded', () => { const quoteForm = document.getElementById('quoteForm'); const quoteInput = document.getElementById('quoteInput'); const quoteList = document.getElementById('quoteList'); // Function to fetch quotes from the server and display them const fetchQuotes = async () => { try { const response = await fetch('/quotes'); const quotes = await response.json(); // Clear previous quotes quoteList.innerHTML = ''; // Append new quotes to the list quotes.forEach(quote => { const quoteItem = document.createElement('div'); quoteItem.textContent = quote; quoteList.appendChild(quoteItem); }); } catch (error) { console.error('Error fetching quotes:', error); } }; // Fetch initial quotes when the page loads fetchQuotes(); // Submit quote form quoteForm.addEventListener('submit', async event => { event.preventDefault(); const newQuote = quoteInput.value.trim(); if (newQuote === '') { alert('Please enter a quote.'); return; } try { // Send the new quote to the server await fetch('/addquote', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ quote: newQuote }) }); // Clear the input field quoteInput.value = ''; // Fetch and display updated quotes fetchQuotes(); } catch (error) { console.error('Error adding quote:', error); } }); }); ```{eval-rst} .. admonition:: Important :class: warning Save database files and other changeable data to permanent storage to avoid losing them when updating the project when the code folder is “rolled back” to the state of the repository update. The data folder in the project root and the /data directory are different directories. You can check that the save is going to /data by going to the “data” folder on the “Repository” page. ``` ```{eval-rst} .. admonition:: Important :class: warning To avoid the 502 error, change host 127.0.0.1 (or similar localhost) to 0.0.0.0 in your code, and specify in the configuration the port that your application listens to (example - 8080). ``` ## If you are unable to deploy the project Write the symptoms you observe to support@amverum.com indicating your username and project name, we will try to help you.