Go RESTful API interface development

Article directory

  • What is a RESTful API
  • Go popular web framework-Gin
  • Go Hello World
  • Gin routers and controllers
  • Gin handles request parameters
  • Generate HTTP request response
  • Gin learning content
  • Practical use of Gin framework to develop RESTful API
  • OAuth 2.0 interface understanding
  • Develop OAuth2.0 interface example with Go

There is a rule in programming – Don’t Repeat Yourself (don’t repeat your code). The core concept of this guideline is: if there is some repeated code, this code should be extracted and encapsulated into a method.
Over time, there are a number of methods that can be integrated into tool classes. If the tool classes form a large scale, they can be integrated into class libraries. The class library is more systematic and has more complete functions. Not only should you not re-create the existing “wheels” in the project, but you should also not reinvent the “wheels” that others have already built. Just use the existing “wheels”.

What is RESTful API

  • Resource overview
    • Resources can be singletons or collections
    • Resources can also contain sub-collection resources
    • REST APIs use Uniform Resource Identifiers (URIs) to locate resources
  • Use nouns to refer to resources
    • document
    • gather
    • storage
    • controller
  • maintain consistency
    • Use forward slashes (\) to indicate hierarchical relationships
    • Do not use a forward slash at the end of a URI
    • Use hyphens (-) to make URIs more readable
    • Do not use underscore (_)
    • Use lowercase letters in URIs
    • Do not use file extensions
  • Never use the name of a CRUD function in a URI
  • Filter a collection of URIs using query parameters

Go popular web framework-Gin

Go HelloWorld

package main

import (
"github.com/gin-gonic/gin"
)

func main() {<!-- -->
//Create a default routing engine
r := gin.Default()
// GET: request method; /hello: requested path
// When the client requests the /hello path with the GET method, the following anonymous function will be executed.
r.GET("/hello", func(c *gin.Context) {<!-- -->
// c.JSON: Returns data in JSON format
c.JSON(200, gin.H{<!-- -->
"message": "Hello world!",
})
})
// Start the HTTP service. The service is started at 0.0.0.0:8080 by default.
r.Run()
}

Gin Routing and Controller

  • routing rules
    • HTTP request method
      • GET
      • POST
      • PUT
      • DELETE
    • URL path
      • Static URL path
      • URL parameters with path
      • URL paths with asterisk (*) fuzzy matching parameters
    • Processor function
  • packet routing

Gin handles request parameters

  • Get GET request parameters
  • Get POST request parameters
  • Get URL path parameters
  • Bind request parameters to structure

Generate HTTP request response

  • Generate HTTP request response as a string
  • Generate HTTP request response in JSON format
  • Generate HTTP request responses in XML format
  • Generate HTTP request responses in file format
  • Set HTTP response headers

Gin learning content

  • Gin renders HTML templates
  • Gin handles static resources
  • Gin handles cookies
  • Gin file upload
  • Gin middleware
  • Gin Session

Practical use of Gin framework to develop RESTful API

