Cover Page

Backend Page

Go with Echo

chatterd

Change to your chatterd directory and edit the file main.go:

server$ cd ~/reactive/chatterd
server$ vi main.go

Add the following import lines inside your import() block:

  "context"

	"github.com/jackc/pgx/v4/pgxpool"

Continuing in main.go, find the global variable router and add two routes right after the route for /llmprompt. These serve Chatter’s two APIs: HTTP GET request, with URL endpoint getchatts/, and HTTP POST request, with endpoint postchatt/. With each endpoint, we also specify which HTTP method is allowed for that URL endpoint and its handler function:

    {"GET", "/getchatts/", getchatts},
    {"POST", "/postchatt/", postchatt},

The functions getchatts() and postchatt() will be implemented in handlers.go.

Staying in main.go, set up a pool of open connections to the PostgreSQL chatterdb database. Maintaining a pool of open connections avoids the cost of opening and closing a connection on every database operation. The password used in creating pgxpool must match the one you used when setting up Postgres earlier. The connection pool needs to be accessible to all the URL handlers, so we store it in a global variable chatterDB:

var background = context.Background()
var chatterDB *pgxpool.Pool

Then in the main() function, add these lines right after the function signature:

	var err error
	chatterDB, err = pgxpool.Connect(background, "host=localhost user=chatter password=chattchatt dbname=chatterdb")
	if err != nil { panic(err) }

We’re done with main.go. Save and exit the file.

handlers.go

Edit handlers.go:

server$ vi handlers.go

First add the following import to the import() block:

    "time"

We define a Chatt struct to help postchatt() deserialize JSON received from clients. Add these line right below the import() block:

type Chatt struct {
    Username  string    `json:"username"`
    Message   string    `json:"message"`
    Id        string    `json:"id"`
    Timestamp time.Time `json:"timestamp"`
}

The property names must be capitalized to be visible to the JSON deserializer.

Now add a number of logging functions that, in case of error, prepare a corresponding JSON response to be returned to the client:

func logServerErr(c echo.Context, err error) error {
	log.Println("[Echo] |", http.StatusInternalServerError, `|`, c.RealIP(), `|`, c.Request().Method, c.Request().RequestURI, err.Error())
	return c.JSON(http.StatusInternalServerError, err.Error())
}

func logClientErr(c echo.Context, sc int, err error) error {
	log.Println("[Echo] |", sc, `|`, c.RealIP(), `|`, c.Request().Method, c.Request().RequestURI, err.Error())
	return c.JSON(sc, err.Error())
}

The handler getchatts() uses an open connection from the connection pool to query the database for stored chatts. Once all the rows from the database are retrieved, we insert the resulting array of chatts into the response JSON object. Add getchatts() to your handlers.go after the definition of the llmprompt() function.

func getchatts(c echo.Context) error {
    var chattArr = [][]any{}
    var chatt Chatt

    rows, err := chatterDB.Query(background, `SELECT username, message, id, time FROM chatts`)
    if err != nil {
      if rows != nil { rows.Close() }
		  return logServerErr(c, err)
    }

    for rows.Next() {
        err = rows.Scan(&chatt.Username, &chatt.Message, &chatt.Id, &chatt.Timestamp)
        if err != nil {
          rows.Close()
          return logServerErr(c, err)
        }        
        chattArr = append(chattArr, []any{chatt.Username, chatt.Message, chatt.Id, chatt.Timestamp})
    }

    logOk(c)
    return c.JSON(http.StatusOK, chattArr)
}

Similarly, postchatt() receives a posted chatt in the expected JSON format, deserializes into the Chatt struct, and inserts it into the database, using a connection from the pool. The UUID and time stamp of each chatt are generated at insertion time. Add postchatt() to the end of your handlers.go:

func postchatt(c echo.Context) error {
    var chatt Chatt

    if err := c.Bind(&chatt); err != nil {
      return logClientErr(c, http.StatusUnprocessableEntity, err)
    }

    _, err := chatterDB.Exec(background, `INSERT INTO chatts (username, message, id) VALUES ($1, $2, gen_random_uuid())`, chatt.Username, chatt.Message)
    if err != nil {
        return logClientErr(c, http.StatusBadRequest, err)
    }

    logOk(c)
	  return c.JSON(http.StatusOK, struct{}{}) // empty struct instance serialized to empty JSON: {}
}

We’re done with handlers.go. Save and exit the file.

Build and test run

To build your server:

server$ go get   # -u  # to upgrade all packages to the latest version
server$ go build

:point_right:Go is a compiled language, like C/C++ and unlike Python, which is an interpreted language. This means you must run go build each and every time you made changes to your code, for the changes to show up in your executable.

To run your server:

server$ sudo ./chatterd
# Hit ^C to end the test

You can test your implementation following the instructions in the Testing Chatter APIs section.

References

</details> </div>


Prepared by Sugih Jamin Last updated July 25th, 2025