GoCRUD
Hub
goCRUD

The API #3 REST Server

REST Api 3

There seems to be no common ground for creating a REST Api. Using Go is no exception. My goal is to create a maintainable, fast and simple REST Api. Below I describe my thoughts.

1. Using HTML Form when inserting records.
This step is traditional HTML/CSS/Javascript. The forms could be created on-the-fly by using Javascript.
2. Creating JSON direct from the Form
There is a possibility to read the content from a form directly into JSON. Ready for inserting in Postgresql. Here is the Javascript code:
var myForm = document.getElementById('form');
myForm.addEventListener('submit', function(event) {
  var formData = new FormData(myForm)
  var object = {};
  formData.forEach(function(value, key) {
    object[key] = value;
  });
  var json = JSON.stringify(object)
  alert(json)
});
3. REST URL Naming Convention

URL Naming and Requesting is a challenge. The normal REST URL naming convection? I decided to do my own naming 3 part principle:

https://api.go4webdev.org/scope/action/value

Where scope normally correspond to a SQL table, but also can refer to a function with many joined tables. The action part is normally what you want to do. And last one or several values

The stored SQL query is a combination of scope and action. So /user/id/1 is stored as user_id in the SQL lookup database.
4. Send AJAX URL Request to the API-server

The request can be submitted from Go or from AJAX in the browser. Using AJAX there will be less flickering, as not the whole page is rendered with a partial update. I have found it equal speed to Go, so there is no reason for another trip to the backend.

function api3get(path) {
  var url = "https://api3.go4webdev.org/"+path;
  var xhr = new XMLHttpRequest();
  xhr.open("GET", url);
  xhr.setRequestHeader("Accept", "application/json");
  xhr.onload = function() {
    if (xhr.readyState === 4) {
      document.getElementById("data").innerHTML = this.responseText
    }
  };
  xhr.send();
}
5. Get SQL query from Lookup database

Assuming this uri...

https://api.go4webdev.org/tsk/id/1

...the API fetches the corresponding SQL query in a lookup database. Which means that you can maintain the queries on-the-fly without restarting the API-server.

func Getquery(path string) string {
  // get query from lookup db
  var query string
  err := db.QueryRow("SELECT sql_query FROM sqls WHERE sql_id=$1", path).Scan(&query)
  if err != nil {
    path = ""
  }
  return query
}

which results in this query:

SELECT * FROM tsk WHERE tsk_id = $1
6. Execute the CRUD query and return data

The query endpoints uses a generic approach. Same code for many scopes. The enpoints in the API are here:

package main

import (
  //"fmt"
  "github.com/jmoiron/sqlx"
  _ "github.com/lib/pq"
  "net/http"
  "os"
  "strings"
)

var db *sqlx.DB

func main() {
  Connect()
  http.HandleFunc("/", handler)
  http.Handle("/favicon.ico", http.NotFoundHandler())
  http.ListenAndServe(":9998", nil)
}

func handler(w http.ResponseWriter, r *http.Request) {

  w.Header().Set("Access-Control-Allow-Origin", "*")
  w.Header().Set("Access-Control-Allow-Methods", "GET,HEAD,OPTIONS,POST,PUT,CREATE,DELETE")
  w.Header().Set("Access-Control-Allow-Headers", "*")
  w.Header().Set("Content-Type", "application/json")

  switch r.Method {
  case "DELETE":
    Delete(w, r)
  case "POST":
    Create(w, r)
  case "PUT":
    Update(w, r)
  default: //GET
    Get(w, r)
  }
}

func Getquery(path string) string {
  // get query from lookup db
  var query string
  err := db.QueryRow("SELECT sql_query FROM sqls WHERE sql_id=$1", path).Scan(&query)
  if err != nil {
    path = ""
  }
  return query
}

func getpath(r *http.Request) (string, string, string) {
  //split the url into 3 parts
  path := strings.Split(r.URL.String(), "/")
  switch len(path) {
  case 4:
    return path[1], path[2], path[3]
  case 3:
    return path[1], path[2], ""
  case 2:
    return path[1], "", ""
  default:
    return "", "", ""
  }
}

And the GET code for one record (regardless of scope) is:

package main

import (
  "encoding/json"
  "fmt"
  "net/http"
)

func Get(w http.ResponseWriter, r *http.Request) {
  scope, action, val := getpath(r)
  var data interface{}

  switch action {
  case "id":
    query := Getquery(scope)
    data = getid(query, val)
  case "all":
    query := Getquery(scope + "_" + action)
    fmt.Println(query)
    data = getall(query)
  }
  // convert to json and write
  json.NewEncoder(w).Encode(data)
}

// return a single row
func getid(query string, val string) interface{} {
  if len(query) > 0 {
    row := make(map[string]interface{})
    db.QueryRowx(query, val).MapScan(row)
    return (row)
  }
  return nil
}
7. Prepare HTML using JS and update browser

When AJAX get the response from the Go API, it can be formatted as needed into inner.HTML. This is the final part of the request (see #4). The data could be added into a table or grid by Vanilla Javascript. This example gives only the JSON output.

...
  xhr.onload = function() {
    if (xhr.readyState === 4) {
      document.getElementById("data").innerHTML = this.responseText
    }
  };
...

Step 3-7 above put together:

The result"