mysql> CREATE TABLE `users` (
    -> `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
    -> `phone` VARCHAR(255) DEFAULT NULL,
    -> `name` VARCHAR(255) DEFAULT NULL,
    -> `password` VARCHAR(255) DEFAULT NULL,
    -> PRIMARY KEY (`id`)
    -> ) ENGINE=InnoDB AUTO_INCREMENT=39 DEFAULT CHARSET=utf8;
package main

import (
"crypto/sha256"
"fmt"
"github.com/gin-gonic/gin"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"net/http"
)

type (
User struct {<!-- -->
ID uint `json:"id" gorm:"column:id"`
Phone string `json:"phone" gorm:"column:phone"`
Name string `json:"name" gorm:"column:name"`
Password string `json:"password" gorm:"column:password"`
}

UserRes struct {<!-- -->
ID uint `json:"id"`
Phone string `json:"phone"`
Name string `json:"name"`
}
)

var db *gorm.DB

func main() {<!-- -->
// Connect to the database
var err error
dsn := "root:mm..1213@tcp(127.0.0.1:3306)/UserManager?charset=utf8mb4 & amp;parseTime=True & amp;loc=Local"
db, err = gorm.Open(mysql.New(mysql.Config{<!-- -->
DriverName: "mysql",
DSN: dsn,
}), & amp;gorm.Config{<!-- -->})
if err != nil {<!-- -->
panic("Failed to connect to database")
}

// Auto migrate the User struct to create the corresponding table in the database
err = db.AutoMigrate( & amp;User{<!-- -->})
if err != nil {<!-- -->
panic("Failed to migrate the database")
}

router := gin.Default()
v2 := router.Group("/api/v2/user")
{<!-- -->
v2.POST("/", createUser)
v2.GET("/", fetchAllUser)
v2.GET("/:id", fetchUser)
v2.PUT("/:id", updateUser)
v2.DELETE("/:id", deleteUser)
}
router.Run(":8080")
}

func createUser(c *gin.Context) {<!-- -->
phone := c.PostForm("phone")
name := c.PostForm("name")
user := User{<!-- -->
Phone: phone,
Name: name,
Password: md5Password(phone),
}

tx := db.Begin()
if err := tx.Create( & amp;user).Error; err != nil {<!-- -->
tx.Rollback()
c.JSON(http.StatusInternalServerError, gin.H{<!-- -->
"error": err.Error(),
})
return
}
tx.Commit()

c.JSON(http.StatusCreated, gin.H{<!-- -->
"status": http.StatusCreated,
"message": "User created successfully!",
"ID": user.ID,
})
}

func md5Password(password string) string {<!-- -->
hash := sha256.Sum256([]byte(password))
return fmt.Sprintf("%x", hash)
}

func fetchAllUser(c *gin.Context) {<!-- -->
varuser[]User
var_userRes[]UserRes
db.Find(&user)
if len(user) <= 0 {<!-- -->
c.JSON(
http.StatusNotFound,
gin.H{<!-- -->
"status": http.StatusNotFound,
"message": "No user found!",
})
return
}
for _, item := range user {<!-- -->
_userRes = append(_userRes,
UserRes{<!-- -->
ID: item.ID,
Phone: item.Phone,
Name: item.Name,
})
}
c.JSON(http.StatusOK,
gin.H{<!-- -->
"status": http.StatusOK,
"data": _userRes,
})
}

func fetchUser(c *gin.Context) {<!-- -->
var userUser
ID := c.Param("id")
db.First( & amp;user, ID)
if user.ID == 0 {<!-- -->
c.JSON(http.StatusNotFound, gin.H{<!-- -->"status": http.StatusNotFound, "message": "No user found!"})
return
}
res := UserRes{<!-- -->
ID: user.ID,
Phone: user.Phone,
Name: user.Name,
}
c.JSON(http.StatusOK, gin.H{<!-- -->"status": http.StatusOK, "data": res})
}

func updateUser(c *gin.Context) {<!-- -->
var userUser
userID := c.Param("id")
db.First(&user, userID)
if user.ID == 0 {<!-- -->
c.JSON(http.StatusNotFound, gin.H{<!-- -->"status": http.StatusNotFound, "message": "No user found!"})
return
}
db.Model( & amp;user).Update("phone", c.PostForm("phone"))
db.Model( & amp;user).Update("name", c.PostForm("name"))
c.JSON(http.StatusOK, gin.H{<!-- -->
"status": http.StatusOK,
"message": "Updated User Successfully!",
})
}

func deleteUser(c *gin.Context) {<!-- -->
var userUser
userID := c.Param("id")
db.First(&user, userID)
if user.ID == 0 {<!-- -->
c.JSON(http.StatusNotFound, gin.H{<!-- -->
"status": http.StatusNotFound,
"message": "No user found!",
})
return
}
db.Delete(&user)
c.JSON(http.StatusOK, gin.H{<!-- -->"status": http.StatusOK, "message": "User deleted successfully!"})
}

  • GoLand Tools-Http Client Test
DELETE http://127.0.0.1:8080/api/v2/user/58
Content-Type: application/x-www-form-urlencoded

phone=10086 &name=chYiDong

Understanding the OAuth 2.0 interface

Develop OAuth2.0 interface example with Go

  • After understanding it, the possibility of using it is relatively small.
  1. GitHub OAuth application registration
  • Registration page: https://github.com/settings/applications/new
  1. Login authorization page
<!DOCTYPE HTML>
<html>
<body>
<a href="https://github.com/login/oauth/authorize?client_id=5bcf804cfeb0ef7120f5 & amp;redirect_uri=http://localhost:8087/oauth/redirect">
    Login by GitHub
</a>
</body>
</html>
  1. Welcome Screen
<!DOCTYPE HTML>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, INItial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Hello</title>
</head>
<body>
</body>
<script>
    //Get url parameters
    function getQueryVariable(variable) {<!-- -->
        var query = window.location.search.substring(1);
        var vars = query.split(" & amp;");
        for (var i = 0; i < vars.length; i + + ) {<!-- -->
            var pair = vars[i].split("=");
            if (pair[0] == variable) {<!-- -->
                return pair[1];
            }
        }
        return (false);
    }
    // Get access_token
    const token = getQueryVariable("access_token");
    // Call user information interface
    fetch('https://api.github.com/user', {<!-- -->
        headers: {<!-- -->
            Authorization: 'token ' + token
        }
    })
    // Parse the requested JSON
     .then(res => res.json())
     .then(res => {<!-- -->
        //Return user information
        const nameNode = document.createTextNode(`Hi, ${<!-- -->res.name}, Welcome to login our site by GitHub!`)
        document.body.appendChild(nameNode)
     })
</script>
</html>
  1. Written in Go language

package main

import (
"encoding/json"
"fmt"
"html/template"
"net/http"
"os"
)

// const clientID = "<your client id>"
const clientID = "5bcf804cfeb0ef7120f5"

// const clientSecret = "<your client secret>"
const clientSecret = "8d31102da18096d13eb6ec819cd81ca898ed7189"

func hello(w http.ResponseWriter, r *http.Request) {<!-- -->
if r.Method == "GET" {<!-- -->
t, _ := template.ParseFiles("hello.html")
t.Execute(w, nil)
}
}

func login(w http.ResponseWriter, r *http.Request) {<!-- -->
if r.Method == "GET" {<!-- -->
t, _ := template.ParseFiles("login.html")
t.Execute(w, nil)
}
}

func main() {<!-- -->
http.HandleFunc("/login", login)
http.HandleFunc("/", hello)
http.HandleFunc("/hello", hello)

httpClient := http.Client{<!-- -->}
http.HandleFunc("/oauth/redirect", func(w http.ResponseWriter, r *http.Request) {<!-- -->
err := r.ParseForm()
if err != nil {<!-- -->
fmt.Fprintf(os.Stdout, "could not parse query: %v", err)
w.WriteHeader(http.StatusBadRequest)
}
code := r.FormValue("code")

reqURL := fmt.Sprintf("https://github.com/login/oauth/access_token?" +
"client_id=%s & amp;client_secret=%s & amp;code=%s", clientID, clientSecret, code)
req, err := http.NewRequest(http.MethodPost, reqURL, nil)
if err != nil {<!-- -->
fmt.Fprintf(os.Stdout, "could not create HTTP request: %v", err)
w.WriteHeader(http.StatusBadRequest)
}
req.Header.Set("accept", "application/json")

res, err := httpClient.Do(req)
if err != nil {<!-- -->
fmt.Fprintf(os.Stdout, "could not send HTTP request: %v", err)
w.WriteHeader(http.StatusInternalServerError)
}
defer res.Body.Close()

var tAccessTokenResponse
if err := json.NewDecoder(res.Body).Decode( & amp;t); err != nil {<!-- -->
fmt.Fprintf(os.Stdout, "could not parse JSON response: %v", err)
w.WriteHeader(http.StatusBadRequest)
}

w.Header().Set("Location", "/hello.html?access_token=" + t.AccessToken)
w.WriteHeader(http.StatusFound)
})

http.ListenAndServe(":8087", nil)
}

type AccessTokenResponse struct {<!-- -->
AccessToken string `json:"access_token"`
}